Merge pull request #1 from tbetbetbe/ruby-auth-initial-port-from-umbrella-project
Ruby auth initial port from umbrella project
This commit is contained in:
		
						commit
						648b30e937
					
				|  | @ -1,3 +1,5 @@ | |||
| *~ | ||||
| Gemfile.lock | ||||
| *.gem | ||||
| *.rbc | ||||
| /.config | ||||
|  |  | |||
|  | @ -0,0 +1,4 @@ | |||
| source 'https://rubygems.org' | ||||
| 
 | ||||
| # Specify your gem's dependencies in googleauth.gemspec | ||||
| gemspec | ||||
|  | @ -0,0 +1,14 @@ | |||
| # -*- ruby -*- | ||||
| require 'rspec/core/rake_task' | ||||
| require 'rubocop/rake_task' | ||||
| 
 | ||||
| desc 'Run Rubocop to check for style violations' | ||||
| RuboCop::RakeTask.new | ||||
| 
 | ||||
| desc 'Run rake task' | ||||
| RSpec::Core::RakeTask.new(:spec) | ||||
| 
 | ||||
| desc 'Does rubocop lint and runs the specs' | ||||
| task all: [:rubocop, :spec] | ||||
| 
 | ||||
| task default: :all | ||||
|  | @ -0,0 +1,38 @@ | |||
| # -*- ruby -*- | ||||
| # encoding: utf-8 | ||||
| $LOAD_PATH.push File.expand_path('../lib', __FILE__) | ||||
| require 'googleauth/version' | ||||
| 
 | ||||
| Gem::Specification.new do |s| | ||||
|   s.name          = 'googleauth' | ||||
|   s.version       = Google::Auth::VERSION | ||||
|   s.authors       = ['Tim Emiola'] | ||||
|   s.email         = 'temiola@google.com' | ||||
|   s.homepage      = 'https://github.com/google/google-auth-library-ruby' | ||||
|   s.summary       = 'Google Auth Library for Ruby' | ||||
|   s.description   = <<-eos | ||||
|    Allows simple authorization for accessing Google APIs. | ||||
|    Provide support Application Default Credentials, as described at | ||||
|    https://developers.google.com/accounts/docs/application-default-credentials | ||||
|   eos | ||||
| 
 | ||||
|   s.files         = `git ls-files`.split("\n") | ||||
|   s.test_files    = `git ls-files -- spec/*`.split("\n") | ||||
|   s.executables   = `git ls-files -- bin/*.rb`.split("\n").map do |f| | ||||
|     File.basename(f) | ||||
|   end | ||||
|   s.require_paths = ['lib'] | ||||
|   s.platform      = Gem::Platform::RUBY | ||||
| 
 | ||||
|   s.add_dependency 'faraday', '~> 0.9' | ||||
|   s.add_dependency 'logging', '~> 1.8' | ||||
|   s.add_dependency 'jwt', '~> 1.2.1' | ||||
|   s.add_dependency 'memoist', '~> 0.11.0' | ||||
|   s.add_dependency 'multi_json', '1.10.1' | ||||
|   s.add_dependency 'signet', '~> 0.6.0' | ||||
| 
 | ||||
|   s.add_development_dependency 'bundler', '~> 1.7' | ||||
|   s.add_development_dependency 'rake', '~> 10.0' | ||||
|   s.add_development_dependency 'rubocop', '~> 0.28.0' | ||||
|   s.add_development_dependency 'rspec', '~> 3.0' | ||||
| end | ||||
|  | @ -0,0 +1,84 @@ | |||
| # Copyright 2015, Google Inc. | ||||
| # All rights reserved. | ||||
| # | ||||
| # Redistribution and use in source and binary forms, with or without | ||||
| # modification, are permitted provided that the following conditions are | ||||
| # met: | ||||
| # | ||||
| #     * Redistributions of source code must retain the above copyright | ||||
| # notice, this list of conditions and the following disclaimer. | ||||
| #     * Redistributions in binary form must reproduce the above | ||||
| # copyright notice, this list of conditions and the following disclaimer | ||||
| # in the documentation and/or other materials provided with the | ||||
| # distribution. | ||||
| #     * Neither the name of Google Inc. nor the names of its | ||||
| # contributors may be used to endorse or promote products derived from | ||||
| # this software without specific prior written permission. | ||||
| # | ||||
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| require 'faraday' | ||||
| require 'googleauth/signet' | ||||
| require 'memoist' | ||||
| 
 | ||||
