diff --git a/Gemfile b/Gemfile index 616e438..a4b27f9 100644 --- a/Gemfile +++ b/Gemfile @@ -4,6 +4,8 @@ gem "multipart-post" gem "oauth2" gem "json" gem "addressable" +gem "sinatra" +gem "launchy" group :development do gem "rspec" diff --git a/Gemfile.lock b/Gemfile.lock index b661fc1..a8e7bbc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -15,6 +15,8 @@ GEM json (1.7.7) jwt (0.1.5) multi_json (>= 1.0) + launchy (2.1.0) + addressable (~> 2.2.6) multi_json (1.6.0) multipart-post (1.1.5) oauth2 (0.8.0) @@ -24,6 +26,8 @@ GEM multi_json (~> 1.0) rack (~> 1.2) rack (1.4.5) + rack-protection (1.5.3) + rack rake (0.9.2.2) rspec (2.10.0) rspec-core (~> 2.10.0) @@ -33,6 +37,11 @@ GEM rspec-expectations (2.10.0) diff-lcs (~> 1.1.3) rspec-mocks (2.10.1) + sinatra (1.4.5) + rack (~> 1.4) + rack-protection (~> 1.4) + tilt (~> 1.3, >= 1.3.4) + tilt (1.4.1) webmock (1.8.7) addressable (>= 2.2.7) crack (>= 0.1.7) @@ -45,7 +54,9 @@ DEPENDENCIES bundler jeweler (~> 1.6.4) json + launchy multipart-post oauth2 rspec + sinatra webmock diff --git a/README.markdown b/README.markdown index a432952..8a2be58 100644 --- a/README.markdown +++ b/README.markdown @@ -10,7 +10,23 @@ RubyBox provides a simple, chainable, feature-rich client for [Box's 2.0 API](ht Authorization ------------- -RubyBox uses Box's OAuth2 Implementaton, Here are the steps involved in authorizing a client: +RubyBox uses Box's OAuth2 Implementaton. For any UNIX based operating systems (including Linux, MacOS) you can authorize the application through Box's UI and generate the access code and refresh code automatically based on your client ID and client secret. For non UNIX based operating systems, you will have to manually generate the access token and refresh token and find a way to store the refresh token. + +For UNIX based operating systems, here are the steps involved in authorizing a client: + +```ruby +require 'ruby-box' + +session = RubyBox::Session.new({ + client_id: 'your-client-id', + client_secret: 'your-client-secret' +}) +client = RubyBox::Client.new(session) +``` +This should automatically generate the required access token and refresh token for the client to work. You will be required to grant access to the application through Box's UI, every time you run you invoke ruby-box. The access token will be automatically refreshed and there is no need to write any extra lines of code to store the refresh token. + + +For Non-UNIX based operating systems, here are the steps involved in authorizing a client: __1)__ Get the authorization url. diff --git a/lib/ruby-box/box_authenticator.rb b/lib/ruby-box/box_authenticator.rb new file mode 100755 index 0000000..f82ec0a --- /dev/null +++ b/lib/ruby-box/box_authenticator.rb @@ -0,0 +1,16 @@ +require 'sinatra' + +code = nil + +get '/' do + code = params[:code] + if !code.nil? + puts "code=#{code}" + body "Box access code captured. Please return to your program to continue. You can close this window." + end +end + +after do + puts body + Process.kill 'TERM', Process.pid +end diff --git a/lib/ruby-box/session.rb b/lib/ruby-box/session.rb index 8cf96e6..8f33c49 100644 --- a/lib/ruby-box/session.rb +++ b/lib/ruby-box/session.rb @@ -1,4 +1,5 @@ require 'oauth2' +require 'launchy' module RubyBox class Session @@ -15,8 +16,25 @@ def initialize(opts={}, backoff=0.1) if opts[:client_id] @oauth2_client = OAuth2::Client.new(opts[:client_id], opts[:client_secret], OAUTH2_URLS.dup) - @access_token = OAuth2::AccessToken.new(@oauth2_client, opts[:access_token]) if opts[:access_token] - @refresh_token = opts[:refresh_token] + + ##Redirect to box to generate access key and refresh token only on UNIX based OS + if ::File.exist?('/bin/uname') || ::File.exist?('/usr/bin/uname') + Launchy.open("https://app.box.com/api/oauth2/authorize?response_type=code&client_id=#{opts[:client_id]}&redirect_uri=http://localhost:4567") + authTokenCodeOutput = `ruby #{::File.dirname(__FILE__)}/box_authenticator.rb 2> /dev/null` + if authTokenCodeOutput =~ /code=(\w+)/ + authTokenCode = $1 + #puts "Got Auth Code #{authTokenCode}" + else + puts "**ERROR** : Authorization Token Code is not available." + exit + end + @token = get_access_token(authTokenCode) + @access_token = @token + @refresh_token = @token.refresh_token + else + @access_token = OAuth2::AccessToken.new(@oauth2_client, opts[:access_token]) if opts[:access_token] + @refresh_token = opts[:refresh_token] + end @as_user = opts[:as_user] else # Support legacy API for historical reasons. @api_key = opts[:api_key] @@ -38,6 +56,7 @@ def get_access_token(code) def refresh_token(refresh_token) refresh_access_token_obj = OAuth2::AccessToken.new(@oauth2_client, @access_token.token, {'refresh_token' => refresh_token}) @access_token = refresh_access_token_obj.refresh! + @refresh_token = @access_token.refresh_token end def build_auth_header @@ -60,15 +79,24 @@ def request(uri, request, raw=false, retries=0) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true - http.ssl_version = :SSLv3 + http.ssl_version = :TLSv1_2 + #http.ssl_version = :SSLv3 #http.set_debug_output($stdout) + ##Clear the existing request fields to avoid duplicates + if request.key?('Authorization') + request.delete('Authorization') + end + if @access_token request.add_field('Authorization', "Bearer #{@access_token.token}") else request.add_field('Authorization', build_auth_header) end + if request.key?('As-User') + request.delete('As-User') + end request.add_field('As-User', "#{@as_user}") if @as_user