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 | *.gem | ||||||
| *.rbc | *.rbc | ||||||
| /.config | /.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