| module Google | ||||
|   # Module Auth provides classes that provide Google-specific authorization | ||||
|   # used to access Google APIs. | ||||
|   module Auth | ||||
|     # Extends Signet::OAuth2::Client so that the auth token is obtained from | ||||
|     # the GCE metadata server. | ||||
|     class GCECredentials < Signet::OAuth2::Client | ||||
|       # The IP Address is used in the URIs to speed up failures on non-GCE | ||||
|       # systems. | ||||
|       COMPUTE_AUTH_TOKEN_URI = 'http://169.254.169.254/computeMetadata/v1/'\ | ||||
|                                'instance/service-accounts/default/token' | ||||
|       COMPUTE_CHECK_URI = 'http://169.254.169.254' | ||||
| 
 | ||||
|       class << self | ||||
|         extend Memoist | ||||
|         # Detect if this appear to be a GCE instance, by checking if metadata | ||||
|         # is available | ||||
|         def on_gce?(options = {}) | ||||
|           c = options[:connection] || Faraday.default_connection | ||||
|           resp = c.get(COMPUTE_CHECK_URI) do |req| | ||||
|             # Comment from: oauth2client/client.py | ||||
|             # | ||||
|             # Note: the explicit `timeout` below is a workaround. The underlying | ||||
|             # issue is that resolving an unknown host on some networks will take | ||||
|             # 20-30 seconds; making this timeout short fixes the issue, but | ||||
|             # could lead to false negatives in the event that we are on GCE, but | ||||
|             # the metadata resolution was particularly slow. The latter case is | ||||
|             # "unlikely". | ||||
|             req.options.timeout = 0.1 | ||||
|           end | ||||
|           return false unless resp.status == 200 | ||||
|           return false unless resp.headers.key?('Metadata-Flavor') | ||||
|           return resp.headers['Metadata-Flavor'] == 'Google' | ||||
|         rescue [Faraday::TimeoutError, Faraday::ConnectionFailed] | ||||
|           return false | ||||
|         end | ||||
|         memoize :on_gce? | ||||
|       end | ||||
| 
 | ||||
|       # Overrides the super class method to change how access tokens are | ||||
|       # fetched. | ||||
|       def fetch_access_token(options = {}) | ||||
|         c = options[:connection] || Faraday.default_connection | ||||
|         c.headers = { 'Metadata-Flavor' => 'Google' } | ||||
|         resp = c.get(COMPUTE_AUTH_TOKEN_URI) | ||||
|         Signet::OAuth2.parse_credentials(resp.body, | ||||
|                                          resp.headers['content-type']) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,71 @@ | |||
| # Copyright 2015, Google Inc. | ||||
| # All rights reserved. | ||||
| # | ||||
| # Redistribution and use in source and binary forms, with or without | ||||
| # modification, are permitted provided that the following conditions are | ||||
| # met: | ||||
| # | ||||
| #     * Redistributions of source code must retain the above copyright | ||||
| # notice, this list of conditions and the following disclaimer. | ||||
| #     * Redistributions in binary form must reproduce the above | ||||
| # copyright notice, this list of conditions and the following disclaimer | ||||
| # in the documentation and/or other materials provided with the | ||||
| # distribution. | ||||
| #     * Neither the name of Google Inc. nor the names of its | ||||
| # contributors may be used to endorse or promote products derived from | ||||
| # this software without specific prior written permission. | ||||
| # | ||||
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| require 'googleauth/signet' | ||||
| require 'multi_json' | ||||
| require 'openssl' | ||||
| 
 | ||||
| # Reads the private key and client email fields from service account JSON key. | ||||
| def read_json_key(json_key_io) | ||||
|   json_key = MultiJson.load(json_key_io.read) | ||||
|   fail 'missing client_email' unless json_key.key?('client_email') | ||||
|   fail 'missing private_key' unless json_key.key?('private_key') | ||||
|   [json_key['private_key'], json_key['client_email']] | ||||
| end | ||||
| 
 | ||||
