diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ebc8bf..4fa4a13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.5.0 (12/10/2015) + +### Changes + +* Initial support for user credentials ([@sqrrrl][]) +* Update Signet to 0.7 + ## 0.4.2 (05/08/2015) ### Changes diff --git a/Gemfile b/Gemfile index c1ba049..ebeec76 100755 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,7 @@ group :development do gem 'fakeredis', '~> 0.5' gem 'webmock', '~> 1.21' gem 'rack-test', '~> 0.6' + gem 'sinatra' end platforms :jruby do diff --git a/README.md b/README.md index ad3d52b..e45c287 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,8 @@ $ gem install googleauth require 'googleauth' # Get the environment configured authorization -scopes = ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/compute'] +scopes = ['https://www.googleapis.com/auth/cloud-platform', + 'https://www.googleapis.com/auth/compute'] authorization = Google::Auth.get_application_default(scopes) # Add the the access token obtained using the authorization to a hash, e.g @@ -61,6 +62,84 @@ and authorization level for the application independent of the user. This is the recommended approach to authorize calls to Cloud APIs, particularly when you're building an application that uses Google Compute Engine. +## User Credentials + +The library also provides support for requesting and storing user +credentials (3-Legged OAuth2.) Two implementations are currently available, +a generic authorizer useful for command line apps or custom integrations as +well as a web variant tailored toward Rack-based applications. + +The authorizers are intended for authorization use cases. For sign-on, +see [Google Idenity Platform](https://developers.google.com/identity/) + +### Example (Web) + +```ruby +require 'googleauth' +require 'googleauth/web_user_authorizer' +require 'googleauth/stores/redis_token_store' +require 'redis' + +client_id = Google::Auth::ClientId.from_file('/path/to/client_secrets.json') +scope = ['https://www.googleapis.com/auth/drive'] +token_store = Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new) +authorizer = Google::Auth::WebUserAuthorizer.new( + client_id, scope, token_store, '/oauth2callback') + + +get('/authorize') do + # NOTE: Assumes the user is already authenticated to the app + user_id = request.session['user_id'] + credentials = authorizer.get_credentials(user_id, request) + if credentials.nil? + redirect authorizer.get_authorization_url(user_id: user_id, request: request) + end + # Credentials are valid, can call APIs + # ... +end + +get('/oauth2callback') do + target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred( + request) + redirect target_url +end +``` + +### Example (Command Line) + +```ruby +require 'googleauth' +require 'googleauth/stores/file_token_store' + +scope = 'https://www.googleapis.com/auth/drive' +client_id = Google::Auth::ClientId.from_file('/path/to/client_secrets.json') +token_store = Google::Auth::Stores::FileTokenStore.new( + :file => '/path/to/tokens.yaml') +authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store) + +credentials = authorizer.get_credentials(user_id) +if credentials.nil? + url = authorizer.get_authorization_url(base_url: 'urn:ietf:wg:oauth:2.0:oob') + puts "Open #{url} in your browser and enter the resulting code:" + code = gets + credentials = authorizer.get_and_store_credentials_from_code( + user_id: user_id, code: code, base_url: OOB_URI) +end + +# OK to use credentials +``` + +### Storage + +Authorizers require a storage instance to manage long term persistence of +access and refresh tokens. Two storage implementations are included: + +* Google::Auth::Stores::FileTokenStore +* Google::Auth::Stores::RedisTokenStore + +Custom storage implementations can also be used. See +[token_store.rb](lib/googleauth/token_store.rb) for additional details. + ## What about auth in google-apis-ruby-client? The goal is for all auth done by diff --git a/googleauth.gemspec b/googleauth.gemspec index 482ad24..bb52743 100755 --- a/googleauth.gemspec +++ b/googleauth.gemspec @@ -30,5 +30,5 @@ Gem::Specification.new do |s| s.add_dependency 'jwt', '~> 1.4' s.add_dependency 'memoist', '~> 0.12' s.add_dependency 'multi_json', '~> 1.11' - s.add_dependency 'signet', '~> 0.6' + s.add_dependency 'signet', '~> 0.7' end diff --git a/lib/googleauth/signet.rb b/lib/googleauth/signet.rb index d090dab..9ba2e36 100644 --- a/lib/googleauth/signet.rb +++ b/lib/googleauth/signet.rb @@ -42,7 +42,7 @@ module Signet def apply!(a_hash, opts = {}) # fetch the access token there is currently not one, or if the client # has expired - fetch_access_token!(opts) if access_token.nil? || expired? + fetch_access_token!(opts) if access_token.nil? || expires_within?(60) a_hash[AUTH_METADATA_KEY] = "Bearer #{access_token}" end @@ -65,7 +65,7 @@ module Signet end alias_method :orig_fetch_access_token!, :fetch_access_token! - def fetch_access_token!(options) + def fetch_access_token!(options = {}) info = orig_fetch_access_token!(options) notify_refresh_listeners info diff --git a/lib/googleauth/version.rb b/lib/googleauth/version.rb index e39a9c4..84408ae 100644 --- a/lib/googleauth/version.rb +++ b/lib/googleauth/version.rb @@ -31,6 +31,6 @@ module Google # Module Auth provides classes that provide Google-specific authorization # used to access Google APIs. module Auth - VERSION = '0.4.2' + VERSION = '0.5.0' end end diff --git a/lib/googleauth/web_user_authorizer.rb b/lib/googleauth/web_user_authorizer.rb index 0accff7..3cc1d01 100644 --- a/lib/googleauth/web_user_authorizer.rb +++ b/lib/googleauth/web_user_authorizer.rb @@ -44,16 +44,16 @@ module Google # user_id = request.session['user_email'] # credentials = authorizer.get_credentials(user_id, request) # if credentials.nil? - # redirect authorizer.get_redirect_uri(user_id, request) + # redirect authorizer.get_authorization_url(user_id: user_id, request: request) # end # # Credentials are valid, can call APIs # ... # end # # get('/oauth2callback') do - # user_id = request.session['user_email'] - # _, return_uri = authorizer.handle_auth_callback(user_id, request) - # redirect return_uri + # target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred( + # request) + # redirect target_url # end # # Instead of implementing the callback directly, applications are diff --git a/spec/googleauth/apply_auth_examples.rb b/spec/googleauth/apply_auth_examples.rb index 90c010d..19c5be3 100644 --- a/spec/googleauth/apply_auth_examples.rb +++ b/spec/googleauth/apply_auth_examples.rb @@ -57,124 +57,101 @@ shared_examples 'apply/apply! are OK' do # auth client describe '#fetch_access_token' do let(:token) { '1/abcdef1234567890' } - let(:stubs) do + let(:stub) do make_auth_stubs access_token: token end - let(:connection) do - Faraday.new do |b| - b.adapter(:test, stubs) - end - end it 'should set access_token to the fetched value' do - @client.fetch_access_token!(connection: connection) + stub + @client.fetch_access_token! expect(@client.access_token).to eq(token) - stubs.verify_stubbed_calls + expect(stub).to have_been_requested end it 'should notify refresh listeners after updating' do + stub expect do |b| @client.on_refresh(&b) - @client.fetch_access_token!(connection: connection) + @client.fetch_access_token! end.to yield_with_args(have_attributes( access_token: '1/abcdef1234567890')) - stubs.verify_stubbed_calls + expect(stub).to have_been_requested end end describe '#apply!' do it 'should update the target hash with fetched access token' do token = '1/abcdef1234567890' - stubs = make_auth_stubs access_token: token - c = Faraday.new do |b| - b.adapter(:test, stubs) - end + stub = make_auth_stubs access_token: token md = { foo: 'bar' } - @client.apply!(md, connection: c) + @client.apply!(md) want = { :foo => 'bar', auth_key => "Bearer #{token}" } expect(md).to eq(want) - stubs.verify_stubbed_calls + expect(stub).to have_been_requested end end describe 'updater_proc' do it 'should provide a proc that updates a hash with the access token' do token = '1/abcdef1234567890' - stubs = make_auth_stubs access_token: token - c = Faraday.new do |b| - b.adapter(:test, stubs) - end - + stub = make_auth_stubs access_token: token md = { foo: 'bar' } the_proc = @client.updater_proc - got = the_proc.call(md, connection: c) + got = the_proc.call(md) want = { :foo => 'bar', auth_key => "Bearer #{token}" } expect(got).to eq(want) - stubs.verify_stubbed_calls + expect(stub).to have_been_requested end end describe '#apply' do it 'should not update the original hash with the access token' do token = '1/abcdef1234567890' - stubs = make_auth_stubs access_token: token - c = Faraday.new do |b| - b.adapter(:test, stubs) - end + stub = make_auth_stubs access_token: token md = { foo: 'bar' } - @client.apply(md, connection: c) + @client.apply(md) want = { foo: 'bar' } expect(md).to eq(want) - stubs.verify_stubbed_calls + expect(stub).to have_been_requested end it 'should add the token to the returned hash' do token = '1/abcdef1234567890' - stubs = make_auth_stubs access_token: token - c = Faraday.new do |b| - b.adapter(:test, stubs) - end + stub = make_auth_stubs access_token: token md = { foo: 'bar' } - got = @client.apply(md, connection: c) + got = @client.apply(md) want = { :foo => 'bar', auth_key => "Bearer #{token}" } expect(got).to eq(want) - stubs.verify_stubbed_calls + expect(stub).to have_been_requested end it 'should not fetch a new token if the current is not expired' do token = '1/abcdef1234567890' - stubs = make_auth_stubs access_token: token - c = Faraday.new do |b| - b.adapter(:test, stubs) - end + stub = make_auth_stubs access_token: token n = 5 # arbitrary n.times do |_t| md = { foo: 'bar' } - got = @client.apply(md, connection: c) + got = @client.apply(md) want = { :foo => 'bar', auth_key => "Bearer #{token}" } expect(got).to eq(want) end - stubs.verify_stubbed_calls + expect(stub).to have_been_requested end it 'should fetch a new token if the current one is expired' do token_1 = '1/abcdef1234567890' - token_2 = '2/abcdef1234567890' + token_2 = '2/abcdef1234567891' [token_1, token_2].each do |t| - stubs = make_auth_stubs access_token: t - c = Faraday.new do |b| - b.adapter(:test, stubs) - end + make_auth_stubs access_token: t md = { foo: 'bar' } - got = @client.apply(md, connection: c) + got = @client.apply(md) want = { :foo => 'bar', auth_key => "Bearer #{t}" } expect(got).to eq(want) - stubs.verify_stubbed_calls @client.expires_at -= 3601 # default is to expire in 1hr end end diff --git a/spec/googleauth/compute_engine_spec.rb b/spec/googleauth/compute_engine_spec.rb index c14ddca..d79af8c 100644 --- a/spec/googleauth/compute_engine_spec.rb +++ b/spec/googleauth/compute_engine_spec.rb @@ -37,7 +37,7 @@ require 'googleauth/compute_engine' require 'spec_helper' describe Google::Auth::GCECredentials do - MD_URI = '/computeMetadata/v1/instance/service-accounts/default/token' + MD_URI = 'http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token' GCECredentials = Google::Auth::GCECredentials before(:example) do @@ -46,16 +46,14 @@ describe Google::Auth::GCECredentials do def make_auth_stubs(opts = {}) access_token = opts[:access_token] || '' - Faraday::Adapter::Test::Stubs.new do |stub| - stub.get(MD_URI) do |env| - headers = env[:request_headers] - expect(headers['Metadata-Flavor']).to eq('Google') - build_json_response( - 'access_token' => access_token, - 'token_type' => 'Bearer', - 'expires_in' => 3600) - end - end + body = MultiJson.dump('access_token' => access_token, + 'token_type' => 'Bearer', + 'expires_in' => 3600) + stub_request(:get, MD_URI) + .with(headers: { 'Metadata-Flavor' => 'Google' }) + .to_return(body: body, + status: 200, + headers: { 'Content-Type' => 'application/json' }) end it_behaves_like 'apply/apply! are OK' @@ -63,83 +61,48 @@ describe Google::Auth::GCECredentials do context 'metadata is unavailable' do describe '#fetch_access_token' do it 'should fail if the metadata request returns a 404' do - stubs = Faraday::Adapter::Test::Stubs.new do |stub| - stub.get(MD_URI) do |_env| - [404, - { 'Metadata-Flavor' => 'Google' }, - ''] - end - end - c = Faraday.new do |b| - b.adapter(:test, stubs) - end - blk = proc { @client.fetch_access_token!(connection: c) } + stub = stub_request(:get, MD_URI) + .to_return(status: 404, + headers: { 'Metadata-Flavor' => 'Google' }) + blk = proc { @client.fetch_access_token! } expect(&blk).to raise_error Signet::AuthorizationError - stubs.verify_stubbed_calls + expect(stub).to have_been_requested end it 'should fail if the metadata request returns an unexpected code' do - stubs = Faraday::Adapter::Test::Stubs.new do |stub| - stub.get(MD_URI) do |_env| - [503, - { 'Metadata-Flavor' => 'Google' }, - ''] - end - end - c = Faraday.new do |b| - b.adapter(:test, stubs) - end - blk = proc { @client.fetch_access_token!(connection: c) } + stub = stub_request(:get, MD_URI) + .to_return(status: 503, + headers: { 'Metadata-Flavor' => 'Google' }) + blk = proc { @client.fetch_access_token! } expect(&blk).to raise_error Signet::AuthorizationError - stubs.verify_stubbed_calls + expect(stub).to have_been_requested end end end describe '#on_gce?' do it 'should be true when Metadata-Flavor is Google' do - stubs = Faraday::Adapter::Test::Stubs.new do |stub| - stub.get('/') do |_env| - [200, - { 'Metadata-Flavor' => 'Google' }, - ''] - end - end - c = Faraday.new do |b| - b.adapter(:test, stubs) - end - expect(GCECredentials.on_gce?(connection: c)).to eq(true) - stubs.verify_stubbed_calls + stub = stub_request(:get, 'http://169.254.169.254') + .to_return(status: 200, + headers: { 'Metadata-Flavor' => 'Google' }) + expect(GCECredentials.on_gce?({}, true)).to eq(true) + expect(stub).to have_been_requested end it 'should be false when Metadata-Flavor is not Google' do - stubs = Faraday::Adapter::Test::Stubs.new do |stub| - stub.get('/') do |_env| - [200, - { 'Metadata-Flavor' => 'NotGoogle' }, - ''] - end - end - c = Faraday.new do |b| - b.adapter(:test, stubs) - end - expect(GCECredentials.on_gce?(connection: c)).to eq(false) - stubs.verify_stubbed_calls + stub = stub_request(:get, 'http://169.254.169.254') + .to_return(status: 200, + headers: { 'Metadata-Flavor' => 'NotGoogle' }) + expect(GCECredentials.on_gce?({}, true)).to eq(false) + expect(stub).to have_been_requested end it 'should be false if the response is not 200' do - stubs = Faraday::Adapter::Test::Stubs.new do |stub| - stub.get('/') do |_env| - [404, - { 'Metadata-Flavor' => 'Google' }, - ''] - end - end - c = Faraday.new do |b| - b.adapter(:test, stubs) - end - expect(GCECredentials.on_gce?(connection: c)).to eq(false) - stubs.verify_stubbed_calls + stub = stub_request(:get, 'http://169.254.169.254') + .to_return(status: 404, + headers: { 'Metadata-Flavor' => 'NotGoogle' }) + expect(GCECredentials.on_gce?({}, true)).to eq(false) + expect(stub).to have_been_requested end end end diff --git a/spec/googleauth/get_application_default_spec.rb b/spec/googleauth/get_application_default_spec.rb index 26eda35..5f2521d 100644 --- a/spec/googleauth/get_application_default_spec.rb +++ b/spec/googleauth/get_application_default_spec.rb @@ -37,6 +37,9 @@ require 'googleauth' require 'spec_helper' describe '#get_application_default' do + # Pass unique options each time to bypass memoization + let(:options) { |example| { dememoize: example } } + before(:example) do @key = OpenSSL::PKey::RSA.new(2048) @var_name = ENV_VAR @@ -59,31 +62,24 @@ describe '#get_application_default' do Dir.mktmpdir do |dir| key_path = File.join(dir, 'does-not-exist') ENV[@var_name] = key_path - expect { Google::Auth.get_application_default(@scope) } + expect { Google::Auth.get_application_default(@scope, options) } .to raise_error RuntimeError end end it 'fails without default file or env if not on compute engine' do - stubs = Faraday::Adapter::Test::Stubs.new do |stub| - stub.get('/') do |_env| - [404, - { 'Metadata-Flavor' => 'Google' }, - ''] - end - end # GCE not detected + stub = stub_request(:get, 'http://169.254.169.254') + .to_return(status: 404, + headers: { 'Metadata-Flavor' => 'NotGoogle' }) Dir.mktmpdir do |dir| ENV.delete(@var_name) unless ENV[@var_name].nil? # no env var ENV['HOME'] = dir # no config present in this tmp dir - c = Faraday.new do |b| - b.adapter(:test, stubs) - end blk = proc do - Google::Auth.get_application_default(@scope, connection: c) + Google::Auth.get_application_default(@scope, options) end expect(&blk).to raise_error RuntimeError end - stubs.verify_stubbed_calls + expect(stub).to have_been_requested end end @@ -94,7 +90,8 @@ describe '#get_application_default' do FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV[@var_name] = key_path - expect(Google::Auth.get_application_default(@scope)).to_not be_nil + expect(Google::Auth.get_application_default(@scope, options)) + .to_not be_nil end end @@ -105,7 +102,8 @@ describe '#get_application_default' do FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV['HOME'] = dir - expect(Google::Auth.get_application_default(@scope)).to_not be_nil + expect(Google::Auth.get_application_default(@scope, options)) + .to_not be_nil end end @@ -116,30 +114,21 @@ describe '#get_application_default' do FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) ENV['HOME'] = dir - expect(Google::Auth.get_application_default).to_not be_nil + expect(Google::Auth.get_application_default(nil, options)).to_not be_nil end end it 'succeeds without default file or env if on compute engine' do - stubs = Faraday::Adapter::Test::Stubs.new do |stub| - stub.get('/') do |_env| - [200, - { 'Metadata-Flavor' => 'Google' }, - ''] - end - end # GCE detected + stub = stub_request(:get, 'http://169.254.169.254') + .to_return(status: 200, + headers: { 'Metadata-Flavor' => 'Google' }) Dir.mktmpdir do |dir| ENV.delete(@var_name) unless ENV[@var_name].nil? # no env var ENV['HOME'] = dir # no config present in this tmp dir - c = Faraday.new do |b| - b.adapter(:test, stubs) - end - creds = Google::Auth.get_application_default( - @scope, - connection: c) + creds = Google::Auth.get_application_default(@scope, options) expect(creds).to_not be_nil end - stubs.verify_stubbed_calls + expect(stub).to have_been_requested end it 'succeeds with system default file' do @@ -148,7 +137,8 @@ describe '#get_application_default' do key_path = File.join('/etc/google/auth/', CREDENTIALS_FILE_NAME) FileUtils.mkdir_p(File.dirname(key_path)) File.write(key_path, cred_json_text) - expect(Google::Auth.get_application_default(@scope)).to_not be_nil + expect(Google::Auth.get_application_default(@scope, options)) + .to_not be_nil File.delete(key_path) end end @@ -161,7 +151,8 @@ describe '#get_application_default' do ENV[CLIENT_SECRET_VAR] = cred_json[:client_secret] ENV[REFRESH_TOKEN_VAR] = cred_json[:refresh_token] ENV[ACCOUNT_TYPE_VAR] = cred_json[:type] - expect(Google::Auth.get_application_default(@scope)).to_not be_nil + expect(Google::Auth.get_application_default(@scope, options)) + .to_not be_nil end end @@ -225,7 +216,7 @@ describe '#get_application_default' do File.write(key_path, cred_json_text) ENV[@var_name] = key_path blk = proc do - Google::Auth.get_application_default(@scope) + Google::Auth.get_application_default(@scope, options) end expect(&blk).to raise_error RuntimeError end @@ -239,7 +230,7 @@ describe '#get_application_default' do File.write(key_path, cred_json_text) ENV['HOME'] = dir blk = proc do - Google::Auth.get_application_default(@scope) + Google::Auth.get_application_default(@scope, options) end expect(&blk).to raise_error RuntimeError end @@ -249,7 +240,7 @@ describe '#get_application_default' do ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] blk = proc do - Google::Auth.get_application_default(@scope) + Google::Auth.get_application_default(@scope, options) end expect(&blk).to raise_error RuntimeError end diff --git a/spec/googleauth/service_account_spec.rb b/spec/googleauth/service_account_spec.rb index 8af2ff7..8b07a3b 100644 --- a/spec/googleauth/service_account_spec.rb +++ b/spec/googleauth/service_account_spec.rb @@ -129,16 +129,21 @@ describe Google::Auth::ServiceAccountCredentials do def make_auth_stubs(opts = {}) access_token = opts[:access_token] || '' - Faraday::Adapter::Test::Stubs.new do |stub| - stub.post('/oauth2/v3/token') do |env| - params = Addressable::URI.form_unencode(env[:body]) - _claim, _header = JWT.decode(params.assoc('assertion').last, - @key.public_key) - want = ['grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer'] - expect(params.assoc('grant_type')).to eq(want) - build_access_token_json(access_token) - end + body = MultiJson.dump('access_token' => access_token, + 'token_type' => 'Bearer', + 'expires_in' => 3600) + blk = proc do |request| + params = Addressable::URI.form_unencode(request.body) + _claim, _header = JWT.decode(params.assoc('assertion').last, + @key.public_key) end + stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token') + .with(body: hash_including( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer'), + &blk) + .to_return(body: body, + status: 200, + headers: { 'Content-Type' => 'application/json' }) end def cred_json_text diff --git a/spec/googleauth/signet_spec.rb b/spec/googleauth/signet_spec.rb index 884e2fe..e855d40 100644 --- a/spec/googleauth/signet_spec.rb +++ b/spec/googleauth/signet_spec.rb @@ -50,16 +50,21 @@ describe Signet::OAuth2::Client do def make_auth_stubs(opts) access_token = opts[:access_token] || '' - Faraday::Adapter::Test::Stubs.new do |stub| - stub.post('/o/oauth2/token') do |env| - params = Addressable::URI.form_unencode(env[:body]) - _claim, _header = JWT.decode(params.assoc('assertion').last, - @key.public_key) - want = ['grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer'] - expect(params.assoc('grant_type')).to eq(want) - build_access_token_json(access_token) - end + body = MultiJson.dump('access_token' => access_token, + 'token_type' => 'Bearer', + 'expires_in' => 3600) + blk = proc do |request| + params = Addressable::URI.form_unencode(request.body) + _claim, _header = JWT.decode(params.assoc('assertion').last, + @key.public_key) end + stub_request(:post, 'https://accounts.google.com/o/oauth2/token') + .with(body: hash_including( + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer'), + &blk) + .to_return(body: body, + status: 200, + headers: { 'Content-Type' => 'application/json' }) end it_behaves_like 'apply/apply! are OK' diff --git a/spec/googleauth/user_refresh_spec.rb b/spec/googleauth/user_refresh_spec.rb index 9b87b78..7e86578 100644 --- a/spec/googleauth/user_refresh_spec.rb +++ b/spec/googleauth/user_refresh_spec.rb @@ -65,14 +65,14 @@ describe Google::Auth::UserRefreshCredentials do def make_auth_stubs(opts = {}) access_token = opts[:access_token] || '' - Faraday::Adapter::Test::Stubs.new do |stub| - stub.post('/oauth2/v3/token') do |env| - params = Addressable::URI.form_unencode(env[:body]) - want = %w(grant_type refresh_token) - expect(params.assoc('grant_type')).to eq(want) - build_access_token_json(access_token) - end - end + body = MultiJson.dump('access_token' => access_token, + 'token_type' => 'Bearer', + 'expires_in' => 3600) + stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token') + .with(body: hash_including('grant_type' => 'refresh_token')) + .to_return(body: body, + status: 200, + headers: { 'Content-Type' => 'application/json' }) end def cred_json_text(missing = nil) @@ -244,70 +244,50 @@ describe Google::Auth::UserRefreshCredentials do end describe 'when revoking a refresh token' do - let(:stubs) do - Faraday::Adapter::Test::Stubs.new do |stub| - stub.get('/o/oauth2/revoke') do |env| - expect(env.params['token']).to eql 'refreshtoken' - [200] - end - end - end - - let(:connection) do - Faraday.new do |c| - c.adapter(:test, stubs) - end + let(:stub) do + stub_request(:get, 'https://accounts.google.com/o/oauth2/revoke' \ + '?token=refreshtoken') + .to_return(status: 200, + headers: { 'Content-Type' => 'application/json' }) end before(:example) do - @client.revoke!(connection: connection) + stub + @client.revoke! end it_behaves_like 'revoked token' end describe 'when revoking an access token' do - let(:stubs) do - Faraday::Adapter::Test::Stubs.new do |stub| - stub.get('/o/oauth2/revoke') do |env| - expect(env.params['token']).to eql 'accesstoken' - [200] - end - end - end - - let(:connection) do - Faraday.new do |c| - c.adapter(:test, stubs) - end + let(:stub) do + stub_request(:get, 'https://accounts.google.com/o/oauth2/revoke' \ + '?token=accesstoken') + .to_return(status: 200, + headers: { 'Content-Type' => 'application/json' }) end before(:example) do + stub @client.refresh_token = nil @client.access_token = 'accesstoken' - @client.revoke!(connection: connection) + @client.revoke! end it_behaves_like 'revoked token' end describe 'when revoking an invalid token' do - let(:stubs) do - Faraday::Adapter::Test::Stubs.new do |stub| - stub.get('/o/oauth2/revoke') do |_env| - [400] - end - end - end - - let(:connection) do - Faraday.new do |c| - c.adapter(:test, stubs) - end + let(:stub) do + stub_request(:get, 'https://accounts.google.com/o/oauth2/revoke' \ + '?token=refreshtoken') + .to_return(status: 400, + headers: { 'Content-Type' => 'application/json' }) end it 'raises an authorization error' do - expect { @client.revoke!(connection: connection) }.to raise_error( + stub + expect { @client.revoke! }.to raise_error( Signet::AuthorizationError) end end