diff --git a/googleauth.gemspec b/googleauth.gemspec index 35c9b70..813e419 100755 --- a/googleauth.gemspec +++ b/googleauth.gemspec @@ -35,6 +35,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'bundler', '~> 1.9' s.add_development_dependency 'simplecov', '~> 0.9' s.add_development_dependency 'coveralls', '~> 0.7' + s.add_development_dependency 'fakefs', '~> 0.6' s.add_development_dependency 'rake', '~> 10.0' s.add_development_dependency 'rubocop', '~> 0.30' s.add_development_dependency 'rspec', '~> 3.0' diff --git a/lib/googleauth.rb b/lib/googleauth.rb index 520bbf4..cacf94c 100644 --- a/lib/googleauth.rb +++ b/lib/googleauth.rb @@ -110,7 +110,8 @@ END # @param options [hash] allows override of the connection being used def get_application_default(scope = nil, options = {}) creds = DefaultCredentials.from_env(scope) || - DefaultCredentials.from_well_known_path(scope) + DefaultCredentials.from_well_known_path(scope) || + DefaultCredentials.from_system_default_path(scope) return creds unless creds.nil? fail NOT_FOUND_ERROR unless GCECredentials.on_gce?(options) GCECredentials.new diff --git a/lib/googleauth/credentials_loader.rb b/lib/googleauth/credentials_loader.rb index d3cbf0c..04d5fc0 100644 --- a/lib/googleauth/credentials_loader.rb +++ b/lib/googleauth/credentials_loader.rb @@ -47,11 +47,14 @@ module Google REFRESH_TOKEN_VAR = 'GOOGLE_REFRESH_TOKEN' ACCOUNT_TYPE_VAR = 'GOOGLE_ACCOUNT_TYPE' + CREDENTIALS_FILE_NAME = 'application_default_credentials.json' NOT_FOUND_ERROR = "Unable to read the credential file specified by #{ENV_VAR}" - WELL_KNOWN_PATH = 'gcloud/application_default_credentials.json' + WELL_KNOWN_PATH = "gcloud/#{CREDENTIALS_FILE_NAME}" WELL_KNOWN_ERROR = 'Unable to read the default credential file' + SYSTEM_DEFAULT_ERROR = 'Unable to read the system default credential file' + # determines if the current OS is windows def windows? RbConfig::CONFIG['host_os'] =~ /Windows|mswin/ @@ -100,6 +103,25 @@ module Google raise "#{WELL_KNOWN_ERROR}: #{e}" end + # Creates an instance from the system default path + # + # @param scope [string|array|nil] the scope(s) to access + def from_system_default_path(scope = nil) + if windows? + return nil unless ENV['ProgramData'] + prefix = File.join(ENV['ProgramData'], 'Google/Auth') + else + prefix = '/etc/google/auth/' + end + path = File.join(prefix, CREDENTIALS_FILE_NAME) + return nil unless File.exist?(path) + File.open(path) do |f| + return make_creds(json_key_io: f, scope: scope) + end + rescue StandardError => e + raise "#{SYSTEM_DEFAULT_ERROR}: #{e}" + end + private def service_account_env_vars? diff --git a/spec/googleauth/get_application_default_spec.rb b/spec/googleauth/get_application_default_spec.rb index 140bd9c..13fe861 100644 --- a/spec/googleauth/get_application_default_spec.rb +++ b/spec/googleauth/get_application_default_spec.rb @@ -32,6 +32,7 @@ $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'faraday' +require 'fakefs/safe' require 'googleauth' require 'spec_helper' @@ -141,6 +142,17 @@ describe '#get_application_default' do stubs.verify_stubbed_calls end + it 'succeeds with system default file' do + ENV.delete(@var_name) unless ENV[@var_name].nil? + FakeFS 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 + File.delete(key_path) + end + end + it 'succeeds if environment vars are valid' do ENV.delete(@var_name) unless ENV[@var_name].nil? # no env var ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] diff --git a/spec/googleauth/service_account_spec.rb b/spec/googleauth/service_account_spec.rb index 2f727f5..ceda286 100644 --- a/spec/googleauth/service_account_spec.rb +++ b/spec/googleauth/service_account_spec.rb @@ -32,6 +32,7 @@ $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'apply_auth_examples' +require 'fakefs/safe' require 'fileutils' require 'googleauth/service_account' require 'jwt' @@ -231,6 +232,30 @@ describe Google::Auth::ServiceAccountCredentials do end end end + + describe '#from_system_default_path' do + before(:example) do + @scope = 'https://www.googleapis.com/auth/userinfo.profile' + @path = File.join('/etc/google/auth/', CREDENTIALS_FILE_NAME) + @clz = ServiceAccountCredentials + end + + it 'is nil if no file exists' do + FakeFS do + expect(ServiceAccountCredentials.from_system_default_path(@scope)) + .to be_nil + end + end + + it 'successfully loads the file when it is present' do + FakeFS do + FileUtils.mkdir_p(File.dirname(@path)) + File.write(@path, cred_json_text) + expect(@clz.from_system_default_path(@scope)).to_not be_nil + File.delete(@path) + end + end + end end describe Google::Auth::ServiceAccountJwtHeaderCredentials do diff --git a/spec/googleauth/user_refresh_spec.rb b/spec/googleauth/user_refresh_spec.rb index 02260e1..2ac4404 100644 --- a/spec/googleauth/user_refresh_spec.rb +++ b/spec/googleauth/user_refresh_spec.rb @@ -32,6 +32,7 @@ $LOAD_PATH.unshift(spec_dir) $LOAD_PATH.uniq! require 'apply_auth_examples' +require 'fakefs/safe' require 'fileutils' require 'googleauth/user_refresh' require 'jwt' @@ -189,4 +190,41 @@ describe Google::Auth::UserRefreshCredentials do end end end + + describe '#from_system_default_path' do + before(:example) do + @scope = 'https://www.googleapis.com/auth/userinfo.profile' + @path = File.join('/etc/google/auth/', CREDENTIALS_FILE_NAME) + @clz = UserRefreshCredentials + end + + it 'is nil if no file exists' do + FakeFS do + expect(UserRefreshCredentials.from_system_default_path(@scope)) + .to be_nil + end + end + + it 'fails if the file is invalid' do + needed = %w(client_id client_secret refresh_token) + needed.each do |missing| + FakeFS do + FileUtils.mkdir_p(File.dirname(@path)) + File.write(@path, cred_json_text(missing)) + expect { @clz.from_system_default_path(@scope) } + .to raise_error RuntimeError + File.delete(@path) + end + end + end + + it 'successfully loads the file when it is present' do + FakeFS do + FileUtils.mkdir_p(File.dirname(@path)) + File.write(@path, cred_json_text) + expect(@clz.from_system_default_path(@scope)).to_not be_nil + File.delete(@path) + end + end + end end