| module Google | ||||
|   # Module Auth provides classes that provide Google-specific authorization | ||||
|   # used to access Google APIs. | ||||
|   module Auth | ||||
|     # Authenticates requests using Google's Service Account credentials. | ||||
|     # | ||||
|     # This class provides a simpler surface to the behavior in | ||||
|     # Signet::OAuth2::Client.  It allows authorizing requests directly using | ||||
|     # credentials from a json key file downloaded from the developer console | ||||
|     # (via 'Generate new Json Key'). | ||||
|     # | ||||
|     # cf [Application Default Credentials](http://goo.gl/mkAHpZ) | ||||
|     class ServiceAccountCredentials < Signet::OAuth2::Client | ||||
|       TOKEN_CRED_URI = 'https://www.googleapis.com/oauth2/v3/token' | ||||
| 
 | ||||
|       # Initializes a ServiceAccountCredentials. | ||||
|       # | ||||
|       # @param scope [string|array] the scope(s) to access | ||||
|       # @param json_key_io [IO] an IO from which the JSON key can be read | ||||
|       def initialize(scope, json_key_io) | ||||
|         private_key, client_email = read_json_key(json_key_io) | ||||
|         super(token_credential_uri: TOKEN_CRED_URI, | ||||
|               audience: TOKEN_CRED_URI,  # TODO: confirm this | ||||
|               scope: scope, | ||||
|               issuer: client_email, | ||||
|               signing_key: OpenSSL::PKey::RSA.new(private_key)) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,63 @@ | |||
| # Copyright 2015, Google Inc. | ||||
| # All rights reserved. | ||||
| # | ||||
| # Redistribution and use in source and binary forms, with or without | ||||
| # modification, are permitted provided that the following conditions are | ||||
| # met: | ||||
| # | ||||
| #     * Redistributions of source code must retain the above copyright | ||||
| # notice, this list of conditions and the following disclaimer. | ||||
| #     * Redistributions in binary form must reproduce the above | ||||
| # copyright notice, this list of conditions and the following disclaimer | ||||
| # in the documentation and/or other materials provided with the | ||||
| # distribution. | ||||
| #     * Neither the name of Google Inc. nor the names of its | ||||
| # contributors may be used to endorse or promote products derived from | ||||
| # this software without specific prior written permission. | ||||
| # | ||||
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| require 'signet/oauth_2/client' | ||||
| 
 | ||||
| module Signet | ||||
|   # OAuth2 supports OAuth2 authentication. | ||||
|   module OAuth2 | ||||
|     AUTH_METADATA_KEY = :Authorization | ||||
|     # Signet::OAuth2::Client creates an OAuth2 client | ||||
|     # | ||||
|     # This reopens Client to add #apply and #apply! methods which update a | ||||
|     # hash with the fetched authentication token. | ||||
|     class Client | ||||
|       # Updates a_hash updated with the authentication token | ||||
|       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? | ||||
|         a_hash[AUTH_METADATA_KEY] = "Bearer #{access_token}" | ||||
|       end | ||||
| 
 | ||||
|       # Returns a clone of a_hash updated with the authentication token | ||||
|       def apply(a_hash, opts = {}) | ||||
|         a_copy = a_hash.clone | ||||
|         apply!(a_copy, opts) | ||||
|         a_copy | ||||
|       end | ||||
| 
 | ||||
|       # Returns a reference to the #apply method, suitable for passing as | ||||
|       # a closure | ||||
|       def updater_proc | ||||
|         lambda(&method(:apply)) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,36 @@ | |||
| # Copyright 2014, Google Inc. | ||||
| # All rights reserved. | ||||
| # | ||||
| # Redistribution and use in source and binary forms, with or without | ||||
| # modification, are permitted provided that the following conditions are | ||||
| # met: | ||||
| # | ||||
| #     * Redistributions of source code must retain the above copyright | ||||
| # notice, this list of conditions and the following disclaimer. | ||||
| #     * Redistributions in binary form must reproduce the above | ||||
| # copyright notice, this list of conditions and the following disclaimer | ||||
| # in the documentation and/or other materials provided with the | ||||
| # distribution. | ||||
| #     * Neither the name of Google Inc. nor the names of its | ||||
| # contributors may be used to endorse or promote products derived from | ||||
| # this software without specific prior written permission. | ||||
| # | ||||
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| module Google | ||||
|   # Module Auth provides classes that provide Google-specific authorization | ||||
|   # used to access Google APIs. | ||||
|   module Auth | ||||
|     VERSION = '0.1.0' | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,169 @@ | |||
| # Copyright 2015, Google Inc. | ||||
| # All rights reserved. | ||||
| # | ||||
| # Redistribution and use in source and binary forms, with or without | ||||
| # modification, are permitted provided that the following conditions are | ||||
| # met: | ||||
| # | ||||
| #     * Redistributions of source code must retain the above copyright | ||||
| # notice, this list of conditions and the following disclaimer. | ||||
| #     * Redistributions in binary form must reproduce the above | ||||
| # copyright notice, this list of conditions and the following disclaimer | ||||
| # in the documentation and/or other materials provided with the | ||||
| # distribution. | ||||
| #     * Neither the name of Google Inc. nor the names of its | ||||
| # contributors may be used to endorse or promote products derived from | ||||
| # this software without specific prior written permission. | ||||
| # | ||||
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) | ||||
| $LOAD_PATH.unshift(spec_dir) | ||||
| $LOAD_PATH.uniq! | ||||
| 
 | ||||
| require 'faraday' | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| def build_json_response(payload) | ||||
|   [200, | ||||
|    { 'Content-Type' => 'application/json; charset=utf-8' }, | ||||
|    MultiJson.dump(payload)] | ||||
| end | ||||
| 
 | ||||
| def build_access_token_json(token) | ||||
|   build_json_response('access_token' => token, | ||||
|                       'token_type' => 'Bearer', | ||||
|                       'expires_in' => 3600) | ||||
| end | ||||
| 
 | ||||
| WANTED_AUTH_KEY = :Authorization | ||||
| 
 | ||||
| shared_examples 'apply/apply! are OK' do | ||||
|   # tests that use these examples need to define | ||||
|   # | ||||
|   # @client which should be an auth client | ||||
|   # | ||||
|   # @make_auth_stubs, which should stub out the expected http behaviour of the | ||||
|   # auth client | ||||
|   describe '#fetch_access_token' do | ||||
|     it 'should set access_token to the fetched value' do | ||||
|       token = '1/abcdef1234567890' | ||||
|       stubs = make_auth_stubs access_token: token | ||||
|       c = Faraday.new do |b| | ||||
|         b.adapter(:test, stubs) | ||||
|       end | ||||
| 
 | ||||
|       @client.fetch_access_token!(connection: c) | ||||
|       expect(@client.access_token).to eq(token) | ||||
|       stubs.verify_stubbed_calls | ||||
|     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 | ||||
| 
 | ||||
|       md = { foo: 'bar' } | ||||
|       @client.apply!(md, connection: c) | ||||
|       want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" } | ||||
|       expect(md).to eq(want) | ||||
|       stubs.verify_stubbed_calls | ||||
|     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 | ||||
| 
 | ||||
|       md = { foo: 'bar' } | ||||
|       the_proc = @client.updater_proc | ||||
|       got = the_proc.call(md, connection: c) | ||||
|       want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" } | ||||
|       expect(got).to eq(want) | ||||
|       stubs.verify_stubbed_calls | ||||
|     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 | ||||
| 
 | ||||
|       md = { foo: 'bar' } | ||||
|       @client.apply(md, connection: c) | ||||
|       want = { foo: 'bar' } | ||||
|       expect(md).to eq(want) | ||||
|       stubs.verify_stubbed_calls | ||||
|     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 | ||||
| 
 | ||||
|       md = { foo: 'bar' } | ||||
|       got = @client.apply(md, connection: c) | ||||
|       want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" } | ||||
|       expect(got).to eq(want) | ||||
|       stubs.verify_stubbed_calls | ||||
|     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 | ||||
| 
 | ||||
|       n = 5 # arbitrary | ||||
|       n.times do |_t| | ||||
|         md = { foo: 'bar' } | ||||
|         got = @client.apply(md, connection: c) | ||||
|         want = { :foo => 'bar', WANTED_AUTH_KEY => "Bearer #{token}" } | ||||
|         expect(got).to eq(want) | ||||
|       end | ||||
|       stubs.verify_stubbed_calls | ||||
|     end | ||||
| 
 | ||||
|     it 'should fetch a new token if the current one is expired' do | ||||
|       token_1 = '1/abcdef1234567890' | ||||
|       token_2 = '2/abcdef1234567890' | ||||
| 
 | ||||
|       [token_1, token_2].each do |t| | ||||
|         stubs = make_auth_stubs access_token: t | ||||
|         c = Faraday.new do |b| | ||||
|           b.adapter(:test, stubs) | ||||
|         end | ||||
|         md = { foo: 'bar' } | ||||
|         got = @client.apply(md, connection: c) | ||||
|         want = { :foo => 'bar', WANTED_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 | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,108 @@ | |||
| # Copyright 2015, Google Inc. | ||||
| # All rights reserved. | ||||
| # | ||||
| # Redistribution and use in source and binary forms, with or without | ||||
| # modification, are permitted provided that the following conditions are | ||||
| # met: | ||||
| # | ||||
| #     * Redistributions of source code must retain the above copyright | ||||
| # notice, this list of conditions and the following disclaimer. | ||||
| #     * Redistributions in binary form must reproduce the above | ||||
| # copyright notice, this list of conditions and the following disclaimer | ||||
| # in the documentation and/or other materials provided with the | ||||
| # distribution. | ||||
| #     * Neither the name of Google Inc. nor the names of its | ||||
| # contributors may be used to endorse or promote products derived from | ||||
| # this software without specific prior written permission. | ||||
| # | ||||
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) | ||||
| $LOAD_PATH.unshift(spec_dir) | ||||
| $LOAD_PATH.uniq! | ||||
| 
 | ||||
| require 'apply_auth_examples' | ||||
| require 'faraday' | ||||
| require 'googleauth/compute_engine' | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| describe Google::Auth::GCECredentials do | ||||
|   MD_URI = '/computeMetadata/v1/instance/service-accounts/default/token' | ||||
|   GCECredentials = Google::Auth::GCECredentials | ||||
| 
 | ||||
|   before(:example) do | ||||
|     @client = GCECredentials.new | ||||
|   end | ||||
| 
 | ||||
|   def make_auth_stubs(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 | ||||
|   end | ||||
| 
 | ||||
|   it_behaves_like 'apply/apply! are OK' | ||||
| 
 | ||||
|   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 | ||||
|     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 | ||||
|     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 | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,71 @@ | |||
| # Copyright 2015, Google Inc. | ||||
| # All rights reserved. | ||||
| # | ||||
| # Redistribution and use in source and binary forms, with or without | ||||
| # modification, are permitted provided that the following conditions are | ||||
| # met: | ||||
| # | ||||
| #     * Redistributions of source code must retain the above copyright | ||||
| # notice, this list of conditions and the following disclaimer. | ||||
| #     * Redistributions in binary form must reproduce the above | ||||
| # copyright notice, this list of conditions and the following disclaimer | ||||
| # in the documentation and/or other materials provided with the | ||||
| # distribution. | ||||
| #     * Neither the name of Google Inc. nor the names of its | ||||
| # contributors may be used to endorse or promote products derived from | ||||
| # this software without specific prior written permission. | ||||
| # | ||||
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) | ||||
| $LOAD_PATH.unshift(spec_dir) | ||||
| $LOAD_PATH.uniq! | ||||
| 
 | ||||
| require 'apply_auth_examples' | ||||
| require 'googleauth/service_account' | ||||
| require 'jwt' | ||||
| require 'multi_json' | ||||
| require 'openssl' | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| describe Google::Auth::ServiceAccountCredentials do | ||||
|   before(:example) do | ||||
|     @key = OpenSSL::PKey::RSA.new(2048) | ||||
|     cred_json = { | ||||
|       private_key_id: 'a_private_key_id', | ||||
|       private_key: @key.to_pem, | ||||
|       client_email: 'app@developer.gserviceaccount.com', | ||||
|       client_id: 'app.apps.googleusercontent.com', | ||||
|       type: 'service_account' | ||||
|     } | ||||
|     cred_json_text = MultiJson.dump(cred_json) | ||||
|     @client = Google::Auth::ServiceAccountCredentials.new( | ||||
|         'https://www.googleapis.com/auth/userinfo.profile', | ||||
|         StringIO.new(cred_json_text)) | ||||
|   end | ||||
| 
 | ||||
|   def make_auth_stubs(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 | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   it_behaves_like 'apply/apply! are OK' | ||||
| end | ||||
|  | @ -0,0 +1,66 @@ | |||
| # Copyright 2015, Google Inc. | ||||
| # All rights reserved. | ||||
| # | ||||
| # Redistribution and use in source and binary forms, with or without | ||||
| # modification, are permitted provided that the following conditions are | ||||
| # met: | ||||
| # | ||||
| #     * Redistributions of source code must retain the above copyright | ||||
| # notice, this list of conditions and the following disclaimer. | ||||
| #     * Redistributions in binary form must reproduce the above | ||||
| # copyright notice, this list of conditions and the following disclaimer | ||||
| # in the documentation and/or other materials provided with the | ||||
| # distribution. | ||||
| #     * Neither the name of Google Inc. nor the names of its | ||||
| # contributors may be used to endorse or promote products derived from | ||||
| # this software without specific prior written permission. | ||||
| # | ||||
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) | ||||
| $LOAD_PATH.unshift(spec_dir) | ||||
| $LOAD_PATH.uniq! | ||||
| 
 | ||||
| require 'apply_auth_examples' | ||||
| require 'googleauth/signet' | ||||
| require 'jwt' | ||||
| require 'openssl' | ||||
| require 'spec_helper' | ||||
| 
 | ||||
| describe Signet::OAuth2::Client do | ||||
|   before(:example) do | ||||
|     @key = OpenSSL::PKey::RSA.new(2048) | ||||
|     @client = Signet::OAuth2::Client.new( | ||||
|         token_credential_uri: 'https://accounts.google.com/o/oauth2/token', | ||||
|         scope: 'https://www.googleapis.com/auth/userinfo.profile', | ||||
|         issuer: 'app@example.com', | ||||
|         audience: 'https://accounts.google.com/o/oauth2/token', | ||||
|         signing_key: @key | ||||
|       ) | ||||
|   end | ||||
| 
 | ||||
|   def make_auth_stubs(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 | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   it_behaves_like 'apply/apply! are OK' | ||||
| end | ||||
|  | @ -0,0 +1,51 @@ | |||
| # Copyright 2015, Google Inc. | ||||
| # All rights reserved. | ||||
| # | ||||
| # Redistribution and use in source and binary forms, with or without | ||||
| # modification, are permitted provided that the following conditions are | ||||
| # met: | ||||
| # | ||||
| #     * Redistributions of source code must retain the above copyright | ||||
| # notice, this list of conditions and the following disclaimer. | ||||
| #     * Redistributions in binary form must reproduce the above | ||||
| # copyright notice, this list of conditions and the following disclaimer | ||||
| # in the documentation and/or other materials provided with the | ||||
| # distribution. | ||||
| #     * Neither the name of Google Inc. nor the names of its | ||||
| # contributors may be used to endorse or promote products derived from | ||||
| # this software without specific prior written permission. | ||||
| # | ||||
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| spec_dir = File.expand_path(File.dirname(__FILE__)) | ||||
| root_dir = File.expand_path(File.join(spec_dir, '..')) | ||||
| lib_dir = File.expand_path(File.join(root_dir, 'lib')) | ||||
| 
 | ||||
| $LOAD_PATH.unshift(spec_dir) | ||||
| $LOAD_PATH.unshift(lib_dir) | ||||
| $LOAD_PATH.uniq! | ||||
| 
 | ||||
| require 'faraday' | ||||
| require 'rspec' | ||||
| require 'logging' | ||||
| require 'rspec/logging_helper' | ||||
| 
 | ||||
| # Allow Faraday to support test stubs | ||||
| Faraday::Adapter.load_middleware(:test) | ||||
| 
 | ||||
| # Configure RSpec to capture log messages for each test. The output from the | ||||
| # logs will be stored in the @log_output variable. It is a StringIO instance. | ||||
| RSpec.configure do |config| | ||||
|   include RSpec::LoggingHelper | ||||
|   config.capture_log_messages | ||||
| end | ||||
		Loading…
	
		Reference in New Issue