add google-style and refactor (#203)

This commit is contained in:
Graham Paye 2019-03-15 12:34:54 -07:00 committed by GitHub
parent 1c64927249
commit bf822910fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1315 additions and 1349 deletions

View File

@ -1,30 +1,11 @@
inherit_gem:
google-style: google-style.yml
AllCops: AllCops:
Exclude: Exclude:
- "spec/**/*" - "spec/**/*"
- "Rakefile" - "Rakefile"
Metrics/AbcSize:
Max: 25
Metrics/BlockLength:
Exclude:
- "googleauth.gemspec"
Metrics/CyclomaticComplexity:
Max: 8
Metrics/MethodLength:
Max: 20
Metrics/ModuleLength:
Max: 150
Metrics/ClassLength: Metrics/ClassLength:
Enabled: false Max: 110
Layout/IndentHeredoc: Metrics/ModuleLength:
Enabled: false Max: 105
Style/FormatString:
Enabled: false
Style/GuardClause:
Enabled: false
Style/PercentLiteralDelimiters: # Contradicting rule
Enabled: false
Style/StringLiterals:
EnforcedStyle: double_quotes
Style/SymbolArray: # Undefined syntax in Ruby 1.9.3
Enabled: false

View File

@ -8,12 +8,12 @@ group :development do
gem "coveralls", "~> 0.7" gem "coveralls", "~> 0.7"
gem "fakefs", "~> 0.6" gem "fakefs", "~> 0.6"
gem "fakeredis", "~> 0.5" gem "fakeredis", "~> 0.5"
gem "google-style", "~> 0.2"
gem "logging", "~> 2.0" gem "logging", "~> 2.0"
gem "rack-test", "~> 0.6" gem "rack-test", "~> 0.6"
gem "rake", "~> 10.0" gem "rake", "~> 10.0"
gem "redis", "~> 3.2" gem "redis", "~> 3.2"
gem "rspec", "~> 3.0" gem "rspec", "~> 3.0"
gem "rubocop", ">= 0.41", "< 0.50"
gem "simplecov", "~> 0.9" gem "simplecov", "~> 0.9"
gem "sinatra" gem "sinatra"
gem "webmock", "~> 1.21" gem "webmock", "~> 1.21"

View File

@ -1,20 +1,20 @@
# -*- ruby -*- # -*- ruby -*-
require 'rspec/core/rake_task' require "rspec/core/rake_task"
require 'rubocop/rake_task' require "rubocop/rake_task"
require 'bundler/gem_tasks' require "bundler/gem_tasks"
desc 'Run Rubocop to check for style violations' desc "Run Rubocop to check for style violations"
RuboCop::RakeTask.new RuboCop::RakeTask.new
desc 'Run rake task' desc "Run rake task"
RSpec::Core::RakeTask.new(:spec) RSpec::Core::RakeTask.new :spec
desc 'Does rubocop lint and runs the specs' desc "Does rubocop lint and runs the specs"
task all: [:rubocop, :spec] task all: %i[rubocop spec]
task default: :all task default: :all
task :release, :tag do |t, args| task :release, :tag do |_t, args|
tag = args[:tag] tag = args[:tag]
raise "You must provide a tag to release." if tag.nil? raise "You must provide a tag to release." if tag.nil?
@ -28,9 +28,11 @@ task :release, :tag do |t, args|
api_token = ENV["RUBYGEMS_API_TOKEN"] api_token = ENV["RUBYGEMS_API_TOKEN"]
require "gems" require "gems"
::Gems.configure do |config| if api_token
config.key = api_token ::Gems.configure do |config|
end if api_token config.key = api_token
end
end
Bundler.with_clean_env do Bundler.with_clean_env do
sh "rm -rf pkg" sh "rm -rf pkg"
@ -41,9 +43,9 @@ task :release, :tag do |t, args|
path_to_be_pushed = "pkg/#{version}.gem" path_to_be_pushed = "pkg/#{version}.gem"
if File.file? path_to_be_pushed if File.file? path_to_be_pushed
begin begin
::Gems.push(File.new path_to_be_pushed) ::Gems.push File.new(path_to_be_pushed)
puts "Successfully built and pushed googleauth for version #{version}" puts "Successfully built and pushed googleauth for version #{version}"
rescue => e rescue StandardError => e
puts "Error while releasing googleauth version #{version}: #{e.message}" puts "Error while releasing googleauth version #{version}: #{e.message}"
end end
else else
@ -53,10 +55,10 @@ end
namespace :kokoro do namespace :kokoro do
task :load_env_vars do task :load_env_vars do
service_account = "#{ENV["KOKORO_GFILE_DIR"]}/service-account.json" service_account = "#{ENV['KOKORO_GFILE_DIR']}/service-account.json"
ENV["GOOGLE_APPLICATION_CREDENTIALS"] = service_account ENV["GOOGLE_APPLICATION_CREDENTIALS"] = service_account
filename = "#{ENV["KOKORO_GFILE_DIR"]}/env_vars.json" filename = "#{ENV['KOKORO_GFILE_DIR']}/env_vars.json"
env_vars = JSON.parse(File.read(filename)) env_vars = JSON.parse File.read(filename)
env_vars.each { |k, v| ENV[k] = v } env_vars.each { |k, v| ENV[k] = v }
end end
@ -64,8 +66,8 @@ namespace :kokoro do
version = "0.1.0" version = "0.1.0"
Bundler.with_clean_env do Bundler.with_clean_env do
version = `bundle exec gem list` version = `bundle exec gem list`
.split("\n").select { |line| line.include? "googleauth" } .split("\n").select { |line| line.include? "googleauth" }
.first.split("(").last.split(")").first || "0.1.0" .first.split("(").last.split(")").first || "0.1.0"
end end
Rake::Task["kokoro:load_env_vars"].invoke Rake::Task["kokoro:load_env_vars"].invoke
Rake::Task["release"].invoke "v/#{version}" Rake::Task["release"].invoke "v/#{version}"

View File

@ -1,7 +1,7 @@
# -*- ruby -*- # -*- ruby -*-
# encoding: utf-8 # encoding: utf-8
$LOAD_PATH.push File.expand_path("../lib", __FILE__) $LOAD_PATH.push File.expand_path("lib", __dir__)
require "googleauth/version" require "googleauth/version"
Gem::Specification.new do |gem| Gem::Specification.new do |gem|
@ -18,10 +18,10 @@ Gem::Specification.new do |gem|
https://developers.google.com/accounts/docs/application-default-credentials https://developers.google.com/accounts/docs/application-default-credentials
DESCRIPTION DESCRIPTION
gem.files = `git ls-files`.split("\n") gem.files = `git ls-files`.split "\n"
gem.test_files = `git ls-files -- spec/*`.split("\n") gem.test_files = `git ls-files -- spec/*`.split "\n"
gem.executables = `git ls-files -- bin/*.rb`.split("\n").map do |f| gem.executables = `git ls-files -- bin/*.rb`.split("\n").map do |f|
File.basename(f) File.basename f
end end
gem.require_paths = ["lib"] gem.require_paths = ["lib"]
gem.platform = Gem::Platform::RUBY gem.platform = Gem::Platform::RUBY

View File

@ -34,11 +34,13 @@ module Google
# Module Auth provides classes that provide Google-specific authorization # Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs. # used to access Google APIs.
module Auth module Auth
NOT_FOUND_ERROR = <<ERROR_MESSAGE.freeze NOT_FOUND_ERROR = <<~ERROR_MESSAGE.freeze
Could not load the default credentials. Browse to Could not load the default credentials. Browse to
https://developers.google.com/accounts/docs/application-default-credentials https://developers.google.com/accounts/docs/application-default-credentials
for more information for more information
ERROR_MESSAGE ERROR_MESSAGE
module_function
# Obtains the default credentials implementation to use in this # Obtains the default credentials implementation to use in this
# environment. # environment.
@ -63,19 +65,17 @@ ERROR_MESSAGE
# connection to use for token refresh requests. # connection to use for token refresh requests.
# * `:connection` The connection to use to determine whether GCE # * `:connection` The connection to use to determine whether GCE
# metadata credentials are available. # metadata credentials are available.
def get_application_default(scope = nil, options = {}) def get_application_default scope = nil, options = {}
creds = DefaultCredentials.from_env(scope, options) || creds = DefaultCredentials.from_env(scope, options) ||
DefaultCredentials.from_well_known_path(scope, options) || DefaultCredentials.from_well_known_path(scope, options) ||
DefaultCredentials.from_system_default_path(scope, options) DefaultCredentials.from_system_default_path(scope, options)
return creds unless creds.nil? return creds unless creds.nil?
unless GCECredentials.on_gce?(options) unless GCECredentials.on_gce? options
# Clear cache of the result of GCECredentials.on_gce? # Clear cache of the result of GCECredentials.on_gce?
GCECredentials.unmemoize_all GCECredentials.unmemoize_all
raise NOT_FOUND_ERROR raise NOT_FOUND_ERROR
end end
GCECredentials.new GCECredentials.new
end end
module_function :get_application_default
end end
end end

View File

@ -63,7 +63,7 @@ module Google
# @note Direction instantion is discouraged to avoid embedding IDs # @note Direction instantion is discouraged to avoid embedding IDs
# & secrets in source. See {#from_file} to load from # & secrets in source. See {#from_file} to load from
# `client_secrets.json` files. # `client_secrets.json` files.
def initialize(id, secret) def initialize id, secret
CredentialsLoader.warn_if_cloud_sdk_credentials id CredentialsLoader.warn_if_cloud_sdk_credentials id
raise "Client id can not be nil" if id.nil? raise "Client id can not be nil" if id.nil?
raise "Client secret can not be nil" if secret.nil? raise "Client secret can not be nil" if secret.nil?
@ -77,12 +77,12 @@ module Google
# @param [String, File] file # @param [String, File] file
# Path of file to read from # Path of file to read from
# @return [Google::Auth::ClientID] # @return [Google::Auth::ClientID]
def self.from_file(file) def self.from_file file
raise "File can not be nil." if file.nil? raise "File can not be nil." if file.nil?
File.open(file.to_s) do |f| File.open file.to_s do |f|
json = f.read json = f.read
config = MultiJson.load json config = MultiJson.load json
from_hash(config) from_hash config
end end
end end
@ -93,11 +93,11 @@ module Google
# @param [hash] config # @param [hash] config
# Parsed contents of the JSON file # Parsed contents of the JSON file
# @return [Google::Auth::ClientID] # @return [Google::Auth::ClientID]
def self.from_hash(config) def self.from_hash config
raise "Hash can not be nil." if config.nil? raise "Hash can not be nil." if config.nil?
raw_detail = config[INSTALLED_APP] || config[WEB_APP] raw_detail = config[INSTALLED_APP] || config[WEB_APP]
raise MISSING_TOP_LEVEL_ELEMENT_ERROR if raw_detail.nil? raise MISSING_TOP_LEVEL_ELEMENT_ERROR if raw_detail.nil?
ClientId.new(raw_detail[CLIENT_ID], raw_detail[CLIENT_SECRET]) ClientId.new raw_detail[CLIENT_ID], raw_detail[CLIENT_SECRET]
end end
end end
end end

View File

@ -35,16 +35,16 @@ module Google
# Module Auth provides classes that provide Google-specific authorization # Module Auth provides classes that provide Google-specific authorization
# used to access Google APIs. # used to access Google APIs.
module Auth module Auth
NO_METADATA_SERVER_ERROR = <<ERROR.freeze NO_METADATA_SERVER_ERROR = <<~ERROR.freeze
Error code 404 trying to get security access token Error code 404 trying to get security access token
from Compute Engine metadata for the default service account. This from Compute Engine metadata for the default service account. This
may be because the virtual machine instance does not have permission may be because the virtual machine instance does not have permission
scopes specified. scopes specified.
ERROR ERROR
UNEXPECTED_ERROR_SUFFIX = <<ERROR.freeze UNEXPECTED_ERROR_SUFFIX = <<~ERROR.freeze
trying to get security access token from Compute Engine metadata for trying to get security access token from Compute Engine metadata for
the default service account the default service account
ERROR ERROR
# Extends Signet::OAuth2::Client so that the auth token is obtained from # Extends Signet::OAuth2::Client so that the auth token is obtained from
# the GCE metadata server. # the GCE metadata server.
@ -60,9 +60,9 @@ ERROR
# Detect if this appear to be a GCE instance, by checking if metadata # Detect if this appear to be a GCE instance, by checking if metadata
# is available # is available
def on_gce?(options = {}) def on_gce? options = {}
c = options[:connection] || Faraday.default_connection c = options[:connection] || Faraday.default_connection
resp = c.get(COMPUTE_CHECK_URI) do |req| resp = c.get COMPUTE_CHECK_URI do |req|
# Comment from: oauth2client/client.py # Comment from: oauth2client/client.py
# #
# Note: the explicit `timeout` below is a workaround. The underlying # Note: the explicit `timeout` below is a workaround. The underlying
@ -74,10 +74,10 @@ ERROR
req.options.timeout = 0.1 req.options.timeout = 0.1
end end
return false unless resp.status == 200 return false unless resp.status == 200
return false unless resp.headers.key?("Metadata-Flavor") return false unless resp.headers.key? "Metadata-Flavor"
return resp.headers["Metadata-Flavor"] == "Google" resp.headers["Metadata-Flavor"] == "Google"
rescue Faraday::TimeoutError, Faraday::ConnectionFailed rescue Faraday::TimeoutError, Faraday::ConnectionFailed
return false false
end end
memoize :on_gce? memoize :on_gce?
@ -85,21 +85,21 @@ ERROR
# Overrides the super class method to change how access tokens are # Overrides the super class method to change how access tokens are
# fetched. # fetched.
def fetch_access_token(options = {}) def fetch_access_token options = {}
c = options[:connection] || Faraday.default_connection c = options[:connection] || Faraday.default_connection
retry_with_error do retry_with_error do
headers = { "Metadata-Flavor" => "Google" } headers = { "Metadata-Flavor" => "Google" }
resp = c.get(COMPUTE_AUTH_TOKEN_URI, nil, headers) resp = c.get COMPUTE_AUTH_TOKEN_URI, nil, headers
case resp.status case resp.status
when 200 when 200
Signet::OAuth2.parse_credentials(resp.body, Signet::OAuth2.parse_credentials(resp.body,
resp.headers["content-type"]) resp.headers["content-type"])
when 404 when 404
raise(Signet::AuthorizationError, NO_METADATA_SERVER_ERROR) raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
else else
msg = "Unexpected error code #{resp.status}" \ msg = "Unexpected error code #{resp.status}" \
"#{UNEXPECTED_ERROR_SUFFIX}" "#{UNEXPECTED_ERROR_SUFFIX}"
raise(Signet::AuthorizationError, msg) raise Signet::AuthorizationError, msg
end end
end end
end end

View File

@ -27,8 +27,6 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, MethodLength
require "forwardable" require "forwardable"
require "json" require "json"
require "signet/oauth_2/client" require "signet/oauth_2/client"
@ -56,7 +54,8 @@ module Google
:token_credential_uri, :audience, :token_credential_uri, :audience,
:scope, :issuer, :signing_key, :updater_proc :scope, :issuer, :signing_key, :updater_proc
def initialize(keyfile, options = {}) # rubocop:disable Metrics/AbcSize
def initialize keyfile, options = {}
scope = options[:scope] scope = options[:scope]
verify_keyfile_provided! keyfile verify_keyfile_provided! keyfile
@project_id = options["project_id"] || options["project"] @project_id = options["project_id"] || options["project"]
@ -79,12 +78,13 @@ module Google
@project_id ||= CredentialsLoader.load_gcloud_project_id @project_id ||= CredentialsLoader.load_gcloud_project_id
@client.fetch_access_token! @client.fetch_access_token!
end end
# rubocop:enable Metrics/AbcSize
# Returns the default credentials checking, in this order, the path env # Returns the default credentials checking, in this order, the path env
# evironment variables, json environment variables, default paths. If the # evironment variables, json environment variables, default paths. If the
# previously stated locations do not contain keyfile information, # previously stated locations do not contain keyfile information,
# this method defaults to use the application default. # this method defaults to use the application default.
def self.default(options = {}) def self.default options = {}
# First try to find keyfile file from environment variables. # First try to find keyfile file from environment variables.
client = from_path_vars options client = from_path_vars options
@ -99,7 +99,7 @@ module Google
client client
end end
def self.from_path_vars(options) def self.from_path_vars options
self::PATH_ENV_VARS self::PATH_ENV_VARS
.map { |v| ENV[v] } .map { |v| ENV[v] }
.compact .compact
@ -110,23 +110,21 @@ module Google
nil nil
end end
def self.from_json_vars(options) def self.from_json_vars options
json = lambda do |v| json = lambda do |v|
unless ENV[v].nil? unless ENV[v].nil?
begin begin
JSON.parse ENV[v] JSON.parse ENV[v]
rescue rescue StandardError
nil nil
end end
end end
end end
self::JSON_ENV_VARS.map(&json).compact.each do |hash| self::JSON_ENV_VARS.map(&json).compact.each { |hash| return new hash, options }
return new hash, options
end
nil nil
end end
def self.from_default_paths(options) def self.from_default_paths options
self::DEFAULT_PATHS self::DEFAULT_PATHS
.select { |p| ::File.file? p } .select { |p| ::File.file? p }
.each do |file| .each do |file|
@ -135,7 +133,7 @@ module Google
nil nil
end end
def self.from_application_default(options) def self.from_application_default options
scope = options[:scope] || self::SCOPE scope = options[:scope] || self::SCOPE
client = Google::Auth.get_application_default scope client = Google::Auth.get_application_default scope
new client, options new client, options
@ -148,30 +146,30 @@ module Google
protected protected
# Verify that the keyfile argument is provided. # Verify that the keyfile argument is provided.
def verify_keyfile_provided!(keyfile) def verify_keyfile_provided! keyfile
return unless keyfile.nil? return unless keyfile.nil?
raise "The keyfile passed to Google::Auth::Credentials.new was nil." raise "The keyfile passed to Google::Auth::Credentials.new was nil."
end end
# Verify that the keyfile argument is a file. # Verify that the keyfile argument is a file.
def verify_keyfile_exists!(keyfile) def verify_keyfile_exists! keyfile
exists = ::File.file? keyfile exists = ::File.file? keyfile
raise "The keyfile '#{keyfile}' is not a valid file." unless exists raise "The keyfile '#{keyfile}' is not a valid file." unless exists
end end
# Initializes the Signet client. # Initializes the Signet client.
def init_client(keyfile, connection_options = {}) def init_client keyfile, connection_options = {}
client_opts = client_options keyfile client_opts = client_options keyfile
Signet::OAuth2::Client.new(client_opts) Signet::OAuth2::Client.new(client_opts)
.configure_connection(connection_options) .configure_connection(connection_options)
end end
# returns a new Hash with string keys instead of symbol keys. # returns a new Hash with string keys instead of symbol keys.
def stringify_hash_keys(hash) def stringify_hash_keys hash
Hash[hash.map { |k, v| [k.to_s, v] }] Hash[hash.map { |k, v| [k.to_s, v] }]
end end
def client_options(options) def client_options options
# Keyfile options have higher priority over constructor defaults # Keyfile options have higher priority over constructor defaults
options["token_credential_uri"] ||= self.class::TOKEN_CREDENTIAL_URI options["token_credential_uri"] ||= self.class::TOKEN_CREDENTIAL_URI
options["audience"] ||= self.class::AUDIENCE options["audience"] ||= self.class::AUDIENCE
@ -179,10 +177,10 @@ module Google
# client options for initializing signet client # client options for initializing signet client
{ token_credential_uri: options["token_credential_uri"], { token_credential_uri: options["token_credential_uri"],
audience: options["audience"], audience: options["audience"],
scope: Array(options["scope"]), scope: Array(options["scope"]),
issuer: options["client_email"], issuer: options["client_email"],
signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) } signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
end end
end end
end end

View File

@ -75,11 +75,9 @@ module Google
# #
# By default, it calls #new on the current class, but this behaviour can # By default, it calls #new on the current class, but this behaviour can
# be modified, allowing different instances to be created. # be modified, allowing different instances to be created.
def make_creds(*args) def make_creds *args
creds = new(*args) creds = new(*args)
if creds.respond_to?(:configure_connection) && args.size == 1 creds = creds.configure_connection args[0] if creds.respond_to?(:configure_connection) && args.size == 1
creds = creds.configure_connection(args[0])
end
creds creds
end end
@ -95,16 +93,16 @@ module Google
# The following keys are recognized: # The following keys are recognized:
# * `:default_connection` The connection object to use. # * `:default_connection` The connection object to use.
# * `:connection_builder` A `Proc` that returns a connection. # * `:connection_builder` A `Proc` that returns a connection.
def from_env(scope = nil, options = {}) def from_env scope = nil, options = {}
options = interpret_options scope, options options = interpret_options scope, options
if ENV.key?(ENV_VAR) if ENV.key? ENV_VAR
path = ENV[ENV_VAR] path = ENV[ENV_VAR]
raise "file #{path} does not exist" unless File.exist?(path) raise "file #{path} does not exist" unless File.exist? path
File.open(path) do |f| File.open path do |f|
return make_creds(options.merge(json_key_io: f)) return make_creds options.merge(json_key_io: f)
end end
elsif service_account_env_vars? || authorized_user_env_vars? elsif service_account_env_vars? || authorized_user_env_vars?
return make_creds(options) return make_creds options
end end
rescue StandardError => e rescue StandardError => e
raise "#{NOT_FOUND_ERROR}: #{e}" raise "#{NOT_FOUND_ERROR}: #{e}"
@ -121,16 +119,16 @@ module Google
# The following keys are recognized: # The following keys are recognized:
# * `:default_connection` The connection object to use. # * `:default_connection` The connection object to use.
# * `:connection_builder` A `Proc` that returns a connection. # * `:connection_builder` A `Proc` that returns a connection.
def from_well_known_path(scope = nil, options = {}) def from_well_known_path scope = nil, options = {}
options = interpret_options scope, options options = interpret_options scope, options
home_var = OS.windows? ? "APPDATA" : "HOME" home_var = OS.windows? ? "APPDATA" : "HOME"
base = WELL_KNOWN_PATH base = WELL_KNOWN_PATH
root = ENV[home_var].nil? ? "" : ENV[home_var] root = ENV[home_var].nil? ? "" : ENV[home_var]
base = File.join(".config", base) unless OS.windows? base = File.join ".config", base unless OS.windows?
path = File.join(root, base) path = File.join root, base
return nil unless File.exist?(path) return nil unless File.exist? path
File.open(path) do |f| File.open path do |f|
return make_creds(options.merge(json_key_io: f)) return make_creds options.merge(json_key_io: f)
end end
rescue StandardError => e rescue StandardError => e
raise "#{WELL_KNOWN_ERROR}: #{e}" raise "#{WELL_KNOWN_ERROR}: #{e}"
@ -147,28 +145,29 @@ module Google
# The following keys are recognized: # The following keys are recognized:
# * `:default_connection` The connection object to use. # * `:default_connection` The connection object to use.
# * `:connection_builder` A `Proc` that returns a connection. # * `:connection_builder` A `Proc` that returns a connection.
def from_system_default_path(scope = nil, options = {}) def from_system_default_path scope = nil, options = {}
options = interpret_options scope, options options = interpret_options scope, options
if OS.windows? if OS.windows?
return nil unless ENV["ProgramData"] return nil unless ENV["ProgramData"]
prefix = File.join(ENV["ProgramData"], "Google/Auth") prefix = File.join ENV["ProgramData"], "Google/Auth"
else else
prefix = "/etc/google/auth/" prefix = "/etc/google/auth/"
end end
path = File.join(prefix, CREDENTIALS_FILE_NAME) path = File.join prefix, CREDENTIALS_FILE_NAME
return nil unless File.exist?(path) return nil unless File.exist? path
File.open(path) do |f| File.open path do |f|
return make_creds(options.merge(json_key_io: f)) return make_creds options.merge(json_key_io: f)
end end
rescue StandardError => e rescue StandardError => e
raise "#{SYSTEM_DEFAULT_ERROR}: #{e}" raise "#{SYSTEM_DEFAULT_ERROR}: #{e}"
end end
module_function
# Issues warning if cloud sdk client id is used # Issues warning if cloud sdk client id is used
def warn_if_cloud_sdk_credentials(client_id) def warn_if_cloud_sdk_credentials client_id
warn CLOUD_SDK_CREDENTIALS_WARNING if client_id == CLOUD_SDK_CLIENT_ID warn CLOUD_SDK_CREDENTIALS_WARNING if client_id == CLOUD_SDK_CLIENT_ID
end end
module_function :warn_if_cloud_sdk_credentials
# Finds project_id from gcloud CLI configuration # Finds project_id from gcloud CLI configuration
def load_gcloud_project_id def load_gcloud_project_id
@ -177,23 +176,19 @@ module Google
gcloud_json = IO.popen("#{gcloud} #{GCLOUD_CONFIG_COMMAND}", &:read) gcloud_json = IO.popen("#{gcloud} #{GCLOUD_CONFIG_COMMAND}", &:read)
config = MultiJson.load gcloud_json config = MultiJson.load gcloud_json
config["configuration"]["properties"]["core"]["project"] config["configuration"]["properties"]["core"]["project"]
rescue rescue StandardError
nil nil
end end
module_function :load_gcloud_project_id
private private
def interpret_options(scope, options) def interpret_options scope, options
if scope.is_a? Hash if scope.is_a? Hash
options = scope options = scope
scope = nil scope = nil
end end
if scope && !options[:scope] return options.merge scope: scope if scope && !options[:scope]
options.merge(scope: scope) options
else
options
end
end end
def service_account_env_vars? def service_account_env_vars?
@ -201,8 +196,7 @@ module Google
end end
def authorized_user_env_vars? def authorized_user_env_vars?
([CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR] - ([CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR] - ENV.keys).empty?
ENV.keys).empty?
end end
end end
end end

View File

@ -45,17 +45,17 @@ module Google
# override CredentialsLoader#make_creds to use the class determined by # override CredentialsLoader#make_creds to use the class determined by
# loading the json. # loading the json.
def self.make_creds(options = {}) def self.make_creds options = {}
json_key_io = options[:json_key_io] json_key_io = options[:json_key_io]
if json_key_io if json_key_io
json_key, clz = determine_creds_class(json_key_io) json_key, clz = determine_creds_class json_key_io
warn_if_cloud_sdk_credentials json_key["client_id"] warn_if_cloud_sdk_credentials json_key["client_id"]
io = StringIO.new(MultiJson.dump(json_key)) io = StringIO.new MultiJson.dump(json_key)
clz.make_creds(options.merge(json_key_io: io)) clz.make_creds options.merge(json_key_io: io)
else else
warn_if_cloud_sdk_credentials ENV[CredentialsLoader::CLIENT_ID_VAR] warn_if_cloud_sdk_credentials ENV[CredentialsLoader::CLIENT_ID_VAR]
clz = read_creds clz = read_creds
clz.make_creds(options) clz.make_creds options
end end
end end
@ -74,10 +74,10 @@ module Google
end end
# Reads the input json and determines which creds class to use. # Reads the input json and determines which creds class to use.
def self.determine_creds_class(json_key_io) def self.determine_creds_class json_key_io
json_key = MultiJson.load json_key_io.read json_key = MultiJson.load json_key_io.read
key = "type" key = "type"
raise "the json is missing the '#{key}' field" unless json_key.key?(key) raise "the json is missing the '#{key}' field" unless json_key.key? key
type = json_key[key] type = json_key[key]
case type case type
when "service_account" when "service_account"

View File

@ -44,7 +44,7 @@ module Google
# #
# @param selector the IAM selector. # @param selector the IAM selector.
# @param token the IAM token. # @param token the IAM token.
def initialize(selector, token) def initialize selector, token
raise TypeError unless selector.is_a? String raise TypeError unless selector.is_a? String
raise TypeError unless token.is_a? String raise TypeError unless token.is_a? String
@selector = selector @selector = selector
@ -52,16 +52,16 @@ module Google
end end
# Adds the credential fields to the hash. # Adds the credential fields to the hash.
def apply!(a_hash) def apply! a_hash
a_hash[SELECTOR_KEY] = @selector a_hash[SELECTOR_KEY] = @selector
a_hash[TOKEN_KEY] = @token a_hash[TOKEN_KEY] = @token
a_hash a_hash
end end
# Returns a clone of a_hash updated with the authoriation header # Returns a clone of a_hash updated with the authoriation header
def apply(a_hash) def apply a_hash
a_copy = a_hash.clone a_copy = a_hash.clone
apply!(a_copy) apply! a_copy
a_copy a_copy
end end

View File

@ -34,10 +34,10 @@ module Google
# JsonKeyReader contains the behaviour used to read private key and # JsonKeyReader contains the behaviour used to read private key and
# client email fields from the service account # client email fields from the service account
module JsonKeyReader module JsonKeyReader
def read_json_key(json_key_io) def read_json_key json_key_io
json_key = MultiJson.load(json_key_io.read) json_key = MultiJson.load json_key_io.read
raise "missing client_email" unless json_key.key?("client_email") raise "missing client_email" unless json_key.key? "client_email"
raise "missing private_key" unless json_key.key?("private_key") raise "missing private_key" unless json_key.key? "private_key"
project_id = json_key["project_id"] project_id = json_key["project_id"]
[json_key["private_key"], json_key["client_email"], project_id] [json_key["private_key"], json_key["client_email"], project_id]
end end

View File

@ -36,22 +36,22 @@ module Google
# Small utility for normalizing scopes into canonical form # Small utility for normalizing scopes into canonical form
module ScopeUtil module ScopeUtil
ALIASES = { ALIASES = {
"email" => "https://www.googleapis.com/auth/userinfo.email", "email" => "https://www.googleapis.com/auth/userinfo.email",
"profile" => "https://www.googleapis.com/auth/userinfo.profile", "profile" => "https://www.googleapis.com/auth/userinfo.profile",
"openid" => "https://www.googleapis.com/auth/plus.me" "openid" => "https://www.googleapis.com/auth/plus.me"
}.freeze }.freeze
def self.normalize(scope) def self.normalize scope
list = as_array(scope) list = as_array scope
list.map { |item| ALIASES[item] || item } list.map { |item| ALIASES[item] || item }
end end
def self.as_array(scope) def self.as_array scope
case scope case scope
when Array when Array
scope scope
when String when String
scope.split(" ") scope.split " "
else else
raise "Invalid scope value. Must be string or array" raise "Invalid scope value. Must be string or array"
end end

View File

@ -56,10 +56,10 @@ module Google
# #
# @param json_key_io [IO] an IO from which the JSON key can be read # @param json_key_io [IO] an IO from which the JSON key can be read
# @param scope [string|array|nil] the scope(s) to access # @param scope [string|array|nil] the scope(s) to access
def self.make_creds(options = {}) def self.make_creds options = {}
json_key_io, scope = options.values_at(:json_key_io, :scope) json_key_io, scope = options.values_at :json_key_io, :scope
if json_key_io if json_key_io
private_key, client_email, project_id = read_json_key(json_key_io) private_key, client_email, project_id = read_json_key json_key_io
else else
private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR] private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR]
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR] client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
@ -68,26 +68,26 @@ module Google
project_id ||= CredentialsLoader.load_gcloud_project_id project_id ||= CredentialsLoader.load_gcloud_project_id
new(token_credential_uri: TOKEN_CRED_URI, new(token_credential_uri: TOKEN_CRED_URI,
audience: TOKEN_CRED_URI, audience: TOKEN_CRED_URI,
scope: scope, scope: scope,
issuer: client_email, issuer: client_email,
signing_key: OpenSSL::PKey::RSA.new(private_key), signing_key: OpenSSL::PKey::RSA.new(private_key),
project_id: project_id) project_id: project_id)
.configure_connection(options) .configure_connection(options)
end end
# Handles certain escape sequences that sometimes appear in input. # Handles certain escape sequences that sometimes appear in input.
# Specifically, interprets the "\n" sequence for newline, and removes # Specifically, interprets the "\n" sequence for newline, and removes
# enclosing quotes. # enclosing quotes.
def self.unescape(str) def self.unescape str
str = str.gsub '\n', "\n" str = str.gsub '\n', "\n"
str = str[1..-2] if str.start_with?('"') && str.end_with?('"') str = str[1..-2] if str.start_with?('"') && str.end_with?('"')
str str
end end
def initialize(options = {}) def initialize options = {}
@project_id = options[:project_id] @project_id = options[:project_id]
super(options) super options
end end
# Extends the base class. # Extends the base class.
@ -95,7 +95,7 @@ module Google
# If scope(s) is not set, it creates a transient # If scope(s) is not set, it creates a transient
# ServiceAccountJwtHeaderCredentials instance and uses that to # ServiceAccountJwtHeaderCredentials instance and uses that to
# authenticate instead. # authenticate instead.
def apply!(a_hash, opts = {}) def apply! a_hash, opts = {}
# Use the base implementation if scopes are set # Use the base implementation if scopes are set
unless scope.nil? unless scope.nil?
super super
@ -105,13 +105,13 @@ module Google
# Use the ServiceAccountJwtHeaderCredentials using the same cred values # Use the ServiceAccountJwtHeaderCredentials using the same cred values
# if no scopes are set. # if no scopes are set.
cred_json = { cred_json = {
private_key: @signing_key.to_s, private_key: @signing_key.to_s,
client_email: @issuer client_email: @issuer
} }
alt_clz = ServiceAccountJwtHeaderCredentials alt_clz = ServiceAccountJwtHeaderCredentials
key_io = StringIO.new(MultiJson.dump(cred_json)) key_io = StringIO.new MultiJson.dump(cred_json)
alt = alt_clz.make_creds(json_key_io: key_io) alt = alt_clz.make_creds json_key_io: key_io
alt.apply!(a_hash) alt.apply! a_hash
end end
end end
@ -141,43 +141,43 @@ module Google
# By default, it calls #new with 2 args, the second one being an # By default, it calls #new with 2 args, the second one being an
# optional scope. Here's the constructor only has one param, so # optional scope. Here's the constructor only has one param, so
# we modify make_creds to reflect this. # we modify make_creds to reflect this.
def self.make_creds(*args) def self.make_creds *args
new(json_key_io: args[0][:json_key_io]) new json_key_io: args[0][:json_key_io]
end end
# Initializes a ServiceAccountJwtHeaderCredentials. # Initializes a ServiceAccountJwtHeaderCredentials.
# #
# @param json_key_io [IO] an IO from which the JSON key can be read # @param json_key_io [IO] an IO from which the JSON key can be read
def initialize(options = {}) def initialize options = {}
json_key_io = options[:json_key_io] json_key_io = options[:json_key_io]
if json_key_io if json_key_io
@private_key, @issuer, @project_id = @private_key, @issuer, @project_id =
self.class.read_json_key(json_key_io) self.class.read_json_key json_key_io
else else
@private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR] @private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
@issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR] @issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
@project_id = ENV[CredentialsLoader::PROJECT_ID_VAR] @project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
end end
@project_id ||= CredentialsLoader.load_gcloud_project_id @project_id ||= CredentialsLoader.load_gcloud_project_id
@signing_key = OpenSSL::PKey::RSA.new(@private_key) @signing_key = OpenSSL::PKey::RSA.new @private_key
end end
# Construct a jwt token if the JWT_AUD_URI key is present in the input # Construct a jwt token if the JWT_AUD_URI key is present in the input
# hash. # hash.
# #
# The jwt token is used as the value of a 'Bearer '. # The jwt token is used as the value of a 'Bearer '.
def apply!(a_hash, opts = {}) def apply! a_hash, opts = {}
jwt_aud_uri = a_hash.delete(JWT_AUD_URI_KEY) jwt_aud_uri = a_hash.delete JWT_AUD_URI_KEY
return a_hash if jwt_aud_uri.nil? return a_hash if jwt_aud_uri.nil?
jwt_token = new_jwt_token(jwt_aud_uri, opts) jwt_token = new_jwt_token jwt_aud_uri, opts
a_hash[AUTH_METADATA_KEY] = "Bearer #{jwt_token}" a_hash[AUTH_METADATA_KEY] = "Bearer #{jwt_token}"
a_hash a_hash
end end
# Returns a clone of a_hash updated with the authoriation header # Returns a clone of a_hash updated with the authoriation header
def apply(a_hash, opts = {}) def apply a_hash, opts = {}
a_copy = a_hash.clone a_copy = a_hash.clone
apply!(a_copy, opts) apply! a_copy, opts
a_copy a_copy
end end
@ -190,7 +190,7 @@ module Google
protected protected
# Creates a jwt uri token. # Creates a jwt uri token.
def new_jwt_token(jwt_aud_uri, options = {}) def new_jwt_token jwt_aud_uri, options = {}
now = Time.new now = Time.new
skew = options[:skew] || 60 skew = options[:skew] || 60
assertion = { assertion = {
@ -200,7 +200,7 @@ module Google
"exp" => (now + EXPIRY).to_i, "exp" => (now + EXPIRY).to_i,
"iat" => (now - skew).to_i "iat" => (now - skew).to_i
} }
JWT.encode(assertion, @signing_key, SIGNING_ALGORITHM) JWT.encode assertion, @signing_key, SIGNING_ALGORITHM
end end
end end
end end

View File

@ -38,24 +38,24 @@ module Signet
# This reopens Client to add #apply and #apply! methods which update a # This reopens Client to add #apply and #apply! methods which update a
# hash with the fetched authentication token. # hash with the fetched authentication token.
class Client class Client
def configure_connection(options) def configure_connection options
@connection_info = @connection_info =
options[:connection_builder] || options[:default_connection] options[:connection_builder] || options[:default_connection]
self self
end end
# Updates a_hash updated with the authentication token # Updates a_hash updated with the authentication token
def apply!(a_hash, opts = {}) def apply! a_hash, opts = {}
# fetch the access token there is currently not one, or if the client # fetch the access token there is currently not one, or if the client
# has expired # has expired
fetch_access_token!(opts) if access_token.nil? || expires_within?(60) fetch_access_token! opts if access_token.nil? || expires_within?(60)
a_hash[AUTH_METADATA_KEY] = "Bearer #{access_token}" a_hash[AUTH_METADATA_KEY] = "Bearer #{access_token}"
end end
# Returns a clone of a_hash updated with the authentication token # Returns a clone of a_hash updated with the authentication token
def apply(a_hash, opts = {}) def apply a_hash, opts = {}
a_copy = a_hash.clone a_copy = a_hash.clone
apply!(a_copy, opts) apply! a_copy, opts
a_copy a_copy
end end
@ -65,18 +65,18 @@ module Signet
lambda(&method(:apply)) lambda(&method(:apply))
end end
def on_refresh(&block) def on_refresh &block
@refresh_listeners ||= [] @refresh_listeners ||= []
@refresh_listeners << block @refresh_listeners << block
end end
alias orig_fetch_access_token! fetch_access_token! alias orig_fetch_access_token! fetch_access_token!
def fetch_access_token!(options = {}) def fetch_access_token! options = {}
unless options[:connection] unless options[:connection]
connection = build_default_connection connection = build_default_connection
options = options.merge(connection: connection) if connection options = options.merge connection: connection if connection
end end
info = orig_fetch_access_token!(options) info = orig_fetch_access_token! options
notify_refresh_listeners notify_refresh_listeners
info info
end end
@ -84,7 +84,7 @@ module Signet
def notify_refresh_listeners def notify_refresh_listeners
listeners = @refresh_listeners || [] listeners = @refresh_listeners || []
listeners.each do |block| listeners.each do |block|
block.call(self) block.call self
end end
end end
@ -98,15 +98,13 @@ module Signet
end end
end end
def retry_with_error(max_retry_count = 5) def retry_with_error max_retry_count = 5
retry_count = 0 retry_count = 0
begin begin
yield yield
rescue => e rescue StandardError => e
if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError) raise e if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError)
raise e
end
if retry_count < max_retry_count if retry_count < max_retry_count
retry_count += 1 retry_count += 1
@ -114,7 +112,7 @@ module Signet
retry retry
else else
msg = "Unexpected error: #{e.inspect}" msg = "Unexpected error: #{e.inspect}"
raise(Signet::AuthorizationError, msg) raise Signet::AuthorizationError, msg
end end
end end
end end

View File

@ -39,24 +39,24 @@ module Google
# #
# @param [String, File] file # @param [String, File] file
# Path to storage file # Path to storage file
def initialize(options = {}) def initialize options = {}
path = options[:file] path = options[:file]
@store = YAML::Store.new(path) @store = YAML::Store.new path
end end
# (see Google::Auth::Stores::TokenStore#load) # (see Google::Auth::Stores::TokenStore#load)
def load(id) def load id
@store.transaction { @store[id] } @store.transaction { @store[id] }
end end
# (see Google::Auth::Stores::TokenStore#store) # (see Google::Auth::Stores::TokenStore#store)
def store(id, token) def store id, token
@store.transaction { @store[id] = token } @store.transaction { @store[id] = token }
end end
# (see Google::Auth::Stores::TokenStore#delete) # (see Google::Auth::Stores::TokenStore#delete)
def delete(id) def delete id
@store.transaction { @store.delete(id) } @store.transaction { @store.delete id }
end end
end end
end end

View File

@ -48,34 +48,34 @@ module Google
# @note If no redis instance is provided, a new one is created and # @note If no redis instance is provided, a new one is created and
# the options passed through. You may include any other keys accepted # the options passed through. You may include any other keys accepted
# by `Redis.new` # by `Redis.new`
def initialize(options = {}) def initialize options = {}
redis = options.delete(:redis) redis = options.delete :redis
prefix = options.delete(:prefix) prefix = options.delete :prefix
@redis = case redis @redis = case redis
when Redis when Redis
redis redis
else else
Redis.new(options) Redis.new options
end end
@prefix = prefix || DEFAULT_KEY_PREFIX @prefix = prefix || DEFAULT_KEY_PREFIX
end end
# (see Google::Auth::Stores::TokenStore#load) # (see Google::Auth::Stores::TokenStore#load)
def load(id) def load id
key = key_for(id) key = key_for id
@redis.get(key) @redis.get key
end end
# (see Google::Auth::Stores::TokenStore#store) # (see Google::Auth::Stores::TokenStore#store)
def store(id, token) def store id, token
key = key_for(id) key = key_for id
@redis.set(key, token) @redis.set key, token
end end
# (see Google::Auth::Stores::TokenStore#delete) # (see Google::Auth::Stores::TokenStore#delete)
def delete(id) def delete id
key = key_for(id) key = key_for id
@redis.del(key) @redis.del key
end end
private private
@ -86,7 +86,7 @@ module Google
# ID of the token # ID of the token
# @return [String] # @return [String]
# Redis key # Redis key
def key_for(id) def key_for id
@prefix + id @prefix + id
end end
end end

View File

@ -43,7 +43,7 @@ module Google
# ID of token data to load. # ID of token data to load.
# @return [String] # @return [String]
# The loaded token data. # The loaded token data.
def load(_id) def load _id
raise "Not implemented" raise "Not implemented"
end end
@ -53,7 +53,7 @@ module Google
# ID of token data to store. # ID of token data to store.
# @param [String] token # @param [String] token
# The token data to store. # The token data to store.
def store(_id, _token) def store _id, _token
raise "Not implemented" raise "Not implemented"
end end
@ -61,7 +61,7 @@ module Google
# #
# @param [String] id # @param [String] id
# ID of the token data to delete # ID of the token data to delete
def delete(_id) def delete _id
raise "Not implemented" raise "Not implemented"
end end
end end

View File

@ -72,7 +72,7 @@ module Google
# @param [String] callback_uri # @param [String] callback_uri
# URL (either absolute or relative) of the auth callback. # URL (either absolute or relative) of the auth callback.
# Defaults to '/oauth2callback' # Defaults to '/oauth2callback'
def initialize(client_id, scope, token_store, callback_uri = nil) def initialize client_id, scope, token_store, callback_uri = nil
raise NIL_CLIENT_ID_ERROR if client_id.nil? raise NIL_CLIENT_ID_ERROR if client_id.nil?
raise NIL_SCOPE_ERROR if scope.nil? raise NIL_SCOPE_ERROR if scope.nil?
@ -97,20 +97,20 @@ module Google
# nil. # nil.
# @return [String] # @return [String]
# Authorization url # Authorization url
def get_authorization_url(options = {}) def get_authorization_url options = {}
scope = options[:scope] || @scope scope = options[:scope] || @scope
credentials = UserRefreshCredentials.new( credentials = UserRefreshCredentials.new(
client_id: @client_id.id, client_id: @client_id.id,
client_secret: @client_id.secret, client_secret: @client_id.secret,
scope: scope scope: scope
) )
redirect_uri = redirect_uri_for(options[:base_url]) redirect_uri = redirect_uri_for options[:base_url]
url = credentials.authorization_uri(access_type: "offline", url = credentials.authorization_uri(access_type: "offline",
redirect_uri: redirect_uri, redirect_uri: redirect_uri,
approval_prompt: "force", approval_prompt: "force",
state: options[:state], state: options[:state],
include_granted_scopes: true, include_granted_scopes: true,
login_hint: options[:login_hint]) login_hint: options[:login_hint])
url.to_s url.to_s
end end
@ -123,28 +123,26 @@ module Google
# the requested scopes # the requested scopes
# @return [Google::Auth::UserRefreshCredentials] # @return [Google::Auth::UserRefreshCredentials]
# Stored credentials, nil if none present # Stored credentials, nil if none present
def get_credentials(user_id, scope = nil) def get_credentials user_id, scope = nil
saved_token = stored_token(user_id) saved_token = stored_token user_id
return nil if saved_token.nil? return nil if saved_token.nil?
data = MultiJson.load(saved_token) data = MultiJson.load saved_token
if data.fetch("client_id", @client_id.id) != @client_id.id if data.fetch("client_id", @client_id.id) != @client_id.id
raise sprintf(MISMATCHED_CLIENT_ID_ERROR, raise format(MISMATCHED_CLIENT_ID_ERROR,
data["client_id"], @client_id.id) data["client_id"], @client_id.id)
end end
credentials = UserRefreshCredentials.new( credentials = UserRefreshCredentials.new(
client_id: @client_id.id, client_id: @client_id.id,
client_secret: @client_id.secret, client_secret: @client_id.secret,
scope: data["scope"] || @scope, scope: data["scope"] || @scope,
access_token: data["access_token"], access_token: data["access_token"],
refresh_token: data["refresh_token"], refresh_token: data["refresh_token"],
expires_at: data.fetch("expiration_time_millis", 0) / 1000 expires_at: data.fetch("expiration_time_millis", 0) / 1000
) )
scope ||= @scope scope ||= @scope
if credentials.includes_scope?(scope) return monitor_credentials user_id, credentials if credentials.includes_scope? scope
return monitor_credentials(user_id, credentials)
end
nil nil
end end
@ -163,20 +161,20 @@ module Google
# callback uri is a relative. # callback uri is a relative.
# @return [Google::Auth::UserRefreshCredentials] # @return [Google::Auth::UserRefreshCredentials]
# Credentials if exchange is successful # Credentials if exchange is successful
def get_credentials_from_code(options = {}) def get_credentials_from_code options = {}
user_id = options[:user_id] user_id = options[:user_id]
code = options[:code] code = options[:code]
scope = options[:scope] || @scope scope = options[:scope] || @scope
base_url = options[:base_url] base_url = options[:base_url]
credentials = UserRefreshCredentials.new( credentials = UserRefreshCredentials.new(
client_id: @client_id.id, client_id: @client_id.id,
client_secret: @client_id.secret, client_secret: @client_id.secret,
redirect_uri: redirect_uri_for(base_url), redirect_uri: redirect_uri_for(base_url),
scope: scope scope: scope
) )
credentials.code = code credentials.code = code
credentials.fetch_access_token!({}) credentials.fetch_access_token!({})
monitor_credentials(user_id, credentials) monitor_credentials user_id, credentials
end end
# Exchanges an authorization code returned in the oauth callback. # Exchanges an authorization code returned in the oauth callback.
@ -196,9 +194,9 @@ module Google
# callback uri is a relative. # callback uri is a relative.
# @return [Google::Auth::UserRefreshCredentials] # @return [Google::Auth::UserRefreshCredentials]
# Credentials if exchange is successful # Credentials if exchange is successful
def get_and_store_credentials_from_code(options = {}) def get_and_store_credentials_from_code options = {}
credentials = get_credentials_from_code(options) credentials = get_credentials_from_code options
store_credentials(options[:user_id], credentials) store_credentials options[:user_id], credentials
end end
# Revokes a user's credentials. This both revokes the actual # Revokes a user's credentials. This both revokes the actual
@ -206,11 +204,11 @@ module Google
# #
# @param [String] user_id # @param [String] user_id
# Unique ID of the user for loading/storing credentials. # Unique ID of the user for loading/storing credentials.
def revoke_authorization(user_id) def revoke_authorization user_id
credentials = get_credentials(user_id) credentials = get_credentials user_id
if credentials if credentials
begin begin
@token_store.delete(user_id) @token_store.delete user_id
ensure ensure
credentials.revoke! credentials.revoke!
end end
@ -226,15 +224,15 @@ module Google
# Unique ID of the user for loading/storing credentials. # Unique ID of the user for loading/storing credentials.
# @param [Google::Auth::UserRefreshCredentials] credentials # @param [Google::Auth::UserRefreshCredentials] credentials
# Credentials to store. # Credentials to store.
def store_credentials(user_id, credentials) def store_credentials user_id, credentials
json = MultiJson.dump( json = MultiJson.dump(
client_id: credentials.client_id, client_id: credentials.client_id,
access_token: credentials.access_token, access_token: credentials.access_token,
refresh_token: credentials.refresh_token, refresh_token: credentials.refresh_token,
scope: credentials.scope, scope: credentials.scope,
expiration_time_millis: credentials.expires_at.to_i * 1000 expiration_time_millis: credentials.expires_at.to_i * 1000
) )
@token_store.store(user_id, json) @token_store.store user_id, json
credentials credentials
end end
@ -245,11 +243,11 @@ module Google
# @param [String] user_id # @param [String] user_id
# Unique ID of the user for loading/storing credentials. # Unique ID of the user for loading/storing credentials.
# @return [String] The saved token from @token_store # @return [String] The saved token from @token_store
def stored_token(user_id) def stored_token user_id
raise NIL_USER_ID_ERROR if user_id.nil? raise NIL_USER_ID_ERROR if user_id.nil?
raise NIL_TOKEN_STORE_ERROR if @token_store.nil? raise NIL_TOKEN_STORE_ERROR if @token_store.nil?
@token_store.load(user_id) @token_store.load user_id
end end
# Begin watching a credential for refreshes so the access token can be # Begin watching a credential for refreshes so the access token can be
@ -259,9 +257,9 @@ module Google
# Unique ID of the user for loading/storing credentials. # Unique ID of the user for loading/storing credentials.
# @param [Google::Auth::UserRefreshCredentials] credentials # @param [Google::Auth::UserRefreshCredentials] credentials
# Credentials to store. # Credentials to store.
def monitor_credentials(user_id, credentials) def monitor_credentials user_id, credentials
credentials.on_refresh do |cred| credentials.on_refresh do |cred|
store_credentials(user_id, cred) store_credentials user_id, cred
end end
credentials credentials
end end
@ -272,11 +270,9 @@ module Google
# Absolute URL to resolve the callback against if necessary. # Absolute URL to resolve the callback against if necessary.
# @return [String] # @return [String]
# Redirect URI # Redirect URI
def redirect_uri_for(base_url) def redirect_uri_for base_url
return @callback_uri unless URI(@callback_uri).scheme.nil? return @callback_uri unless URI(@callback_uri).scheme.nil?
if base_url.nil? || URI(base_url).scheme.nil? raise format(MISSING_ABSOLUTE_URL_ERROR, @callback_uri) if base_url.nil? || URI(base_url).scheme.nil?
raise sprintf(MISSING_ABSOLUTE_URL_ERROR, @callback_uri)
end
URI.join(base_url, @callback_uri).to_s URI.join(base_url, @callback_uri).to_s
end end
end end

View File

@ -56,9 +56,9 @@ module Google
# #
# @param json_key_io [IO] an IO from which the JSON key can be read # @param json_key_io [IO] an IO from which the JSON key can be read
# @param scope [string|array|nil] the scope(s) to access # @param scope [string|array|nil] the scope(s) to access
def self.make_creds(options = {}) def self.make_creds options = {}
json_key_io, scope = options.values_at(:json_key_io, :scope) json_key_io, scope = options.values_at :json_key_io, :scope
user_creds = read_json_key(json_key_io) if json_key_io user_creds = read_json_key json_key_io if json_key_io
user_creds ||= { user_creds ||= {
"client_id" => ENV[CredentialsLoader::CLIENT_ID_VAR], "client_id" => ENV[CredentialsLoader::CLIENT_ID_VAR],
"client_secret" => ENV[CredentialsLoader::CLIENT_SECRET_VAR], "client_secret" => ENV[CredentialsLoader::CLIENT_SECRET_VAR],
@ -67,36 +67,36 @@ module Google
} }
new(token_credential_uri: TOKEN_CRED_URI, new(token_credential_uri: TOKEN_CRED_URI,
client_id: user_creds["client_id"], client_id: user_creds["client_id"],
client_secret: user_creds["client_secret"], client_secret: user_creds["client_secret"],
refresh_token: user_creds["refresh_token"], refresh_token: user_creds["refresh_token"],
project_id: user_creds["project_id"], project_id: user_creds["project_id"],
scope: scope) scope: scope)
.configure_connection(options) .configure_connection(options)
end end
# Reads the client_id, client_secret and refresh_token fields from the # Reads the client_id, client_secret and refresh_token fields from the
# JSON key. # JSON key.
def self.read_json_key(json_key_io) def self.read_json_key json_key_io
json_key = MultiJson.load(json_key_io.read) json_key = MultiJson.load json_key_io.read
wanted = %w(client_id client_secret refresh_token) wanted = %w[client_id client_secret refresh_token]
wanted.each do |key| wanted.each do |key|
raise "the json is missing the #{key} field" unless json_key.key?(key) raise "the json is missing the #{key} field" unless json_key.key? key
end end
json_key json_key
end end
def initialize(options = {}) def initialize options = {}
options ||= {} options ||= {}
options[:token_credential_uri] ||= TOKEN_CRED_URI options[:token_credential_uri] ||= TOKEN_CRED_URI
options[:authorization_uri] ||= AUTHORIZATION_URI options[:authorization_uri] ||= AUTHORIZATION_URI
@project_id = options[:project_id] @project_id = options[:project_id]
@project_id ||= CredentialsLoader.load_gcloud_project_id @project_id ||= CredentialsLoader.load_gcloud_project_id
super(options) super options
end end
# Revokes the credential # Revokes the credential
def revoke!(options = {}) def revoke! options = {}
c = options[:connection] || Faraday.default_connection c = options[:connection] || Faraday.default_connection
retry_with_error do retry_with_error do
@ -119,7 +119,7 @@ module Google
# Scope to verify # Scope to verify
# @return [Boolean] # @return [Boolean]
# True if scope is granted # True if scope is granted
def includes_scope?(required_scope) def includes_scope? required_scope
missing_scope = Google::Auth::ScopeUtil.normalize(required_scope) - missing_scope = Google::Auth::ScopeUtil.normalize(required_scope) -
Google::Auth::ScopeUtil.normalize(scope) Google::Auth::ScopeUtil.normalize(scope)
missing_scope.empty? missing_scope.empty?

View File

@ -97,9 +97,9 @@ module Google
# #
# @param [Rack::Request] request # @param [Rack::Request] request
# Current request # Current request
def self.handle_auth_callback_deferred(request) def self.handle_auth_callback_deferred request
callback_state, redirect_uri = extract_callback_state(request) callback_state, redirect_uri = extract_callback_state request
request.session[CALLBACK_STATE_KEY] = MultiJson.dump(callback_state) request.session[CALLBACK_STATE_KEY] = MultiJson.dump callback_state
redirect_uri redirect_uri
end end
@ -114,8 +114,8 @@ module Google
# @param [String] callback_uri # @param [String] callback_uri
# URL (either absolute or relative) of the auth callback. Defaults # URL (either absolute or relative) of the auth callback. Defaults
# to '/oauth2callback' # to '/oauth2callback'
def initialize(client_id, scope, token_store, callback_uri = nil) def initialize client_id, scope, token_store, callback_uri = nil
super(client_id, scope, token_store, callback_uri) super client_id, scope, token_store, callback_uri
end end
# Handle the result of the oauth callback. Exchanges the authorization # Handle the result of the oauth callback. Exchanges the authorization
@ -127,15 +127,15 @@ module Google
# Current request # Current request
# @return (Google::Auth::UserRefreshCredentials, String) # @return (Google::Auth::UserRefreshCredentials, String)
# credentials & next URL to redirect to # credentials & next URL to redirect to
def handle_auth_callback(user_id, request) def handle_auth_callback user_id, request
callback_state, redirect_uri = WebUserAuthorizer.extract_callback_state( callback_state, redirect_uri = WebUserAuthorizer.extract_callback_state(
request request
) )
WebUserAuthorizer.validate_callback_state(callback_state, request) WebUserAuthorizer.validate_callback_state callback_state, request
credentials = get_and_store_credentials_from_code( credentials = get_and_store_credentials_from_code(
user_id: user_id, user_id: user_id,
code: callback_state[AUTH_CODE_KEY], code: callback_state[AUTH_CODE_KEY],
scope: callback_state[SCOPE_KEY], scope: callback_state[SCOPE_KEY],
base_url: request.url base_url: request.url
) )
[credentials, redirect_uri] [credentials, redirect_uri]
@ -156,7 +156,7 @@ module Google
# not nil. # not nil.
# @return [String] # @return [String]
# Authorization url # Authorization url
def get_authorization_url(options = {}) def get_authorization_url options = {}
options = options.dup options = options.dup
request = options[:request] request = options[:request]
raise NIL_REQUEST_ERROR if request.nil? raise NIL_REQUEST_ERROR if request.nil?
@ -165,11 +165,11 @@ module Google
redirect_to = options[:redirect_to] || request.url redirect_to = options[:redirect_to] || request.url
request.session[XSRF_KEY] = SecureRandom.base64 request.session[XSRF_KEY] = SecureRandom.base64
options[:state] = MultiJson.dump( options[:state] = MultiJson.dump(
SESSION_ID_KEY => request.session[XSRF_KEY], SESSION_ID_KEY => request.session[XSRF_KEY],
CURRENT_URI_KEY => redirect_to CURRENT_URI_KEY => redirect_to
) )
options[:base_url] = request.url options[:base_url] = request.url
super(options) super options
end end
# Fetch stored credentials for the user. # Fetch stored credentials for the user.
@ -186,32 +186,32 @@ module Google
# @raise [Signet::AuthorizationError] # @raise [Signet::AuthorizationError]
# May raise an error if an authorization code is present in the session # May raise an error if an authorization code is present in the session
# and exchange of the code fails # and exchange of the code fails
def get_credentials(user_id, request, scope = nil) def get_credentials user_id, request, scope = nil
if request.session.key?(CALLBACK_STATE_KEY) if request.session.key? CALLBACK_STATE_KEY
# Note - in theory, no need to check required scope as this is # Note - in theory, no need to check required scope as this is
# expected to be called immediately after a return from authorization # expected to be called immediately after a return from authorization
state_json = request.session.delete(CALLBACK_STATE_KEY) state_json = request.session.delete CALLBACK_STATE_KEY
callback_state = MultiJson.load(state_json) callback_state = MultiJson.load state_json
WebUserAuthorizer.validate_callback_state(callback_state, request) WebUserAuthorizer.validate_callback_state callback_state, request
get_and_store_credentials_from_code( get_and_store_credentials_from_code(
user_id: user_id, user_id: user_id,
code: callback_state[AUTH_CODE_KEY], code: callback_state[AUTH_CODE_KEY],
scope: callback_state[SCOPE_KEY], scope: callback_state[SCOPE_KEY],
base_url: request.url base_url: request.url
) )
else else
super(user_id, scope) super user_id, scope
end end
end end
def self.extract_callback_state(request) def self.extract_callback_state request
state = MultiJson.load(request[STATE_PARAM] || "{}") state = MultiJson.load(request[STATE_PARAM] || "{}")
redirect_uri = state[CURRENT_URI_KEY] redirect_uri = state[CURRENT_URI_KEY]
callback_state = { callback_state = {
AUTH_CODE_KEY => request[AUTH_CODE_KEY], AUTH_CODE_KEY => request[AUTH_CODE_KEY],
ERROR_CODE_KEY => request[ERROR_CODE_KEY], ERROR_CODE_KEY => request[ERROR_CODE_KEY],
SESSION_ID_KEY => state[SESSION_ID_KEY], SESSION_ID_KEY => state[SESSION_ID_KEY],
SCOPE_KEY => request[SCOPE_KEY] SCOPE_KEY => request[SCOPE_KEY]
} }
[callback_state, redirect_uri] [callback_state, redirect_uri]
end end
@ -226,12 +226,11 @@ module Google
# Error message if failed # Error message if failed
# @param [Rack::Request] request # @param [Rack::Request] request
# Current request # Current request
def self.validate_callback_state(state, request) def self.validate_callback_state state, request
if state[AUTH_CODE_KEY].nil? raise Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR if state[AUTH_CODE_KEY].nil?
raise Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR if state[ERROR_CODE_KEY]
elsif state[ERROR_CODE_KEY]
raise Signet::AuthorizationError, raise Signet::AuthorizationError,
sprintf(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY]) format(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY])
elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY] elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY]
raise Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR raise Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR
end end
@ -275,9 +274,9 @@ module Google
# Rack environment # Rack environment
# @return [Array] # @return [Array]
# HTTP response # HTTP response
def self.call(env) def self.call env
request = Rack::Request.new(env) request = Rack::Request.new env
return_url = WebUserAuthorizer.handle_auth_callback_deferred(request) return_url = WebUserAuthorizer.handle_auth_callback_deferred request
if return_url if return_url
[REDIR_STATUS, { LOCATION_HEADER => return_url }, []] [REDIR_STATUS, { LOCATION_HEADER => return_url }, []]
else else
@ -285,8 +284,8 @@ module Google
end end
end end
def call(env) def call env
self.class.call(env) self.class.call env
end end
end end
end end

View File

@ -27,14 +27,14 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'faraday' require "faraday"
require 'spec_helper' require "spec_helper"
shared_examples 'apply/apply! are OK' do shared_examples "apply/apply! are OK" do
let(:auth_key) { :authorization } let(:auth_key) { :authorization }
# tests that use these examples need to define # tests that use these examples need to define
@ -43,103 +43,103 @@ shared_examples 'apply/apply! are OK' do
# #
# @make_auth_stubs, which should stub out the expected http behaviour of the # @make_auth_stubs, which should stub out the expected http behaviour of the
# auth client # auth client
describe '#fetch_access_token' do describe "#fetch_access_token" do
let(:token) { '1/abcdef1234567890' } let(:token) { "1/abcdef1234567890" }
let(:stub) do let :stub do
make_auth_stubs access_token: token make_auth_stubs access_token: token
end end
it 'should set access_token to the fetched value' do it "should set access_token to the fetched value" do
stub stub
@client.fetch_access_token! @client.fetch_access_token!
expect(@client.access_token).to eq(token) expect(@client.access_token).to eq(token)
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end
it 'should notify refresh listeners after updating' do it "should notify refresh listeners after updating" do
stub stub
expect do |b| expect do |b|
@client.on_refresh(&b) @client.on_refresh(&b)
@client.fetch_access_token! @client.fetch_access_token!
end.to yield_with_args(have_attributes( end.to yield_with_args(have_attributes(
access_token: '1/abcdef1234567890' access_token: "1/abcdef1234567890"
)) ))
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end
end end
describe '#apply!' do describe "#apply!" do
it 'should update the target hash with fetched access token' do it "should update the target hash with fetched access token" do
token = '1/abcdef1234567890' token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token stub = make_auth_stubs access_token: token
md = { foo: 'bar' } md = { foo: "bar" }
@client.apply!(md) @client.apply! md
want = { :foo => 'bar', auth_key => "Bearer #{token}" } want = { :foo => "bar", auth_key => "Bearer #{token}" }
expect(md).to eq(want) expect(md).to eq(want)
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end
end end
describe 'updater_proc' do describe "updater_proc" do
it 'should provide a proc that updates a hash with the access token' do it "should provide a proc that updates a hash with the access token" do
token = '1/abcdef1234567890' token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token stub = make_auth_stubs access_token: token
md = { foo: 'bar' } md = { foo: "bar" }
the_proc = @client.updater_proc the_proc = @client.updater_proc
got = the_proc.call(md) got = the_proc.call md
want = { :foo => 'bar', auth_key => "Bearer #{token}" } want = { :foo => "bar", auth_key => "Bearer #{token}" }
expect(got).to eq(want) expect(got).to eq(want)
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end
end end
describe '#apply' do describe "#apply" do
it 'should not update the original hash with the access token' do it "should not update the original hash with the access token" do
token = '1/abcdef1234567890' token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token stub = make_auth_stubs access_token: token
md = { foo: 'bar' } md = { foo: "bar" }
@client.apply(md) @client.apply md
want = { foo: 'bar' } want = { foo: "bar" }
expect(md).to eq(want) expect(md).to eq(want)
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end
it 'should add the token to the returned hash' do it "should add the token to the returned hash" do
token = '1/abcdef1234567890' token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token stub = make_auth_stubs access_token: token
md = { foo: 'bar' } md = { foo: "bar" }
got = @client.apply(md) got = @client.apply md
want = { :foo => 'bar', auth_key => "Bearer #{token}" } want = { :foo => "bar", auth_key => "Bearer #{token}" }
expect(got).to eq(want) expect(got).to eq(want)
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end
it 'should not fetch a new token if the current is not expired' do it "should not fetch a new token if the current is not expired" do
token = '1/abcdef1234567890' token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token stub = make_auth_stubs access_token: token
n = 5 # arbitrary n = 5 # arbitrary
n.times do |_t| n.times do |_t|
md = { foo: 'bar' } md = { foo: "bar" }
got = @client.apply(md) got = @client.apply md
want = { :foo => 'bar', auth_key => "Bearer #{token}" } want = { :foo => "bar", auth_key => "Bearer #{token}" }
expect(got).to eq(want) expect(got).to eq(want)
end end
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end
it 'should fetch a new token if the current one is expired' do it "should fetch a new token if the current one is expired" do
token1 = '1/abcdef1234567890' token1 = "1/abcdef1234567890"
token2 = '2/abcdef1234567891' token2 = "2/abcdef1234567891"
[token1, token2].each do |t| [token1, token2].each do |t|
make_auth_stubs access_token: t make_auth_stubs access_token: t
md = { foo: 'bar' } md = { foo: "bar" }
got = @client.apply(md) got = @client.apply md
want = { :foo => 'bar', auth_key => "Bearer #{t}" } want = { :foo => "bar", auth_key => "Bearer #{t}" }
expect(got).to eq(want) expect(got).to eq(want)
@client.expires_at -= 3601 # default is to expire in 1hr @client.expires_at -= 3601 # default is to expire in 1hr
end end

View File

@ -27,131 +27,131 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'spec_helper' require "spec_helper"
require 'fakefs/safe' require "fakefs/safe"
require 'googleauth' require "googleauth"
describe Google::Auth::ClientId do describe Google::Auth::ClientId do
shared_examples 'it has a valid config' do shared_examples "it has a valid config" do
it 'should include a valid id' do it "should include a valid id" do
expect(client_id.id).to eql 'abc@example.com' expect(client_id.id).to eql "abc@example.com"
end end
it 'should include a valid secret' do it "should include a valid secret" do
expect(client_id.secret).to eql 'notasecret' expect(client_id.secret).to eql "notasecret"
end end
end end
shared_examples 'it can successfully load client_id' do shared_examples "it can successfully load client_id" do
context 'loaded from hash' do context "loaded from hash" do
let(:client_id) { Google::Auth::ClientId.from_hash config } let(:client_id) { Google::Auth::ClientId.from_hash config }
it_behaves_like 'it has a valid config' it_behaves_like "it has a valid config"
end end
context 'loaded from file' do context "loaded from file" do
file_path = '/client_secrets.json' file_path = "/client_secrets.json"
let(:client_id) do let :client_id do
FakeFS do FakeFS do
content = MultiJson.dump(config) content = MultiJson.dump config
File.write(file_path, content) File.write file_path, content
Google::Auth::ClientId.from_file(file_path) Google::Auth::ClientId.from_file file_path
end end
end end
it_behaves_like 'it has a valid config' it_behaves_like "it has a valid config"
end end
end end
describe 'with web config' do describe "with web config" do
let(:config) do let :config do
{ {
'web' => { "web" => {
'client_id' => 'abc@example.com', "client_id" => "abc@example.com",
'client_secret' => 'notasecret' "client_secret" => "notasecret"
} }
} }
end end
it_behaves_like 'it can successfully load client_id' it_behaves_like "it can successfully load client_id"
end end
describe 'with installed app config' do describe "with installed app config" do
let(:config) do let :config do
{ {
'installed' => { "installed" => {
'client_id' => 'abc@example.com', "client_id" => "abc@example.com",
'client_secret' => 'notasecret' "client_secret" => "notasecret"
} }
} }
end end
it_behaves_like 'it can successfully load client_id' it_behaves_like "it can successfully load client_id"
end end
context 'with missing top level property' do context "with missing top level property" do
let(:config) do let :config do
{ {
'notvalid' => { "notvalid" => {
'client_id' => 'abc@example.com', "client_id" => "abc@example.com",
'client_secret' => 'notasecret' "client_secret" => "notasecret"
} }
} }
end end
it 'should raise error' do it "should raise error" do
expect { Google::Auth::ClientId.from_hash config }.to raise_error( expect { Google::Auth::ClientId.from_hash config }.to raise_error(
/Expected top level property/ /Expected top level property/
) )
end end
end end
context 'with missing client id' do context "with missing client id" do
let(:config) do let :config do
{ {
'web' => { "web" => {
'client_secret' => 'notasecret' "client_secret" => "notasecret"
} }
} }
end end
it 'should raise error' do it "should raise error" do
expect { Google::Auth::ClientId.from_hash config }.to raise_error( expect { Google::Auth::ClientId.from_hash config }.to raise_error(
/Client id can not be nil/ /Client id can not be nil/
) )
end end
end end
context 'with missing client secret' do context "with missing client secret" do
let(:config) do let :config do
{ {
'web' => { "web" => {
'client_id' => 'abc@example.com' "client_id" => "abc@example.com"
} }
} }
end end
it 'should raise error' do it "should raise error" do
expect { Google::Auth::ClientId.from_hash config }.to raise_error( expect { Google::Auth::ClientId.from_hash config }.to raise_error(
/Client secret can not be nil/ /Client secret can not be nil/
) )
end end
end end
context 'with cloud sdk credentials' do context "with cloud sdk credentials" do
let(:config) do let :config do
{ {
'web' => { "web" => {
'client_id' => Google::Auth::CredentialsLoader::CLOUD_SDK_CLIENT_ID, "client_id" => Google::Auth::CredentialsLoader::CLOUD_SDK_CLIENT_ID,
'client_secret' => 'notasecret' "client_secret" => "notasecret"
} }
} }
end end
it 'should raise warning' do it "should raise warning" do
expect { Google::Auth::ClientId.from_hash config }.to output( expect { Google::Auth::ClientId.from_hash config }.to output(
Google::Auth::CredentialsLoader::CLOUD_SDK_CREDENTIALS_WARNING + "\n" Google::Auth::CredentialsLoader::CLOUD_SDK_CREDENTIALS_WARNING + "\n"
).to_stderr ).to_stderr

View File

@ -27,65 +27,65 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'apply_auth_examples' require "apply_auth_examples"
require 'faraday' require "faraday"
require 'googleauth/compute_engine' require "googleauth/compute_engine"
require 'spec_helper' require "spec_helper"
describe Google::Auth::GCECredentials do describe Google::Auth::GCECredentials do
MD_URI = 'http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token'.freeze MD_URI = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
GCECredentials = Google::Auth::GCECredentials GCECredentials = Google::Auth::GCECredentials
before(:example) do before :example do
@client = GCECredentials.new @client = GCECredentials.new
end end
def make_auth_stubs(opts = {}) def make_auth_stubs opts = {}
access_token = opts[:access_token] || '' access_token = opts[:access_token] || ""
body = MultiJson.dump('access_token' => access_token, body = MultiJson.dump("access_token" => access_token,
'token_type' => 'Bearer', "token_type" => "Bearer",
'expires_in' => 3600) "expires_in" => 3600)
stub_request(:get, MD_URI) stub_request(:get, MD_URI)
.with(headers: { 'Metadata-Flavor' => 'Google' }) .with(headers: { "Metadata-Flavor" => "Google" })
.to_return(body: body, .to_return(body: body,
status: 200, status: 200,
headers: { 'Content-Type' => 'application/json' }) headers: { "Content-Type" => "application/json" })
end end
it_behaves_like 'apply/apply! are OK' it_behaves_like "apply/apply! are OK"
context 'metadata is unavailable' do context "metadata is unavailable" do
describe '#fetch_access_token' do describe "#fetch_access_token" do
it 'should fail if the metadata request returns a 404' do it "should fail if the metadata request returns a 404" do
stub = stub_request(:get, MD_URI) stub = stub_request(:get, MD_URI)
.to_return(status: 404, .to_return(status: 404,
headers: { 'Metadata-Flavor' => 'Google' }) headers: { "Metadata-Flavor" => "Google" })
expect { @client.fetch_access_token! } expect { @client.fetch_access_token! }
.to raise_error Signet::AuthorizationError .to raise_error Signet::AuthorizationError
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end
it 'should fail if the metadata request returns an unexpected code' do it "should fail if the metadata request returns an unexpected code" do
stub = stub_request(:get, MD_URI) stub = stub_request(:get, MD_URI)
.to_return(status: 503, .to_return(status: 503,
headers: { 'Metadata-Flavor' => 'Google' }) headers: { "Metadata-Flavor" => "Google" })
expect { @client.fetch_access_token! } expect { @client.fetch_access_token! }
.to raise_error Signet::AuthorizationError .to raise_error Signet::AuthorizationError
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end
it 'should fail with Signet::AuthorizationError if request times out' do it "should fail with Signet::AuthorizationError if request times out" do
allow_any_instance_of(Faraday::Connection).to receive(:get) allow_any_instance_of(Faraday::Connection).to receive(:get)
.and_raise(Faraday::TimeoutError) .and_raise(Faraday::TimeoutError)
expect { @client.fetch_access_token! } expect { @client.fetch_access_token! }
.to raise_error Signet::AuthorizationError .to raise_error Signet::AuthorizationError
end end
it 'should fail with Signet::AuthorizationError if request fails' do it "should fail with Signet::AuthorizationError if request fails" do
allow_any_instance_of(Faraday::Connection).to receive(:get) allow_any_instance_of(Faraday::Connection).to receive(:get)
.and_raise(Faraday::ConnectionFailed, nil) .and_raise(Faraday::ConnectionFailed, nil)
expect { @client.fetch_access_token! } expect { @client.fetch_access_token! }
@ -94,27 +94,27 @@ describe Google::Auth::GCECredentials do
end end
end end
describe '#on_gce?' do describe "#on_gce?" do
it 'should be true when Metadata-Flavor is Google' do it "should be true when Metadata-Flavor is Google" do
stub = stub_request(:get, 'http://169.254.169.254') stub = stub_request(:get, "http://169.254.169.254")
.to_return(status: 200, .to_return(status: 200,
headers: { 'Metadata-Flavor' => 'Google' }) headers: { "Metadata-Flavor" => "Google" })
expect(GCECredentials.on_gce?({}, true)).to eq(true) expect(GCECredentials.on_gce?({}, true)).to eq(true)
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end
it 'should be false when Metadata-Flavor is not Google' do it "should be false when Metadata-Flavor is not Google" do
stub = stub_request(:get, 'http://169.254.169.254') stub = stub_request(:get, "http://169.254.169.254")
.to_return(status: 200, .to_return(status: 200,
headers: { 'Metadata-Flavor' => 'NotGoogle' }) headers: { "Metadata-Flavor" => "NotGoogle" })
expect(GCECredentials.on_gce?({}, true)).to eq(false) expect(GCECredentials.on_gce?({}, true)).to eq(false)
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end
it 'should be false if the response is not 200' do it "should be false if the response is not 200" do
stub = stub_request(:get, 'http://169.254.169.254') stub = stub_request(:get, "http://169.254.169.254")
.to_return(status: 404, .to_return(status: 404,
headers: { 'Metadata-Flavor' => 'NotGoogle' }) headers: { "Metadata-Flavor" => "NotGoogle" })
expect(GCECredentials.on_gce?({}, true)).to eq(false) expect(GCECredentials.on_gce?({}, true)).to eq(false)
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end

View File

@ -27,34 +27,34 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'googleauth' require "googleauth"
# This test is testing the private class Google::Auth::Credentials. We want to # This test is testing the private class Google::Auth::Credentials. We want to
# make sure that the passed in scope propogates to the Signet object. This means # make sure that the passed in scope propogates to the Signet object. This means
# testing the private API, which is generally frowned on. # testing the private API, which is generally frowned on.
describe Google::Auth::Credentials, :private do describe Google::Auth::Credentials, :private do
let(:default_keyfile_hash) do let :default_keyfile_hash do
{ {
'private_key_id' => 'testabc1234567890xyz', "private_key_id" => "testabc1234567890xyz",
'private_key' => "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAOyi0Hy1l4Ym2m2o71Q0TF4O9E81isZEsX0bb+Bqz1SXEaSxLiXM\nUZE8wu0eEXivXuZg6QVCW/5l+f2+9UPrdNUCAwEAAQJAJkqubA/Chj3RSL92guy3\nktzeodarLyw8gF8pOmpuRGSiEo/OLTeRUMKKD1/kX4f9sxf3qDhB4e7dulXR1co/\nIQIhAPx8kMW4XTTL6lJYd2K5GrH8uBMp8qL5ya3/XHrBgw3dAiEA7+3Iw3ULTn2I\n1J34WlJ2D5fbzMzB4FAHUNEV7Ys3f1kCIQDtUahCMChrl7+H5t9QS+xrn77lRGhs\nB50pjvy95WXpgQIhAI2joW6JzTfz8fAapb+kiJ/h9Vcs1ZN3iyoRlNFb61JZAiA8\nNy5NyNrMVwtB/lfJf1dAK/p/Bwd8LZLtgM6PapRfgw==\n-----END RSA PRIVATE KEY-----\n", "private_key" => "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAOyi0Hy1l4Ym2m2o71Q0TF4O9E81isZEsX0bb+Bqz1SXEaSxLiXM\nUZE8wu0eEXivXuZg6QVCW/5l+f2+9UPrdNUCAwEAAQJAJkqubA/Chj3RSL92guy3\nktzeodarLyw8gF8pOmpuRGSiEo/OLTeRUMKKD1/kX4f9sxf3qDhB4e7dulXR1co/\nIQIhAPx8kMW4XTTL6lJYd2K5GrH8uBMp8qL5ya3/XHrBgw3dAiEA7+3Iw3ULTn2I\n1J34WlJ2D5fbzMzB4FAHUNEV7Ys3f1kCIQDtUahCMChrl7+H5t9QS+xrn77lRGhs\nB50pjvy95WXpgQIhAI2joW6JzTfz8fAapb+kiJ/h9Vcs1ZN3iyoRlNFb61JZAiA8\nNy5NyNrMVwtB/lfJf1dAK/p/Bwd8LZLtgM6PapRfgw==\n-----END RSA PRIVATE KEY-----\n",
'client_email' => 'credz-testabc1234567890xyz@developer.gserviceaccount.com', "client_email" => "credz-testabc1234567890xyz@developer.gserviceaccount.com",
'client_id' => 'credz-testabc1234567890xyz.apps.googleusercontent.com', "client_id" => "credz-testabc1234567890xyz.apps.googleusercontent.com",
'type' => 'service_account', "type" => "service_account",
'project_id' => 'a_project_id' "project_id" => "a_project_id"
} }
end end
it 'uses a default scope' do it "uses a default scope" do
mocked_signet = double('Signet::OAuth2::Client') mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet) allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true) allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
allow(mocked_signet).to receive(:client_id) allow(mocked_signet).to receive(:client_id)
allow(Signet::OAuth2::Client).to receive(:new) do |options| allow(Signet::OAuth2::Client).to receive(:new) do |options|
expect(options[:token_credential_uri]).to eq('https://oauth2.googleapis.com/token') expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq('https://oauth2.googleapis.com/token') expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq([]) expect(options[:scope]).to eq([])
expect(options[:issuer]).to eq(default_keyfile_hash['client_email']) expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA) expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet mocked_signet
@ -63,49 +63,49 @@ describe Google::Auth::Credentials, :private do
Google::Auth::Credentials.new default_keyfile_hash Google::Auth::Credentials.new default_keyfile_hash
end end
it 'uses a custom scope' do it "uses a custom scope" do
mocked_signet = double('Signet::OAuth2::Client') mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet) allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true) allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
allow(mocked_signet).to receive(:client_id) allow(mocked_signet).to receive(:client_id)
allow(Signet::OAuth2::Client).to receive(:new) do |options| allow(Signet::OAuth2::Client).to receive(:new) do |options|
expect(options[:token_credential_uri]).to eq('https://oauth2.googleapis.com/token') expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq('https://oauth2.googleapis.com/token') expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(['http://example.com/scope']) expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash['client_email']) expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA) expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet mocked_signet
end end
Google::Auth::Credentials.new default_keyfile_hash, scope: 'http://example.com/scope' Google::Auth::Credentials.new default_keyfile_hash, scope: "http://example.com/scope"
end end
it 'can be subclassed to pass in other env paths' do it "can be subclassed to pass in other env paths" do
TEST_PATH_ENV_VAR = 'TEST_PATH'.freeze TEST_PATH_ENV_VAR = "TEST_PATH".freeze
TEST_PATH_ENV_VAL = '/unknown/path/to/file.txt'.freeze TEST_PATH_ENV_VAL = "/unknown/path/to/file.txt".freeze
TEST_JSON_ENV_VAR = 'TEST_JSON_VARS'.freeze TEST_JSON_ENV_VAR = "TEST_JSON_VARS".freeze
ENV[TEST_PATH_ENV_VAR] = TEST_PATH_ENV_VAL ENV[TEST_PATH_ENV_VAR] = TEST_PATH_ENV_VAL
ENV[TEST_JSON_ENV_VAR] = JSON.generate(default_keyfile_hash) ENV[TEST_JSON_ENV_VAR] = JSON.generate default_keyfile_hash
class TestCredentials < Google::Auth::Credentials class TestCredentials < Google::Auth::Credentials
SCOPE = 'http://example.com/scope'.freeze SCOPE = "http://example.com/scope".freeze
PATH_ENV_VARS = [TEST_PATH_ENV_VAR].freeze PATH_ENV_VARS = [TEST_PATH_ENV_VAR].freeze
JSON_ENV_VARS = [TEST_JSON_ENV_VAR].freeze JSON_ENV_VARS = [TEST_JSON_ENV_VAR].freeze
end end
allow(::File).to receive(:file?).with(TEST_PATH_ENV_VAL) { false } allow(::File).to receive(:file?).with(TEST_PATH_ENV_VAL) { false }
mocked_signet = double('Signet::OAuth2::Client') mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet) allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true) allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
allow(mocked_signet).to receive(:client_id) allow(mocked_signet).to receive(:client_id)
allow(Signet::OAuth2::Client).to receive(:new) do |options| allow(Signet::OAuth2::Client).to receive(:new) do |options|
expect(options[:token_credential_uri]).to eq('https://oauth2.googleapis.com/token') expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq('https://oauth2.googleapis.com/token') expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(['http://example.com/scope']) expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash['client_email']) expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA) expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet mocked_signet
@ -114,32 +114,32 @@ describe Google::Auth::Credentials, :private do
creds = TestCredentials.default creds = TestCredentials.default
expect(creds).to be_a_kind_of(TestCredentials) expect(creds).to be_a_kind_of(TestCredentials)
expect(creds.client).to eq(mocked_signet) expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash['project_id']) expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
end end
it 'subclasses can use PATH_ENV_VARS to get keyfile path' do it "subclasses can use PATH_ENV_VARS to get keyfile path" do
class TestCredentials < Google::Auth::Credentials class TestCredentials < Google::Auth::Credentials
SCOPE = 'http://example.com/scope'.freeze SCOPE = "http://example.com/scope".freeze
PATH_ENV_VARS = ['PATH_ENV_DUMMY', 'PATH_ENV_TEST'].freeze PATH_ENV_VARS = %w[PATH_ENV_DUMMY PATH_ENV_TEST].freeze
JSON_ENV_VARS = ['JSON_ENV_DUMMY'].freeze JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
DEFAULT_PATHS = ['~/default/path/to/file.txt'].freeze DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
end end
allow(::ENV).to receive(:[]).with('PATH_ENV_DUMMY') { '/fake/path/to/file.txt' } allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
allow(::File).to receive(:file?).with('/fake/path/to/file.txt') { false } allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
allow(::ENV).to receive(:[]).with('PATH_ENV_TEST') { '/unknown/path/to/file.txt' } allow(::ENV).to receive(:[]).with("PATH_ENV_TEST") { "/unknown/path/to/file.txt" }
allow(::File).to receive(:file?).with('/unknown/path/to/file.txt') { true } allow(::File).to receive(:file?).with("/unknown/path/to/file.txt") { true }
allow(::File).to receive(:read).with('/unknown/path/to/file.txt') { JSON.generate(default_keyfile_hash) } allow(::File).to receive(:read).with("/unknown/path/to/file.txt") { JSON.generate default_keyfile_hash }
mocked_signet = double('Signet::OAuth2::Client') mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet) allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true) allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
allow(mocked_signet).to receive(:client_id) allow(mocked_signet).to receive(:client_id)
allow(Signet::OAuth2::Client).to receive(:new) do |options| allow(Signet::OAuth2::Client).to receive(:new) do |options|
expect(options[:token_credential_uri]).to eq('https://oauth2.googleapis.com/token') expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq('https://oauth2.googleapis.com/token') expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(['http://example.com/scope']) expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash['client_email']) expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA) expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet mocked_signet
@ -148,31 +148,31 @@ describe Google::Auth::Credentials, :private do
creds = TestCredentials.default creds = TestCredentials.default
expect(creds).to be_a_kind_of(TestCredentials) expect(creds).to be_a_kind_of(TestCredentials)
expect(creds.client).to eq(mocked_signet) expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash['project_id']) expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
end end
it 'subclasses can use JSON_ENV_VARS to get keyfile contents' do it "subclasses can use JSON_ENV_VARS to get keyfile contents" do
class TestCredentials < Google::Auth::Credentials class TestCredentials < Google::Auth::Credentials
SCOPE = 'http://example.com/scope'.freeze SCOPE = "http://example.com/scope".freeze
PATH_ENV_VARS = ['PATH_ENV_DUMMY'].freeze PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
JSON_ENV_VARS = ['JSON_ENV_DUMMY', 'JSON_ENV_TEST'].freeze JSON_ENV_VARS = %w[JSON_ENV_DUMMY JSON_ENV_TEST].freeze
DEFAULT_PATHS = ['~/default/path/to/file.txt'].freeze DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
end end
allow(::ENV).to receive(:[]).with('PATH_ENV_DUMMY') { '/fake/path/to/file.txt' } allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
allow(::File).to receive(:file?).with('/fake/path/to/file.txt') { false } allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
allow(::ENV).to receive(:[]).with('JSON_ENV_DUMMY') { nil } allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
allow(::ENV).to receive(:[]).with('JSON_ENV_TEST') { JSON.generate(default_keyfile_hash) } allow(::ENV).to receive(:[]).with("JSON_ENV_TEST") { JSON.generate default_keyfile_hash }
mocked_signet = double('Signet::OAuth2::Client') mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet) allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true) allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
allow(mocked_signet).to receive(:client_id) allow(mocked_signet).to receive(:client_id)
allow(Signet::OAuth2::Client).to receive(:new) do |options| allow(Signet::OAuth2::Client).to receive(:new) do |options|
expect(options[:token_credential_uri]).to eq('https://oauth2.googleapis.com/token') expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq('https://oauth2.googleapis.com/token') expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(['http://example.com/scope']) expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash['client_email']) expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA) expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet mocked_signet
@ -181,32 +181,32 @@ describe Google::Auth::Credentials, :private do
creds = TestCredentials.default creds = TestCredentials.default
expect(creds).to be_a_kind_of(TestCredentials) expect(creds).to be_a_kind_of(TestCredentials)
expect(creds.client).to eq(mocked_signet) expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash['project_id']) expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
end end
it 'subclasses can use DEFAULT_PATHS to get keyfile path' do it "subclasses can use DEFAULT_PATHS to get keyfile path" do
class TestCredentials < Google::Auth::Credentials class TestCredentials < Google::Auth::Credentials
SCOPE = 'http://example.com/scope'.freeze SCOPE = "http://example.com/scope".freeze
PATH_ENV_VARS = ['PATH_ENV_DUMMY'].freeze PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
JSON_ENV_VARS = ['JSON_ENV_DUMMY'].freeze JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
DEFAULT_PATHS = ['~/default/path/to/file.txt'].freeze DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
end end
allow(::ENV).to receive(:[]).with('PATH_ENV_DUMMY') { '/fake/path/to/file.txt' } allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
allow(::File).to receive(:file?).with('/fake/path/to/file.txt') { false } allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
allow(::ENV).to receive(:[]).with('JSON_ENV_DUMMY') { nil } allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
allow(::File).to receive(:file?).with('~/default/path/to/file.txt') { true } allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { true }
allow(::File).to receive(:read).with('~/default/path/to/file.txt') { JSON.generate(default_keyfile_hash) } allow(::File).to receive(:read).with("~/default/path/to/file.txt") { JSON.generate default_keyfile_hash }
mocked_signet = double('Signet::OAuth2::Client') mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet) allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true) allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
allow(mocked_signet).to receive(:client_id) allow(mocked_signet).to receive(:client_id)
allow(Signet::OAuth2::Client).to receive(:new) do |options| allow(Signet::OAuth2::Client).to receive(:new) do |options|
expect(options[:token_credential_uri]).to eq('https://oauth2.googleapis.com/token') expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq('https://oauth2.googleapis.com/token') expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(['http://example.com/scope']) expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash['client_email']) expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA) expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet mocked_signet
@ -215,23 +215,23 @@ describe Google::Auth::Credentials, :private do
creds = TestCredentials.default creds = TestCredentials.default
expect(creds).to be_a_kind_of(TestCredentials) expect(creds).to be_a_kind_of(TestCredentials)
expect(creds.client).to eq(mocked_signet) expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash['project_id']) expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
end end
it 'subclasses that find no matches default to Google::Auth.get_application_default' do it "subclasses that find no matches default to Google::Auth.get_application_default" do
class TestCredentials < Google::Auth::Credentials class TestCredentials < Google::Auth::Credentials
SCOPE = 'http://example.com/scope'.freeze SCOPE = "http://example.com/scope".freeze
PATH_ENV_VARS = ['PATH_ENV_DUMMY'].freeze PATH_ENV_VARS = ["PATH_ENV_DUMMY"].freeze
JSON_ENV_VARS = ['JSON_ENV_DUMMY'].freeze JSON_ENV_VARS = ["JSON_ENV_DUMMY"].freeze
DEFAULT_PATHS = ['~/default/path/to/file.txt'].freeze DEFAULT_PATHS = ["~/default/path/to/file.txt"].freeze
end end
allow(::ENV).to receive(:[]).with('PATH_ENV_DUMMY') { '/fake/path/to/file.txt' } allow(::ENV).to receive(:[]).with("PATH_ENV_DUMMY") { "/fake/path/to/file.txt" }
allow(::File).to receive(:file?).with('/fake/path/to/file.txt') { false } allow(::File).to receive(:file?).with("/fake/path/to/file.txt") { false }
allow(::ENV).to receive(:[]).with('JSON_ENV_DUMMY') { nil } allow(::ENV).to receive(:[]).with("JSON_ENV_DUMMY") { nil }
allow(::File).to receive(:file?).with('~/default/path/to/file.txt') { false } allow(::File).to receive(:file?).with("~/default/path/to/file.txt") { false }
mocked_signet = double('Signet::OAuth2::Client') mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet) allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true) allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
allow(mocked_signet).to receive(:client_id) allow(mocked_signet).to receive(:client_id)
@ -243,10 +243,10 @@ describe Google::Auth::Credentials, :private do
default_keyfile_hash default_keyfile_hash
end end
allow(Signet::OAuth2::Client).to receive(:new) do |options| allow(Signet::OAuth2::Client).to receive(:new) do |options|
expect(options[:token_credential_uri]).to eq('https://oauth2.googleapis.com/token') expect(options[:token_credential_uri]).to eq("https://oauth2.googleapis.com/token")
expect(options[:audience]).to eq('https://oauth2.googleapis.com/token') expect(options[:audience]).to eq("https://oauth2.googleapis.com/token")
expect(options[:scope]).to eq(['http://example.com/scope']) expect(options[:scope]).to eq(["http://example.com/scope"])
expect(options[:issuer]).to eq(default_keyfile_hash['client_email']) expect(options[:issuer]).to eq(default_keyfile_hash["client_email"])
expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA) expect(options[:signing_key]).to be_a_kind_of(OpenSSL::PKey::RSA)
mocked_signet mocked_signet
@ -255,14 +255,14 @@ describe Google::Auth::Credentials, :private do
creds = TestCredentials.default creds = TestCredentials.default
expect(creds).to be_a_kind_of(TestCredentials) expect(creds).to be_a_kind_of(TestCredentials)
expect(creds.client).to eq(mocked_signet) expect(creds.client).to eq(mocked_signet)
expect(creds.project_id).to eq(default_keyfile_hash['project_id']) expect(creds.project_id).to eq(default_keyfile_hash["project_id"])
end end
it 'warns when cloud sdk credentials are used' do it "warns when cloud sdk credentials are used" do
mocked_signet = double('Signet::OAuth2::Client') mocked_signet = double "Signet::OAuth2::Client"
allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet) allow(mocked_signet).to receive(:configure_connection).and_return(mocked_signet)
allow(mocked_signet).to receive(:fetch_access_token!).and_return(true) allow(mocked_signet).to receive(:fetch_access_token!).and_return(true)
allow(Signet::OAuth2::Client).to receive(:new) do |options| allow(Signet::OAuth2::Client).to receive(:new) do |_options|
mocked_signet mocked_signet
end end
allow(mocked_signet).to receive(:client_id).and_return(Google::Auth::CredentialsLoader::CLOUD_SDK_CLIENT_ID) allow(mocked_signet).to receive(:client_id).and_return(Google::Auth::CredentialsLoader::CLOUD_SDK_CLIENT_ID)

View File

@ -27,22 +27,22 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'faraday' require "faraday"
require 'fakefs/safe' require "fakefs/safe"
require 'googleauth' require "googleauth"
require 'spec_helper' require "spec_helper"
require 'os' require "os"
describe '#get_application_default' do describe "#get_application_default" do
# Pass unique options each time to bypass memoization # Pass unique options each time to bypass memoization
let(:options) { |example| { dememoize: example } } let(:options) { |example| { dememoize: example } }
before(:example) do before :example do
@key = OpenSSL::PKey::RSA.new(2048) @key = OpenSSL::PKey::RSA.new 2048
@var_name = ENV_VAR @var_name = ENV_VAR
@credential_vars = [ @credential_vars = [
ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, CLIENT_ID_VAR, ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, CLIENT_ID_VAR,
@ -50,36 +50,36 @@ describe '#get_application_default' do
] ]
@original_env_vals = {} @original_env_vals = {}
@credential_vars.each { |var| @original_env_vals[var] = ENV[var] } @credential_vars.each { |var| @original_env_vals[var] = ENV[var] }
@home = ENV['HOME'] @home = ENV["HOME"]
@app_data = ENV['APPDATA'] @app_data = ENV["APPDATA"]
@program_data = ENV['ProgramData'] @program_data = ENV["ProgramData"]
@scope = 'https://www.googleapis.com/auth/userinfo.profile' @scope = "https://www.googleapis.com/auth/userinfo.profile"
end end
after(:example) do after :example do
@credential_vars.each { |var| ENV[var] = @original_env_vals[var] } @credential_vars.each { |var| ENV[var] = @original_env_vals[var] }
ENV['HOME'] = @home unless @home == ENV['HOME'] ENV["HOME"] = @home unless @home == ENV["HOME"]
ENV['APPDATA'] = @app_data unless @app_data == ENV['APPDATA'] ENV["APPDATA"] = @app_data unless @app_data == ENV["APPDATA"]
ENV['ProgramData'] = @program_data unless @program_data == ENV['ProgramData'] ENV["ProgramData"] = @program_data unless @program_data == ENV["ProgramData"]
end end
shared_examples 'it cannot load misconfigured credentials' do shared_examples "it cannot load misconfigured credentials" do
it 'fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist' do it "fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, 'does-not-exist') key_path = File.join dir, "does-not-exist"
ENV[@var_name] = key_path ENV[@var_name] = key_path
expect { Google::Auth.get_application_default @scope, options } expect { Google::Auth.get_application_default @scope, options }
.to raise_error RuntimeError .to raise_error RuntimeError
end end
end end
it 'fails without default file or env if not on compute engine' do it "fails without default file or env if not on compute engine" do
stub = stub_request(:get, 'http://169.254.169.254') stub = stub_request(:get, "http://169.254.169.254")
.to_return(status: 404, .to_return(status: 404,
headers: { 'Metadata-Flavor' => 'NotGoogle' }) headers: { "Metadata-Flavor" => "NotGoogle" })
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
ENV.delete(@var_name) unless ENV[@var_name].nil? # no env var ENV.delete @var_name unless ENV[@var_name].nil? # no env var
ENV['HOME'] = dir # no config present in this tmp dir ENV["HOME"] = dir # no config present in this tmp dir
expect do expect do
Google::Auth.get_application_default @scope, options Google::Auth.get_application_default @scope, options
end.to raise_error RuntimeError end.to raise_error RuntimeError
@ -88,12 +88,12 @@ describe '#get_application_default' do
end end
end end
shared_examples 'it can successfully load credentials' do shared_examples "it can successfully load credentials" do
it 'succeeds if the GOOGLE_APPLICATION_CREDENTIALS file is valid' do it "succeeds if the GOOGLE_APPLICATION_CREDENTIALS file is valid" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, 'my_cert_file') key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV[@var_name] = key_path ENV[@var_name] = key_path
expect(Google::Auth.get_application_default(@scope, options)) expect(Google::Auth.get_application_default(@scope, options))
.to_not be_nil .to_not be_nil
@ -102,73 +102,73 @@ describe '#get_application_default' do
it "propagates default_connection option" do it "propagates default_connection option" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, 'my_cert_file') key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV[@var_name] = key_path ENV[@var_name] = key_path
connection = Faraday.new(headers: {"User-Agent" => "hello"}) connection = Faraday.new headers: { "User-Agent" => "hello" }
opts = options.merge(default_connection: connection) opts = options.merge default_connection: connection
creds = Google::Auth.get_application_default(@scope, opts) creds = Google::Auth.get_application_default @scope, opts
expect(creds.build_default_connection).to be connection expect(creds.build_default_connection).to be connection
end end
end end
it 'succeeds with default file without GOOGLE_APPLICATION_CREDENTIALS' do it "succeeds with default file without GOOGLE_APPLICATION_CREDENTIALS" do
ENV.delete(@var_name) unless ENV[@var_name].nil? ENV.delete @var_name unless ENV[@var_name].nil?
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', WELL_KNOWN_PATH) key_path = File.join dir, ".config", WELL_KNOWN_PATH
key_path = File.join(dir, WELL_KNOWN_PATH) if OS.windows? key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV['HOME'] = dir ENV["HOME"] = dir
ENV['APPDATA'] = dir ENV["APPDATA"] = dir
expect(Google::Auth.get_application_default(@scope, options)) expect(Google::Auth.get_application_default(@scope, options))
.to_not be_nil .to_not be_nil
end end
end end
it 'succeeds with default file without a scope' do it "succeeds with default file without a scope" do
ENV.delete(@var_name) unless ENV[@var_name].nil? ENV.delete @var_name unless ENV[@var_name].nil?
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', WELL_KNOWN_PATH) key_path = File.join dir, ".config", WELL_KNOWN_PATH
key_path = File.join(dir, WELL_KNOWN_PATH) if OS.windows? key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV['HOME'] = dir ENV["HOME"] = dir
ENV['APPDATA'] = dir ENV["APPDATA"] = dir
expect(Google::Auth.get_application_default(nil, options)).to_not be_nil expect(Google::Auth.get_application_default(nil, options)).to_not be_nil
end end
end end
it 'succeeds without default file or env if on compute engine' do it "succeeds without default file or env if on compute engine" do
stub = stub_request(:get, 'http://169.254.169.254') stub = stub_request(:get, "http://169.254.169.254")
.to_return(status: 200, .to_return(status: 200,
headers: { 'Metadata-Flavor' => 'Google' }) headers: { "Metadata-Flavor" => "Google" })
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
ENV.delete(@var_name) unless ENV[@var_name].nil? # no env var ENV.delete @var_name unless ENV[@var_name].nil? # no env var
ENV['HOME'] = dir # no config present in this tmp dir ENV["HOME"] = dir # no config present in this tmp dir
creds = Google::Auth.get_application_default @scope, options creds = Google::Auth.get_application_default @scope, options
expect(creds).to_not be_nil expect(creds).to_not be_nil
end end
expect(stub).to have_been_requested expect(stub).to have_been_requested
end end
it 'succeeds with system default file' do it "succeeds with system default file" do
ENV.delete(@var_name) unless ENV[@var_name].nil? ENV.delete @var_name unless ENV[@var_name].nil?
FakeFS do FakeFS do
ENV['ProgramData'] = '/etc' ENV["ProgramData"] = "/etc"
prefix = OS.windows? ? '/etc/Google/Auth/' : '/etc/google/auth/' prefix = OS.windows? ? "/etc/Google/Auth/" : "/etc/google/auth/"
key_path = File.join(prefix, CREDENTIALS_FILE_NAME) key_path = File.join prefix, CREDENTIALS_FILE_NAME
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
expect(Google::Auth.get_application_default(@scope, options)) expect(Google::Auth.get_application_default(@scope, options))
.to_not be_nil .to_not be_nil
File.delete(key_path) File.delete key_path
end end
end end
it 'succeeds if environment vars are valid' do it "succeeds if environment vars are valid" do
ENV.delete(@var_name) unless ENV[@var_name].nil? # no env var ENV.delete @var_name unless ENV[@var_name].nil? # no env var
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
ENV[CLIENT_ID_VAR] = cred_json[:client_id] ENV[CLIENT_ID_VAR] = cred_json[:client_id]
@ -179,79 +179,79 @@ describe '#get_application_default' do
.to_not be_nil .to_not be_nil
end end
it 'warns when using cloud sdk credentials' do it "warns when using cloud sdk credentials" do
ENV.delete(@var_name) unless ENV[@var_name].nil? # no env var ENV.delete @var_name unless ENV[@var_name].nil? # no env var
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
ENV[CLIENT_ID_VAR] = Google::Auth::CredentialsLoader::CLOUD_SDK_CLIENT_ID ENV[CLIENT_ID_VAR] = Google::Auth::CredentialsLoader::CLOUD_SDK_CLIENT_ID
ENV[CLIENT_SECRET_VAR] = cred_json[:client_secret] ENV[CLIENT_SECRET_VAR] = cred_json[:client_secret]
ENV[REFRESH_TOKEN_VAR] = cred_json[:refresh_token] ENV[REFRESH_TOKEN_VAR] = cred_json[:refresh_token]
ENV[ACCOUNT_TYPE_VAR] = cred_json[:type] ENV[ACCOUNT_TYPE_VAR] = cred_json[:type]
ENV[PROJECT_ID_VAR] = 'a_project_id' ENV[PROJECT_ID_VAR] = "a_project_id"
expect { Google::Auth.get_application_default @scope, options }.to output( expect { Google::Auth.get_application_default @scope, options }.to output(
Google::Auth::CredentialsLoader::CLOUD_SDK_CREDENTIALS_WARNING + "\n" Google::Auth::CredentialsLoader::CLOUD_SDK_CREDENTIALS_WARNING + "\n"
).to_stderr ).to_stderr
end end
end end
describe 'when credential type is service account' do describe "when credential type is service account" do
let(:cred_json) do let :cred_json do
{ {
private_key_id: 'a_private_key_id', private_key_id: "a_private_key_id",
private_key: @key.to_pem, private_key: @key.to_pem,
client_email: 'app@developer.gserviceaccount.com', client_email: "app@developer.gserviceaccount.com",
client_id: 'app.apps.googleusercontent.com', client_id: "app.apps.googleusercontent.com",
type: 'service_account' type: "service_account"
} }
end end
def cred_json_text def cred_json_text
MultiJson.dump(cred_json) MultiJson.dump cred_json
end end
it_behaves_like 'it can successfully load credentials' it_behaves_like "it can successfully load credentials"
it_behaves_like 'it cannot load misconfigured credentials' it_behaves_like "it cannot load misconfigured credentials"
end end
describe 'when credential type is authorized_user' do describe "when credential type is authorized_user" do
let(:cred_json) do let :cred_json do
{ {
client_secret: 'privatekey', client_secret: "privatekey",
refresh_token: 'refreshtoken', refresh_token: "refreshtoken",
client_id: 'app.apps.googleusercontent.com', client_id: "app.apps.googleusercontent.com",
type: 'authorized_user' type: "authorized_user"
} }
end end
def cred_json_text def cred_json_text
MultiJson.dump(cred_json) MultiJson.dump cred_json
end end
it_behaves_like 'it can successfully load credentials' it_behaves_like "it can successfully load credentials"
it_behaves_like 'it cannot load misconfigured credentials' it_behaves_like "it cannot load misconfigured credentials"
end end
describe 'when credential type is unknown' do describe "when credential type is unknown" do
let(:cred_json) do let :cred_json do
{ {
client_secret: 'privatekey', client_secret: "privatekey",
refresh_token: 'refreshtoken', refresh_token: "refreshtoken",
client_id: 'app.apps.googleusercontent.com', client_id: "app.apps.googleusercontent.com",
private_key: @key.to_pem, private_key: @key.to_pem,
client_email: 'app@developer.gserviceaccount.com', client_email: "app@developer.gserviceaccount.com",
type: 'not_known_type' type: "not_known_type"
} }
end end
def cred_json_text def cred_json_text
MultiJson.dump(cred_json) MultiJson.dump cred_json
end end
it 'fails if the GOOGLE_APPLICATION_CREDENTIALS file contains the creds' do it "fails if the GOOGLE_APPLICATION_CREDENTIALS file contains the creds" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, 'my_cert_file') key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV[@var_name] = key_path ENV[@var_name] = key_path
expect do expect do
Google::Auth.get_application_default @scope, options Google::Auth.get_application_default @scope, options
@ -259,22 +259,22 @@ describe '#get_application_default' do
end end
end end
it 'fails if the well known file contains the creds' do it "fails if the well known file contains the creds" do
ENV.delete(@var_name) unless ENV[@var_name].nil? ENV.delete @var_name unless ENV[@var_name].nil?
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', WELL_KNOWN_PATH) key_path = File.join dir, ".config", WELL_KNOWN_PATH
key_path = File.join(dir, WELL_KNOWN_PATH) if OS.windows? key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV['HOME'] = dir ENV["HOME"] = dir
ENV['APPDATA'] = dir ENV["APPDATA"] = dir
expect do expect do
Google::Auth.get_application_default @scope, options Google::Auth.get_application_default @scope, options
end.to raise_error RuntimeError end.to raise_error RuntimeError
end end
end end
it 'fails if env vars are set' do it "fails if env vars are set" do
ENV[ENV_VAR] = nil ENV[ENV_VAR] = nil
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]

View File

@ -27,54 +27,54 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'googleauth/iam' require "googleauth/iam"
describe Google::Auth::IAMCredentials do describe Google::Auth::IAMCredentials do
IAMCredentials = Google::Auth::IAMCredentials IAMCredentials = Google::Auth::IAMCredentials
let(:test_selector) { 'the-test-selector' } let(:test_selector) { "the-test-selector" }
let(:test_token) { 'the-test-token' } let(:test_token) { "the-test-token" }
let(:test_creds) { IAMCredentials.new(test_selector, test_token) } let(:test_creds) { IAMCredentials.new test_selector, test_token }
describe '#apply!' do describe "#apply!" do
it 'should update the target hash with the iam values' do it "should update the target hash with the iam values" do
md = { foo: 'bar' } md = { foo: "bar" }
test_creds.apply!(md) test_creds.apply! md
expect(md[IAMCredentials::SELECTOR_KEY]).to eq test_selector expect(md[IAMCredentials::SELECTOR_KEY]).to eq test_selector
expect(md[IAMCredentials::TOKEN_KEY]).to eq test_token expect(md[IAMCredentials::TOKEN_KEY]).to eq test_token
expect(md[:foo]).to eq 'bar' expect(md[:foo]).to eq "bar"
end end
end end
describe 'updater_proc' do describe "updater_proc" do
it 'should provide a proc that updates a hash with the iam values' do it "should provide a proc that updates a hash with the iam values" do
md = { foo: 'bar' } md = { foo: "bar" }
the_proc = test_creds.updater_proc the_proc = test_creds.updater_proc
got = the_proc.call(md) got = the_proc.call md
expect(got[IAMCredentials::SELECTOR_KEY]).to eq test_selector expect(got[IAMCredentials::SELECTOR_KEY]).to eq test_selector
expect(got[IAMCredentials::TOKEN_KEY]).to eq test_token expect(got[IAMCredentials::TOKEN_KEY]).to eq test_token
expect(got[:foo]).to eq 'bar' expect(got[:foo]).to eq "bar"
end end
end end
describe '#apply' do describe "#apply" do
it 'should not update the original hash with the iam values' do it "should not update the original hash with the iam values" do
md = { foo: 'bar' } md = { foo: "bar" }
test_creds.apply(md) test_creds.apply md
expect(md[IAMCredentials::SELECTOR_KEY]).to be_nil expect(md[IAMCredentials::SELECTOR_KEY]).to be_nil
expect(md[IAMCredentials::TOKEN_KEY]).to be_nil expect(md[IAMCredentials::TOKEN_KEY]).to be_nil
expect(md[:foo]).to eq 'bar' expect(md[:foo]).to eq "bar"
end end
it 'should return a with the iam values' do it "should return a with the iam values" do
md = { foo: 'bar' } md = { foo: "bar" }
got = test_creds.apply(md) got = test_creds.apply md
expect(got[IAMCredentials::SELECTOR_KEY]).to eq test_selector expect(got[IAMCredentials::SELECTOR_KEY]).to eq test_selector
expect(got[IAMCredentials::TOKEN_KEY]).to eq test_token expect(got[IAMCredentials::TOKEN_KEY]).to eq test_token
expect(got[:foo]).to eq 'bar' expect(got[:foo]).to eq "bar"
end end
end end
end end

View File

@ -27,51 +27,51 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'googleauth/scope_util' require "googleauth/scope_util"
describe Google::Auth::ScopeUtil do describe Google::Auth::ScopeUtil do
shared_examples 'normalizes scopes' do shared_examples "normalizes scopes" do
let(:normalized) { Google::Auth::ScopeUtil.normalize(source) } let(:normalized) { Google::Auth::ScopeUtil.normalize source }
it 'normalizes the email scope' do it "normalizes the email scope" do
expect(normalized).to include( expect(normalized).to include(
'https://www.googleapis.com/auth/userinfo.email' "https://www.googleapis.com/auth/userinfo.email"
) )
expect(normalized).to_not include 'email' expect(normalized).to_not include "email"
end end
it 'normalizes the profile scope' do it "normalizes the profile scope" do
expect(normalized).to include( expect(normalized).to include(
'https://www.googleapis.com/auth/userinfo.profile' "https://www.googleapis.com/auth/userinfo.profile"
) )
expect(normalized).to_not include 'profile' expect(normalized).to_not include "profile"
end end
it 'normalizes the openid scope' do it "normalizes the openid scope" do
expect(normalized).to include 'https://www.googleapis.com/auth/plus.me' expect(normalized).to include "https://www.googleapis.com/auth/plus.me"
expect(normalized).to_not include 'openid' expect(normalized).to_not include "openid"
end end
it 'leaves other other scopes as-is' do it "leaves other other scopes as-is" do
expect(normalized).to include 'https://www.googleapis.com/auth/drive' expect(normalized).to include "https://www.googleapis.com/auth/drive"
end end
end end
context 'with scope as string' do context "with scope as string" do
let(:source) do let :source do
'email profile openid https://www.googleapis.com/auth/drive' "email profile openid https://www.googleapis.com/auth/drive"
end end
it_behaves_like 'normalizes scopes' it_behaves_like "normalizes scopes"
end end
context 'with scope as Array' do context "with scope as Array" do
let(:source) do let :source do
%w(email profile openid https://www.googleapis.com/auth/drive) %w[email profile openid https://www.googleapis.com/auth/drive]
end end
it_behaves_like 'normalizes scopes' it_behaves_like "normalizes scopes"
end end
end end

View File

@ -27,81 +27,81 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'apply_auth_examples' require "apply_auth_examples"
require 'fakefs/safe' require "fakefs/safe"
require 'fileutils' require "fileutils"
require 'googleauth/service_account' require "googleauth/service_account"
require 'jwt' require "jwt"
require 'multi_json' require "multi_json"
require 'openssl' require "openssl"
require 'spec_helper' require "spec_helper"
require 'tmpdir' require "tmpdir"
require 'os' require "os"
include Google::Auth::CredentialsLoader include Google::Auth::CredentialsLoader
shared_examples 'jwt header auth' do shared_examples "jwt header auth" do
context 'when jwt_aud_uri is present' do context "when jwt_aud_uri is present" do
let(:test_uri) { 'https://www.googleapis.com/myservice' } let(:test_uri) { "https://www.googleapis.com/myservice" }
let(:auth_prefix) { 'Bearer ' } let(:auth_prefix) { "Bearer " }
let(:auth_key) { ServiceAccountJwtHeaderCredentials::AUTH_METADATA_KEY } let(:auth_key) { ServiceAccountJwtHeaderCredentials::AUTH_METADATA_KEY }
let(:jwt_uri_key) { ServiceAccountJwtHeaderCredentials::JWT_AUD_URI_KEY } let(:jwt_uri_key) { ServiceAccountJwtHeaderCredentials::JWT_AUD_URI_KEY }
def expect_is_encoded_jwt(hdr) def expect_is_encoded_jwt hdr
expect(hdr).to_not be_nil expect(hdr).to_not be_nil
expect(hdr.start_with?(auth_prefix)).to be true expect(hdr.start_with?(auth_prefix)).to be true
authorization = hdr[auth_prefix.length..-1] authorization = hdr[auth_prefix.length..-1]
payload, = JWT.decode(authorization, @key.public_key, true, algorithm: 'RS256') payload, = JWT.decode authorization, @key.public_key, true, algorithm: "RS256"
expect(payload['aud']).to eq(test_uri) expect(payload["aud"]).to eq(test_uri)
expect(payload['iss']).to eq(client_email) expect(payload["iss"]).to eq(client_email)
end end
describe '#apply!' do describe "#apply!" do
it 'should update the target hash with a jwt token' do it "should update the target hash with a jwt token" do
md = { foo: 'bar' } md = { foo: "bar" }
md[jwt_uri_key] = test_uri md[jwt_uri_key] = test_uri
@client.apply!(md) @client.apply! md
auth_header = md[auth_key] auth_header = md[auth_key]
expect_is_encoded_jwt(auth_header) expect_is_encoded_jwt auth_header
expect(md[jwt_uri_key]).to be_nil expect(md[jwt_uri_key]).to be_nil
end end
end end
describe 'updater_proc' do describe "updater_proc" do
it 'should provide a proc that updates a hash with a jwt token' do it "should provide a proc that updates a hash with a jwt token" do
md = { foo: 'bar' } md = { foo: "bar" }
md[jwt_uri_key] = test_uri md[jwt_uri_key] = test_uri
the_proc = @client.updater_proc the_proc = @client.updater_proc
got = the_proc.call(md) got = the_proc.call md
auth_header = got[auth_key] auth_header = got[auth_key]
expect_is_encoded_jwt(auth_header) expect_is_encoded_jwt auth_header
expect(got[jwt_uri_key]).to be_nil expect(got[jwt_uri_key]).to be_nil
expect(md[jwt_uri_key]).to_not be_nil expect(md[jwt_uri_key]).to_not be_nil
end end
end end
describe '#apply' do describe "#apply" do
it 'should not update the original hash with a jwt token' do it "should not update the original hash with a jwt token" do
md = { foo: 'bar' } md = { foo: "bar" }
md[jwt_uri_key] = test_uri md[jwt_uri_key] = test_uri
the_proc = @client.updater_proc the_proc = @client.updater_proc
got = the_proc.call(md) got = the_proc.call md
auth_header = md[auth_key] auth_header = md[auth_key]
expect(auth_header).to be_nil expect(auth_header).to be_nil
expect(got[jwt_uri_key]).to be_nil expect(got[jwt_uri_key]).to be_nil
expect(md[jwt_uri_key]).to_not be_nil expect(md[jwt_uri_key]).to_not be_nil
end end
it 'should add a jwt token to the returned hash' do it "should add a jwt token to the returned hash" do
md = { foo: 'bar' } md = { foo: "bar" }
md[jwt_uri_key] = test_uri md[jwt_uri_key] = test_uri
got = @client.apply(md) got = @client.apply md
auth_header = got[auth_key] auth_header = got[auth_key]
expect_is_encoded_jwt(auth_header) expect_is_encoded_jwt auth_header
end end
end end
end end
@ -109,63 +109,63 @@ end
describe Google::Auth::ServiceAccountCredentials do describe Google::Auth::ServiceAccountCredentials do
ServiceAccountCredentials = Google::Auth::ServiceAccountCredentials ServiceAccountCredentials = Google::Auth::ServiceAccountCredentials
let(:client_email) { 'app@developer.gserviceaccount.com' } let(:client_email) { "app@developer.gserviceaccount.com" }
let(:cred_json) do let :cred_json do
{ {
private_key_id: 'a_private_key_id', private_key_id: "a_private_key_id",
private_key: @key.to_pem, private_key: @key.to_pem,
client_email: client_email, client_email: client_email,
client_id: 'app.apps.googleusercontent.com', client_id: "app.apps.googleusercontent.com",
type: 'service_account', type: "service_account",
project_id: 'a_project_id' project_id: "a_project_id"
} }
end end
before(:example) do before :example do
@key = OpenSSL::PKey::RSA.new(2048) @key = OpenSSL::PKey::RSA.new 2048
@client = ServiceAccountCredentials.make_creds( @client = ServiceAccountCredentials.make_creds(
json_key_io: StringIO.new(cred_json_text), json_key_io: StringIO.new(cred_json_text),
scope: 'https://www.googleapis.com/auth/userinfo.profile' scope: "https://www.googleapis.com/auth/userinfo.profile"
) )
end end
def make_auth_stubs(opts = {}) def make_auth_stubs opts = {}
access_token = opts[:access_token] || '' access_token = opts[:access_token] || ""
body = MultiJson.dump('access_token' => access_token, body = MultiJson.dump("access_token" => access_token,
'token_type' => 'Bearer', "token_type" => "Bearer",
'expires_in' => 3600) "expires_in" => 3600)
blk = proc do |request| blk = proc do |request|
params = Addressable::URI.form_unencode(request.body) params = Addressable::URI.form_unencode request.body
_claim, _header = JWT.decode(params.assoc('assertion').last, _claim, _header = JWT.decode(params.assoc("assertion").last,
@key.public_key, true, @key.public_key, true,
algorithm: 'RS256') algorithm: "RS256")
end end
stub_request(:post, 'https://www.googleapis.com/oauth2/v4/token') stub_request(:post, "https://www.googleapis.com/oauth2/v4/token")
.with(body: hash_including( .with(body: hash_including(
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer' "grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer"
), ),
&blk) &blk)
.to_return(body: body, .to_return(body: body,
status: 200, status: 200,
headers: { 'Content-Type' => 'application/json' }) headers: { "Content-Type" => "application/json" })
end end
def cred_json_text def cred_json_text
MultiJson.dump(cred_json) MultiJson.dump cred_json
end end
it_behaves_like 'apply/apply! are OK' it_behaves_like "apply/apply! are OK"
context 'when scope is nil' do context "when scope is nil" do
before(:example) do before :example do
@client.scope = nil @client.scope = nil
end end
it_behaves_like 'jwt header auth' it_behaves_like "jwt header auth"
end end
describe '#from_env' do describe "#from_env" do
before(:example) do before :example do
@var_name = ENV_VAR @var_name = ENV_VAR
@credential_vars = [ @credential_vars = [
ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, ACCOUNT_TYPE_VAR ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, ACCOUNT_TYPE_VAR
@ -174,56 +174,56 @@ describe Google::Auth::ServiceAccountCredentials do
@credential_vars.each { |var| @original_env_vals[var] = ENV[var] } @credential_vars.each { |var| @original_env_vals[var] = ENV[var] }
ENV[ACCOUNT_TYPE_VAR] = cred_json[:type] ENV[ACCOUNT_TYPE_VAR] = cred_json[:type]
@scope = 'https://www.googleapis.com/auth/userinfo.profile' @scope = "https://www.googleapis.com/auth/userinfo.profile"
@clz = ServiceAccountCredentials @clz = ServiceAccountCredentials
end end
after(:example) do after :example do
@credential_vars.each { |var| ENV[var] = @original_env_vals[var] } @credential_vars.each { |var| ENV[var] = @original_env_vals[var] }
end end
it 'returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset' do it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset" do
ENV.delete(@var_name) unless ENV[@var_name].nil? ENV.delete @var_name unless ENV[@var_name].nil?
expect(ServiceAccountCredentials.from_env(@scope)).to be_nil expect(ServiceAccountCredentials.from_env(@scope)).to be_nil
end end
it 'fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist' do it "fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist" do
ENV.delete(@var_name) unless ENV[@var_name].nil? ENV.delete @var_name unless ENV[@var_name].nil?
expect(ServiceAccountCredentials.from_env(@scope)).to be_nil expect(ServiceAccountCredentials.from_env(@scope)).to be_nil
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, 'does-not-exist') key_path = File.join dir, "does-not-exist"
ENV[@var_name] = key_path ENV[@var_name] = key_path
expect { @clz.from_env(@scope) }.to raise_error RuntimeError expect { @clz.from_env @scope }.to raise_error RuntimeError
end end
end end
it 'succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid' do it "succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, 'my_cert_file') key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV[@var_name] = key_path ENV[@var_name] = key_path
expect(@clz.from_env(@scope)).to_not be_nil expect(@clz.from_env(@scope)).to_not be_nil
end end
end end
it 'succeeds when GOOGLE_PRIVATE_KEY and GOOGLE_CLIENT_EMAIL env vars are'\ it "succeeds when GOOGLE_PRIVATE_KEY and GOOGLE_CLIENT_EMAIL env vars are"\
' valid' do " valid" do
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
expect(@clz.from_env(@scope)).to_not be_nil expect(@clz.from_env(@scope)).to_not be_nil
end end
it 'sets project_id when the PROJECT_ID_VAR env var is set' do it "sets project_id when the PROJECT_ID_VAR env var is set" do
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
ENV[PROJECT_ID_VAR] = cred_json[:project_id] ENV[PROJECT_ID_VAR] = cred_json[:project_id]
ENV[ENV_VAR] = nil ENV[ENV_VAR] = nil
credentials = @clz.from_env(@scope) credentials = @clz.from_env @scope
expect(credentials.project_id).to eq(cred_json[:project_id]) expect(credentials.project_id).to eq(cred_json[:project_id])
end end
it 'succeeds when GOOGLE_PRIVATE_KEY is escaped' do it "succeeds when GOOGLE_PRIVATE_KEY is escaped" do
escaped_key = cred_json[:private_key].gsub "\n", '\n' escaped_key = cred_json[:private_key].gsub "\n", '\n'
ENV[PRIVATE_KEY_VAR] = "\"#{escaped_key}\"" ENV[PRIVATE_KEY_VAR] = "\"#{escaped_key}\""
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
@ -233,110 +233,110 @@ describe Google::Auth::ServiceAccountCredentials do
it "propagates default_connection option" do it "propagates default_connection option" do
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
connection = Faraday.new(headers: {"User-Agent" => "hello"}) connection = Faraday.new headers: { "User-Agent" => "hello" }
creds = @clz.from_env(@scope, default_connection: connection) creds = @clz.from_env @scope, default_connection: connection
expect(creds.build_default_connection).to be connection expect(creds.build_default_connection).to be connection
end end
end end
describe '#from_well_known_path' do describe "#from_well_known_path" do
before(:example) do before :example do
@home = ENV['HOME'] @home = ENV["HOME"]
@app_data = ENV['APPDATA'] @app_data = ENV["APPDATA"]
@scope = 'https://www.googleapis.com/auth/userinfo.profile' @scope = "https://www.googleapis.com/auth/userinfo.profile"
@known_path = WELL_KNOWN_PATH @known_path = WELL_KNOWN_PATH
@clz = ServiceAccountCredentials @clz = ServiceAccountCredentials
end end
after(:example) do after :example do
ENV['HOME'] = @home unless @home == ENV['HOME'] ENV["HOME"] = @home unless @home == ENV["HOME"]
ENV['APPDATA'] = @app_data unless @app_data == ENV['APPDATA'] ENV["APPDATA"] = @app_data unless @app_data == ENV["APPDATA"]
end end
it 'is nil if no file exists' do it "is nil if no file exists" do
ENV['HOME'] = File.dirname(__FILE__) ENV["HOME"] = File.dirname __FILE__
expect(ServiceAccountCredentials.from_well_known_path(@scope)).to be_nil expect(ServiceAccountCredentials.from_well_known_path(@scope)).to be_nil
end end
it 'successfully loads the file when it is present' do it "successfully loads the file when it is present" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', @known_path) key_path = File.join dir, ".config", @known_path
key_path = File.join(dir, WELL_KNOWN_PATH) if OS.windows? key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV['HOME'] = dir ENV["HOME"] = dir
ENV['APPDATA'] = dir ENV["APPDATA"] = dir
expect(@clz.from_well_known_path(@scope)).to_not be_nil expect(@clz.from_well_known_path(@scope)).to_not be_nil
end end
end end
it 'successfully sets project_id when file is present' do it "successfully sets project_id when file is present" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', @known_path) key_path = File.join dir, ".config", @known_path
key_path = File.join(dir, WELL_KNOWN_PATH) if OS.windows? key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV['HOME'] = dir ENV["HOME"] = dir
ENV['APPDATA'] = dir ENV["APPDATA"] = dir
credentials = @clz.from_well_known_path(@scope) credentials = @clz.from_well_known_path @scope
expect(credentials.project_id).to eq(cred_json[:project_id]) expect(credentials.project_id).to eq(cred_json[:project_id])
end end
end end
it "propagates default_connection option" do it "propagates default_connection option" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', @known_path) key_path = File.join dir, ".config", @known_path
key_path = File.join(dir, WELL_KNOWN_PATH) if OS.windows? key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV['HOME'] = dir ENV["HOME"] = dir
ENV['APPDATA'] = dir ENV["APPDATA"] = dir
connection = Faraday.new(headers: {"User-Agent" => "hello"}) connection = Faraday.new headers: { "User-Agent" => "hello" }
creds = @clz.from_well_known_path(@scope, default_connection: connection) creds = @clz.from_well_known_path @scope, default_connection: connection
expect(creds.build_default_connection).to be connection expect(creds.build_default_connection).to be connection
end end
end end
end end
describe '#from_system_default_path' do describe "#from_system_default_path" do
before(:example) do before :example do
@scope = 'https://www.googleapis.com/auth/userinfo.profile' @scope = "https://www.googleapis.com/auth/userinfo.profile"
@program_data = ENV['ProgramData'] @program_data = ENV["ProgramData"]
@prefix = OS.windows? ? '/etc/Google/Auth/' : '/etc/google/auth/' @prefix = OS.windows? ? "/etc/Google/Auth/" : "/etc/google/auth/"
@path = File.join(@prefix, CREDENTIALS_FILE_NAME) @path = File.join @prefix, CREDENTIALS_FILE_NAME
@clz = ServiceAccountCredentials @clz = ServiceAccountCredentials
end end
after(:example) do after :example do
ENV['ProgramData'] = @program_data ENV["ProgramData"] = @program_data
end end
it 'is nil if no file exists' do it "is nil if no file exists" do
FakeFS do FakeFS do
expect(ServiceAccountCredentials.from_system_default_path(@scope)) expect(ServiceAccountCredentials.from_system_default_path(@scope))
.to be_nil .to be_nil
end end
end end
it 'successfully loads the file when it is present' do it "successfully loads the file when it is present" do
FakeFS do FakeFS do
ENV['ProgramData'] = '/etc' ENV["ProgramData"] = "/etc"
FileUtils.mkdir_p(File.dirname(@path)) FileUtils.mkdir_p File.dirname(@path)
File.write(@path, cred_json_text) File.write @path, cred_json_text
expect(@clz.from_system_default_path(@scope)).to_not be_nil expect(@clz.from_system_default_path(@scope)).to_not be_nil
File.delete(@path) File.delete @path
end end
end end
it "propagates default_connection option" do it "propagates default_connection option" do
FakeFS do FakeFS do
ENV['ProgramData'] = '/etc' ENV["ProgramData"] = "/etc"
FileUtils.mkdir_p(File.dirname(@path)) FileUtils.mkdir_p File.dirname(@path)
File.write(@path, cred_json_text) File.write @path, cred_json_text
connection = Faraday.new(headers: {"User-Agent" => "hello"}) connection = Faraday.new headers: { "User-Agent" => "hello" }
creds = @clz.from_system_default_path(@scope, default_connection: connection) creds = @clz.from_system_default_path @scope, default_connection: connection
expect(creds.build_default_connection).to be connection expect(creds.build_default_connection).to be connection
File.delete(@path) File.delete @path
end end
end end
end end
@ -346,32 +346,32 @@ describe Google::Auth::ServiceAccountJwtHeaderCredentials do
ServiceAccountJwtHeaderCredentials = ServiceAccountJwtHeaderCredentials =
Google::Auth::ServiceAccountJwtHeaderCredentials Google::Auth::ServiceAccountJwtHeaderCredentials
let(:client_email) { 'app@developer.gserviceaccount.com' } let(:client_email) { "app@developer.gserviceaccount.com" }
let(:clz) { Google::Auth::ServiceAccountJwtHeaderCredentials } let(:clz) { Google::Auth::ServiceAccountJwtHeaderCredentials }
let(:cred_json) do let :cred_json do
{ {
private_key_id: 'a_private_key_id', private_key_id: "a_private_key_id",
private_key: @key.to_pem, private_key: @key.to_pem,
client_email: client_email, client_email: client_email,
client_id: 'app.apps.googleusercontent.com', client_id: "app.apps.googleusercontent.com",
type: 'service_account', type: "service_account",
project_id: 'a_project_id' project_id: "a_project_id"
} }
end end
before(:example) do before :example do
@key = OpenSSL::PKey::RSA.new(2048) @key = OpenSSL::PKey::RSA.new 2048
@client = clz.make_creds(json_key_io: StringIO.new(cred_json_text)) @client = clz.make_creds json_key_io: StringIO.new(cred_json_text)
end end
def cred_json_text def cred_json_text
MultiJson.dump(cred_json) MultiJson.dump cred_json
end end
it_behaves_like 'jwt header auth' it_behaves_like "jwt header auth"
describe '#from_env' do describe "#from_env" do
before(:example) do before :example do
@var_name = ENV_VAR @var_name = ENV_VAR
@credential_vars = [ @credential_vars = [
ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, ACCOUNT_TYPE_VAR ENV_VAR, PRIVATE_KEY_VAR, CLIENT_EMAIL_VAR, ACCOUNT_TYPE_VAR
@ -381,90 +381,90 @@ describe Google::Auth::ServiceAccountJwtHeaderCredentials do
ENV[ACCOUNT_TYPE_VAR] = cred_json[:type] ENV[ACCOUNT_TYPE_VAR] = cred_json[:type]
end end
after(:example) do after :example do
@credential_vars.each { |var| ENV[var] = @original_env_vals[var] } @credential_vars.each { |var| ENV[var] = @original_env_vals[var] }
end end
it 'returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset' do it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset" do
ENV.delete(@var_name) unless ENV[@var_name].nil? ENV.delete @var_name unless ENV[@var_name].nil?
expect(clz.from_env).to be_nil expect(clz.from_env).to be_nil
end end
it 'fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist' do it "fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist" do
ENV.delete(@var_name) unless ENV[@var_name].nil? ENV.delete @var_name unless ENV[@var_name].nil?
expect(clz.from_env).to be_nil expect(clz.from_env).to be_nil
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, 'does-not-exist') key_path = File.join dir, "does-not-exist"
ENV[@var_name] = key_path ENV[@var_name] = key_path
expect { clz.from_env }.to raise_error RuntimeError expect { clz.from_env }.to raise_error RuntimeError
end end
end end
it 'succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid' do it "succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, 'my_cert_file') key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV[@var_name] = key_path ENV[@var_name] = key_path
expect(clz.from_env).to_not be_nil expect(clz.from_env).to_not be_nil
end end
end end
it 'succeeds when GOOGLE_PRIVATE_KEY and GOOGLE_CLIENT_EMAIL env vars are'\ it "succeeds when GOOGLE_PRIVATE_KEY and GOOGLE_CLIENT_EMAIL env vars are"\
' valid' do " valid" do
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
expect(clz.from_env(@scope)).to_not be_nil expect(clz.from_env(@scope)).to_not be_nil
end end
it 'sets project_id when the PROJECT_ID_VAR env var is set' do it "sets project_id when the PROJECT_ID_VAR env var is set" do
ENV[PRIVATE_KEY_VAR] = cred_json[:private_key] ENV[PRIVATE_KEY_VAR] = cred_json[:private_key]
ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email] ENV[CLIENT_EMAIL_VAR] = cred_json[:client_email]
ENV[PROJECT_ID_VAR] = cred_json[:project_id] ENV[PROJECT_ID_VAR] = cred_json[:project_id]
ENV[ENV_VAR] = nil ENV[ENV_VAR] = nil
credentials = clz.from_env(@scope) credentials = clz.from_env @scope
expect(credentials).to_not be_nil expect(credentials).to_not be_nil
expect(credentials.project_id).to eq(cred_json[:project_id]) expect(credentials.project_id).to eq(cred_json[:project_id])
end end
end end
describe '#from_well_known_path' do describe "#from_well_known_path" do
before(:example) do before :example do
@home = ENV['HOME'] @home = ENV["HOME"]
@app_data = ENV['APPDATA'] @app_data = ENV["APPDATA"]
end end
after(:example) do after :example do
ENV['HOME'] = @home unless @home == ENV['HOME'] ENV["HOME"] = @home unless @home == ENV["HOME"]
ENV['APPDATA'] = @app_data unless @app_data == ENV['APPDATA'] ENV["APPDATA"] = @app_data unless @app_data == ENV["APPDATA"]
end end
it 'is nil if no file exists' do it "is nil if no file exists" do
ENV['HOME'] = File.dirname(__FILE__) ENV["HOME"] = File.dirname __FILE__
expect(clz.from_well_known_path).to be_nil expect(clz.from_well_known_path).to be_nil
end end
it 'successfully loads the file when it is present' do it "successfully loads the file when it is present" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', WELL_KNOWN_PATH) key_path = File.join dir, ".config", WELL_KNOWN_PATH
key_path = File.join(dir, WELL_KNOWN_PATH) if OS.windows? key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV['HOME'] = dir ENV["HOME"] = dir
ENV['APPDATA'] = dir ENV["APPDATA"] = dir
expect(clz.from_well_known_path).to_not be_nil expect(clz.from_well_known_path).to_not be_nil
end end
end end
it 'successfully sets project_id when file is present' do it "successfully sets project_id when file is present" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', WELL_KNOWN_PATH) key_path = File.join dir, ".config", WELL_KNOWN_PATH
key_path = File.join(dir, WELL_KNOWN_PATH) if OS.windows? key_path = File.join dir, WELL_KNOWN_PATH if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV['HOME'] = dir ENV["HOME"] = dir
ENV['APPDATA'] = dir ENV["APPDATA"] = dir
credentials = clz.from_well_known_path(@scope) credentials = clz.from_well_known_path @scope
expect(credentials.project_id).to eq(cred_json[:project_id]) expect(credentials.project_id).to eq(cred_json[:project_id])
end end
end end

View File

@ -27,61 +27,60 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'apply_auth_examples' require "apply_auth_examples"
require 'googleauth/signet' require "googleauth/signet"
require 'jwt' require "jwt"
require 'openssl' require "openssl"
require 'spec_helper' require "spec_helper"
describe Signet::OAuth2::Client do describe Signet::OAuth2::Client do
before(:example) do before :example do
@key = OpenSSL::PKey::RSA.new(2048) @key = OpenSSL::PKey::RSA.new 2048
@client = Signet::OAuth2::Client.new( @client = Signet::OAuth2::Client.new(
token_credential_uri: 'https://oauth2.googleapis.com/token', token_credential_uri: "https://oauth2.googleapis.com/token",
scope: 'https://www.googleapis.com/auth/userinfo.profile', scope: "https://www.googleapis.com/auth/userinfo.profile",
issuer: 'app@example.com', issuer: "app@example.com",
audience: 'https://oauth2.googleapis.com/token', audience: "https://oauth2.googleapis.com/token",
signing_key: @key signing_key: @key
) )
end end
def make_auth_stubs(opts) def make_auth_stubs opts
access_token = opts[:access_token] || '' access_token = opts[:access_token] || ""
body = MultiJson.dump('access_token' => access_token, body = MultiJson.dump("access_token" => access_token,
'token_type' => 'Bearer', "token_type" => "Bearer",
'expires_in' => 3600) "expires_in" => 3600)
blk = proc do |request| blk = proc do |request|
params = Addressable::URI.form_unencode(request.body) params = Addressable::URI.form_unencode request.body
_claim, _header = JWT.decode(params.assoc('assertion').last, _claim, _header = JWT.decode(params.assoc("assertion").last,
@key.public_key, true, @key.public_key, true,
algorithm: 'RS256') algorithm: "RS256")
end end
with_params = {body: hash_including( with_params = { body: hash_including(
"grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer")} "grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer"
if opts[:user_agent] ) }
with_params[:headers] = {"User-Agent" => opts[:user_agent]} with_params[:headers] = { "User-Agent" => opts[:user_agent] } if opts[:user_agent]
end stub_request(:post, "https://oauth2.googleapis.com/token")
stub_request(:post, 'https://oauth2.googleapis.com/token')
.with(with_params, &blk) .with(with_params, &blk)
.to_return(body: body, .to_return(body: body,
status: 200, status: 200,
headers: { 'Content-Type' => 'application/json' }) headers: { "Content-Type" => "application/json" })
end end
it_behaves_like 'apply/apply! are OK' it_behaves_like "apply/apply! are OK"
describe "#configure_connection" do describe "#configure_connection" do
it "honors default_connection" do it "honors default_connection" do
token = "1/abcdef1234567890" token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token, user_agent: "RubyRocks/1.0" stub = make_auth_stubs access_token: token, user_agent: "RubyRocks/1.0"
conn = Faraday.new headers: {"User-Agent" => "RubyRocks/1.0"} conn = Faraday.new headers: { "User-Agent" => "RubyRocks/1.0" }
@client.configure_connection(default_connection: conn) @client.configure_connection default_connection: conn
md = { foo: "bar" } md = { foo: "bar" }
@client.apply!(md) @client.apply! md
want = { foo: "bar", authorization: "Bearer #{token}" } want = { foo: "bar", authorization: "Bearer #{token}" }
expect(md).to eq(want) expect(md).to eq(want)
expect(stub).to have_been_requested expect(stub).to have_been_requested
@ -91,11 +90,11 @@ describe Signet::OAuth2::Client do
token = "1/abcdef1234567890" token = "1/abcdef1234567890"
stub = make_auth_stubs access_token: token, user_agent: "RubyRocks/2.0" stub = make_auth_stubs access_token: token, user_agent: "RubyRocks/2.0"
connection_builder = proc do connection_builder = proc do
Faraday.new headers: {"User-Agent" => "RubyRocks/2.0"} Faraday.new headers: { "User-Agent" => "RubyRocks/2.0" }
end end
@client.configure_connection(connection_builder: connection_builder) @client.configure_connection connection_builder: connection_builder
md = { foo: "bar" } md = { foo: "bar" }
@client.apply!(md) @client.apply! md
want = { foo: "bar", authorization: "Bearer #{token}" } want = { foo: "bar", authorization: "Bearer #{token}" }
expect(md).to eq(want) expect(md).to eq(want)
expect(stub).to have_been_requested expect(stub).to have_been_requested

View File

@ -27,31 +27,31 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'googleauth' require "googleauth"
require 'googleauth/stores/file_token_store' require "googleauth/stores/file_token_store"
require 'spec_helper' require "spec_helper"
require 'fakefs/safe' require "fakefs/safe"
require 'fakefs/spec_helpers' require "fakefs/spec_helpers"
require 'googleauth/stores/store_examples' require "googleauth/stores/store_examples"
module FakeFS module FakeFS
class File class File
# FakeFS doesn't implement. And since we don't need to actually lock, # FakeFS doesn't implement. And since we don't need to actually lock,
# just stub out... # just stub out...
def flock(*); end def flock *; end
end end
end end
describe Google::Auth::Stores::FileTokenStore do describe Google::Auth::Stores::FileTokenStore do
include FakeFS::SpecHelpers include FakeFS::SpecHelpers
let(:store) do let :store do
Google::Auth::Stores::FileTokenStore.new(file: '/tokens.yaml') Google::Auth::Stores::FileTokenStore.new file: "/tokens.yaml"
end end
it_behaves_like 'token store' it_behaves_like "token store"
end end

View File

@ -27,24 +27,24 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'googleauth' require "googleauth"
require 'googleauth/stores/redis_token_store' require "googleauth/stores/redis_token_store"
require 'spec_helper' require "spec_helper"
require 'fakeredis/rspec' require "fakeredis/rspec"
require 'googleauth/stores/store_examples' require "googleauth/stores/store_examples"
describe Google::Auth::Stores::RedisTokenStore do describe Google::Auth::Stores::RedisTokenStore do
let(:redis) do let :redis do
Redis.new Redis.new
end end
let(:store) do let :store do
Google::Auth::Stores::RedisTokenStore.new(redis: redis) Google::Auth::Stores::RedisTokenStore.new redis: redis
end end
it_behaves_like 'token store' it_behaves_like "token store"
end end

View File

@ -27,32 +27,32 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'spec_helper' require "spec_helper"
shared_examples 'token store' do shared_examples "token store" do
before(:each) do before :each do
store.store('default', 'test') store.store "default", "test"
end end
it 'should return a stored value' do it "should return a stored value" do
expect(store.load('default')).to eq 'test' expect(store.load("default")).to eq "test"
end end
it 'should return nil for missing tokens' do it "should return nil for missing tokens" do
expect(store.load('notavalidkey')).to be_nil expect(store.load("notavalidkey")).to be_nil
end end
it 'should return nil for deleted tokens' do it "should return nil for deleted tokens" do
store.delete('default') store.delete "default"
expect(store.load('default')).to be_nil expect(store.load("default")).to be_nil
end end
it 'should save overwrite values on store' do it "should save overwrite values on store" do
store.store('default', 'test2') store.store "default", "test2"
expect(store.load('default')).to eq 'test2' expect(store.load("default")).to eq "test2"
end end
end end

View File

@ -27,295 +27,294 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'googleauth' require "googleauth"
require 'googleauth/user_authorizer' require "googleauth/user_authorizer"
require 'uri' require "uri"
require 'multi_json' require "multi_json"
require 'spec_helper' require "spec_helper"
describe Google::Auth::UserAuthorizer do describe Google::Auth::UserAuthorizer do
include TestHelpers include TestHelpers
let(:client_id) { Google::Auth::ClientId.new('testclient', 'notasecret') } let(:client_id) { Google::Auth::ClientId.new "testclient", "notasecret" }
let(:scope) { %w(email profile) } let(:scope) { %w[email profile] }
let(:token_store) { DummyTokenStore.new } let(:token_store) { DummyTokenStore.new }
let(:callback_uri) { 'https://www.example.com/oauth/callback' } let(:callback_uri) { "https://www.example.com/oauth/callback" }
let(:authorizer) do let :authorizer do
Google::Auth::UserAuthorizer.new(client_id, Google::Auth::UserAuthorizer.new(client_id,
scope, scope,
token_store, token_store,
callback_uri) callback_uri)
end end
shared_examples 'valid authorization url' do shared_examples "valid authorization url" do
it 'should have a valid base URI' do it "should have a valid base URI" do
expect(uri).to match %r{https://accounts.google.com/o/oauth2/auth} expect(uri).to match %r{https://accounts.google.com/o/oauth2/auth}
end end
it 'should request offline access' do it "should request offline access" do
expect(URI(uri).query).to match(/access_type=offline/) expect(URI(uri).query).to match(/access_type=offline/)
end end
it 'should request response type code' do it "should request response type code" do
expect(URI(uri).query).to match(/response_type=code/) expect(URI(uri).query).to match(/response_type=code/)
end end
it 'should force approval' do it "should force approval" do
expect(URI(uri).query).to match(/approval_prompt=force/) expect(URI(uri).query).to match(/approval_prompt=force/)
end end
it 'should include granted scopes' do it "should include granted scopes" do
expect(URI(uri).query).to match(/include_granted_scopes=true/) expect(URI(uri).query).to match(/include_granted_scopes=true/)
end end
it 'should include the correct client id' do it "should include the correct client id" do
expect(URI(uri).query).to match(/client_id=testclient/) expect(URI(uri).query).to match(/client_id=testclient/)
end end
it 'should not include a client secret' do it "should not include a client secret" do
expect(URI(uri).query).to_not match(/client_secret/) expect(URI(uri).query).to_not match(/client_secret/)
end end
it 'should include the callback uri' do it "should include the callback uri" do
expect(URI(uri).query).to match( expect(URI(uri).query).to match(
%r{redirect_uri=https://www.example.com/oauth/callback} %r{redirect_uri=https://www.example.com/oauth/callback}
) )
end end
it 'should include the scope' do it "should include the scope" do
expect(URI(uri).query).to match(/scope=email%20profile/) expect(URI(uri).query).to match(/scope=email%20profile/)
end end
end end
context 'when generating authorization URLs with user ID & state' do context "when generating authorization URLs with user ID & state" do
let(:uri) do let :uri do
authorizer.get_authorization_url(login_hint: 'user1', state: 'mystate') authorizer.get_authorization_url login_hint: "user1", state: "mystate"
end end
it_behaves_like 'valid authorization url' it_behaves_like "valid authorization url"
it 'includes a login hint' do it "includes a login hint" do
expect(URI(uri).query).to match(/login_hint=user1/) expect(URI(uri).query).to match(/login_hint=user1/)
end end
it 'includes the app state' do it "includes the app state" do
expect(URI(uri).query).to match(/state=mystate/) expect(URI(uri).query).to match(/state=mystate/)
end end
end end
context 'when generating authorization URLs with user ID and no state' do context "when generating authorization URLs with user ID and no state" do
let(:uri) { authorizer.get_authorization_url(login_hint: 'user1') } let(:uri) { authorizer.get_authorization_url login_hint: "user1" }
it_behaves_like 'valid authorization url' it_behaves_like "valid authorization url"
it 'includes a login hint' do it "includes a login hint" do
expect(URI(uri).query).to match(/login_hint=user1/) expect(URI(uri).query).to match(/login_hint=user1/)
end end
it 'does not include the state parameter' do it "does not include the state parameter" do
expect(URI(uri).query).to_not match(/state/) expect(URI(uri).query).to_not match(/state/)
end end
end end
context 'when generating authorization URLs with no user ID and no state' do context "when generating authorization URLs with no user ID and no state" do
let(:uri) { authorizer.get_authorization_url } let(:uri) { authorizer.get_authorization_url }
it_behaves_like 'valid authorization url' it_behaves_like "valid authorization url"
it 'does not include the login hint parameter' do it "does not include the login hint parameter" do
expect(URI(uri).query).to_not match(/login_hint/) expect(URI(uri).query).to_not match(/login_hint/)
end end
it 'does not include the state parameter' do it "does not include the state parameter" do
expect(URI(uri).query).to_not match(/state/) expect(URI(uri).query).to_not match(/state/)
end end
end end
context 'when retrieving tokens' do context "when retrieving tokens" do
let(:token_json) do let :token_json do
MultiJson.dump( MultiJson.dump(
access_token: 'accesstoken', access_token: "accesstoken",
refresh_token: 'refreshtoken', refresh_token: "refreshtoken",
expiration_time_millis: 1_441_234_742_000 expiration_time_millis: 1_441_234_742_000
) )
end end
context 'with a valid user id' do context "with a valid user id" do
let(:credentials) do let :credentials do
token_store.store('user1', token_json) token_store.store "user1", token_json
authorizer.get_credentials('user1') authorizer.get_credentials "user1"
end end
it 'should return an instance of UserRefreshCredentials' do it "should return an instance of UserRefreshCredentials" do
expect(credentials).to be_instance_of( expect(credentials).to be_instance_of(
Google::Auth::UserRefreshCredentials Google::Auth::UserRefreshCredentials
) )
end end
it 'should return credentials with a valid refresh token' do it "should return credentials with a valid refresh token" do
expect(credentials.refresh_token).to eq 'refreshtoken' expect(credentials.refresh_token).to eq "refreshtoken"
end end
it 'should return credentials with a valid access token' do it "should return credentials with a valid access token" do
expect(credentials.access_token).to eq 'accesstoken' expect(credentials.access_token).to eq "accesstoken"
end end
it 'should return credentials with a valid client ID' do it "should return credentials with a valid client ID" do
expect(credentials.client_id).to eq 'testclient' expect(credentials.client_id).to eq "testclient"
end end
it 'should return credentials with a valid client secret' do it "should return credentials with a valid client secret" do
expect(credentials.client_secret).to eq 'notasecret' expect(credentials.client_secret).to eq "notasecret"
end end
it 'should return credentials with a valid scope' do it "should return credentials with a valid scope" do
expect(credentials.scope).to eq %w(email profile) expect(credentials.scope).to eq %w[email profile]
end end
it 'should return credentials with a valid expiration time' do it "should return credentials with a valid expiration time" do
expect(credentials.expires_at).to eq Time.at(1_441_234_742) expect(credentials.expires_at).to eq Time.at(1_441_234_742)
end end
end end
context 'with an invalid user id' do context "with an invalid user id" do
it 'should return nil' do it "should return nil" do
expect(authorizer.get_credentials('notauser')).to be_nil expect(authorizer.get_credentials("notauser")).to be_nil
end end
end end
end end
context 'when saving tokens' do context "when saving tokens" do
let(:expiry) { Time.now.to_i } let(:expiry) { Time.now.to_i }
let(:credentials) do let :credentials do
Google::Auth::UserRefreshCredentials.new( Google::Auth::UserRefreshCredentials.new(
client_id: client_id.id, client_id: client_id.id,
client_secret: client_id.secret, client_secret: client_id.secret,
scope: scope, scope: scope,
refresh_token: 'refreshtoken', refresh_token: "refreshtoken",
access_token: 'accesstoken', access_token: "accesstoken",
expires_at: expiry expires_at: expiry
) )
end end
let(:token_json) do let :token_json do
authorizer.store_credentials('user1', credentials) authorizer.store_credentials "user1", credentials
token_store.load('user1') token_store.load "user1"
end end
it 'should persist in the token store' do it "should persist in the token store" do
expect(token_json).to_not be_nil expect(token_json).to_not be_nil
end end
it 'should persist the refresh token' do it "should persist the refresh token" do
expect(MultiJson.load(token_json)['refresh_token']).to eq 'refreshtoken' expect(MultiJson.load(token_json)["refresh_token"]).to eq "refreshtoken"
end end
it 'should persist the access token' do it "should persist the access token" do
expect(MultiJson.load(token_json)['access_token']).to eq 'accesstoken' expect(MultiJson.load(token_json)["access_token"]).to eq "accesstoken"
end end
it 'should persist the client id' do it "should persist the client id" do
expect(MultiJson.load(token_json)['client_id']).to eq 'testclient' expect(MultiJson.load(token_json)["client_id"]).to eq "testclient"
end end
it 'should persist the scope' do it "should persist the scope" do
expect(MultiJson.load(token_json)['scope']).to include('email', 'profile') expect(MultiJson.load(token_json)["scope"]).to include("email", "profile")
end end
it 'should persist the expiry as milliseconds' do it "should persist the expiry as milliseconds" do
expected_expiry = expiry * 1000 expected_expiry = expiry * 1000
expect(MultiJson.load(token_json)['expiration_time_millis']).to eql( expect(MultiJson.load(token_json)["expiration_time_millis"]).to eql(
expected_expiry expected_expiry
) )
end end
end end
context 'with valid authorization code' do context "with valid authorization code" do
let(:token_json) do let :token_json do
MultiJson.dump('access_token' => '1/abc123', MultiJson.dump("access_token" => "1/abc123",
'token_type' => 'Bearer', "token_type" => "Bearer",
'expires_in' => 3600) "expires_in" => 3600)
end end
before(:example) do before :example do
stub_request(:post, 'https://oauth2.googleapis.com/token') stub_request(:post, "https://oauth2.googleapis.com/token")
.to_return(body: token_json, status: 200, headers: { .to_return(body: token_json, status: 200, headers: {
'Content-Type' => 'application/json' "Content-Type" => "application/json"
}) })
end end
it 'should exchange a code for credentials' do it "should exchange a code for credentials" do
credentials = authorizer.get_credentials_from_code( credentials = authorizer.get_credentials_from_code(
user_id: 'user1', code: 'code' user_id: "user1", code: "code"
) )
expect(credentials.access_token).to eq '1/abc123' expect(credentials.access_token).to eq "1/abc123"
end end
it 'should not store credentials when get only requested' do it "should not store credentials when get only requested" do
authorizer.get_credentials_from_code(user_id: 'user1', code: 'code') authorizer.get_credentials_from_code user_id: "user1", code: "code"
expect(token_store.load('user1')).to be_nil expect(token_store.load("user1")).to be_nil
end end
it 'should store credentials when requested' do it "should store credentials when requested" do
authorizer.get_and_store_credentials_from_code( authorizer.get_and_store_credentials_from_code(
user_id: 'user1', code: 'code' user_id: "user1", code: "code"
) )
expect(token_store.load('user1')).to_not be_nil expect(token_store.load("user1")).to_not be_nil
end end
end end
context 'with invalid authorization code' do context "with invalid authorization code" do
before(:example) do before :example do
stub_request(:post, 'https://oauth2.googleapis.com/token') stub_request(:post, "https://oauth2.googleapis.com/token")
.to_return(status: 400) .to_return(status: 400)
end end
it 'should raise an authorization error' do it "should raise an authorization error" do
expect do expect do
authorizer.get_credentials_from_code(user_id: 'user1', code: 'badcode') authorizer.get_credentials_from_code user_id: "user1", code: "badcode"
end.to raise_error Signet::AuthorizationError end.to raise_error Signet::AuthorizationError
end end
it 'should not store credentials when exchange fails' do it "should not store credentials when exchange fails" do
expect do expect do
authorizer.get_credentials_from_code(user_id: 'user1', code: 'badcode') authorizer.get_credentials_from_code user_id: "user1", code: "badcode"
end.to raise_error Signet::AuthorizationError end.to raise_error Signet::AuthorizationError
expect(token_store.load('user1')).to be_nil expect(token_store.load("user1")).to be_nil
end end
end end
context 'when reovking authorization' do context "when reovking authorization" do
let(:token_json) do let :token_json do
MultiJson.dump( MultiJson.dump(
access_token: 'accesstoken', access_token: "accesstoken",
refresh_token: 'refreshtoken', refresh_token: "refreshtoken",
expiration_time_millis: 1_441_234_742_000 expiration_time_millis: 1_441_234_742_000
) )
end end
before(:example) do before :example do
token_store.store('user1', token_json) token_store.store "user1", token_json
stub_request(:post, 'https://oauth2.googleapis.com/revoke') stub_request(:post, "https://oauth2.googleapis.com/revoke")
.with(body: hash_including('token' => 'refreshtoken')) .with(body: hash_including("token" => "refreshtoken"))
.to_return(status: 200) .to_return(status: 200)
end end
it 'should revoke the grant' do it "should revoke the grant" do
authorizer.revoke_authorization('user1') authorizer.revoke_authorization "user1"
expect(a_request( expect(a_request(
:post, 'https://oauth2.googleapis.com/revoke' :post, "https://oauth2.googleapis.com/revoke"
).with(body: hash_including('token' => 'refreshtoken')) ).with(body: hash_including("token" => "refreshtoken")))
)
.to have_been_made .to have_been_made
end end
it 'should remove the token from storage' do it "should remove the token from storage" do
authorizer.revoke_authorization('user1') authorizer.revoke_authorization "user1"
expect(token_store.load('user1')).to be_nil expect(token_store.load("user1")).to be_nil
end end
end end

View File

@ -27,64 +27,64 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'apply_auth_examples' require "apply_auth_examples"
require 'fakefs/safe' require "fakefs/safe"
require 'fileutils' require "fileutils"
require 'googleauth/user_refresh' require "googleauth/user_refresh"
require 'jwt' require "jwt"
require 'multi_json' require "multi_json"
require 'openssl' require "openssl"
require 'spec_helper' require "spec_helper"
require 'tmpdir' require "tmpdir"
require 'os' require "os"
include Google::Auth::CredentialsLoader include Google::Auth::CredentialsLoader
describe Google::Auth::UserRefreshCredentials do describe Google::Auth::UserRefreshCredentials do
UserRefreshCredentials = Google::Auth::UserRefreshCredentials UserRefreshCredentials = Google::Auth::UserRefreshCredentials
let(:cred_json) do let :cred_json do
{ {
client_secret: 'privatekey', client_secret: "privatekey",
client_id: 'client123', client_id: "client123",
refresh_token: 'refreshtoken', refresh_token: "refreshtoken",
type: 'authorized_user' type: "authorized_user"
} }
end end
before(:example) do before :example do
@key = OpenSSL::PKey::RSA.new(2048) @key = OpenSSL::PKey::RSA.new 2048
@client = UserRefreshCredentials.make_creds( @client = UserRefreshCredentials.make_creds(
json_key_io: StringIO.new(cred_json_text), json_key_io: StringIO.new(cred_json_text),
scope: 'https://www.googleapis.com/auth/userinfo.profile' scope: "https://www.googleapis.com/auth/userinfo.profile"
) )
end end
def make_auth_stubs(opts = {}) def make_auth_stubs opts = {}
access_token = opts[:access_token] || '' access_token = opts[:access_token] || ""
body = MultiJson.dump('access_token' => access_token, body = MultiJson.dump("access_token" => access_token,
'token_type' => 'Bearer', "token_type" => "Bearer",
'expires_in' => 3600) "expires_in" => 3600)
stub_request(:post, 'https://oauth2.googleapis.com/token') stub_request(:post, "https://oauth2.googleapis.com/token")
.with(body: hash_including('grant_type' => 'refresh_token')) .with(body: hash_including("grant_type" => "refresh_token"))
.to_return(body: body, .to_return(body: body,
status: 200, status: 200,
headers: { 'Content-Type' => 'application/json' }) headers: { "Content-Type" => "application/json" })
end end
def cred_json_text(missing = nil) def cred_json_text missing = nil
cred_json.delete(missing.to_sym) unless missing.nil? cred_json.delete missing.to_sym unless missing.nil?
MultiJson.dump(cred_json) MultiJson.dump cred_json
end end
it_behaves_like 'apply/apply! are OK' it_behaves_like "apply/apply! are OK"
describe '#from_env' do describe "#from_env" do
before(:example) do before :example do
@var_name = ENV_VAR @var_name = ENV_VAR
@credential_vars = [ @credential_vars = [
ENV_VAR, CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR, ENV_VAR, CLIENT_ID_VAR, CLIENT_SECRET_VAR, REFRESH_TOKEN_VAR,
@ -92,243 +92,243 @@ describe Google::Auth::UserRefreshCredentials do
] ]
@original_env_vals = {} @original_env_vals = {}
@credential_vars.each { |var| @original_env_vals[var] = ENV[var] } @credential_vars.each { |var| @original_env_vals[var] = ENV[var] }
@scope = 'https://www.googleapis.com/auth/userinfo.profile' @scope = "https://www.googleapis.com/auth/userinfo.profile"
@clz = UserRefreshCredentials @clz = UserRefreshCredentials
@project_id = 'a_project_id' @project_id = "a_project_id"
end end
after(:example) do after :example do
@credential_vars.each { |var| ENV[var] = @original_env_vals[var] } @credential_vars.each { |var| ENV[var] = @original_env_vals[var] }
end end
it 'returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset' do it "returns nil if the GOOGLE_APPLICATION_CREDENTIALS is unset" do
ENV.delete(@var_name) unless ENV[@var_name].nil? ENV.delete @var_name unless ENV[@var_name].nil?
expect(UserRefreshCredentials.from_env(@scope)).to be_nil expect(UserRefreshCredentials.from_env(@scope)).to be_nil
end end
it 'fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist' do it "fails if the GOOGLE_APPLICATION_CREDENTIALS path does not exist" do
ENV.delete(@var_name) unless ENV[@var_name].nil? ENV.delete @var_name unless ENV[@var_name].nil?
expect(UserRefreshCredentials.from_env(@scope)).to be_nil expect(UserRefreshCredentials.from_env(@scope)).to be_nil
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, 'does-not-exist') key_path = File.join dir, "does-not-exist"
ENV[@var_name] = key_path ENV[@var_name] = key_path
expect { @clz.from_env(@scope) }.to raise_error RuntimeError expect { @clz.from_env @scope }.to raise_error RuntimeError
end end
end end
it 'fails if the GOOGLE_APPLICATION_CREDENTIALS path file is invalid' do it "fails if the GOOGLE_APPLICATION_CREDENTIALS path file is invalid" do
needed = %w(client_id client_secret refresh_token) needed = %w[client_id client_secret refresh_token]
needed.each do |missing| needed.each do |missing|
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, 'my_cert_file') key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text(missing)) File.write key_path, cred_json_text(missing)
ENV[@var_name] = key_path ENV[@var_name] = key_path
expect { @clz.from_env(@scope) }.to raise_error RuntimeError expect { @clz.from_env @scope }.to raise_error RuntimeError
end end
end end
end end
it 'succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid' do it "succeeds when the GOOGLE_APPLICATION_CREDENTIALS file is valid" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, 'my_cert_file') key_path = File.join dir, "my_cert_file"
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV[@var_name] = key_path ENV[@var_name] = key_path
expect(@clz.from_env(@scope)).to_not be_nil expect(@clz.from_env(@scope)).to_not be_nil
end end
end end
it 'succeeds when GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and '\ it "succeeds when GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and "\
'GOOGLE_REFRESH_TOKEN env vars are valid' do "GOOGLE_REFRESH_TOKEN env vars are valid" do
ENV[ENV_VAR] = nil ENV[ENV_VAR] = nil
ENV[CLIENT_ID_VAR] = cred_json[:client_id] ENV[CLIENT_ID_VAR] = cred_json[:client_id]
ENV[CLIENT_SECRET_VAR] = cred_json[:client_secret] ENV[CLIENT_SECRET_VAR] = cred_json[:client_secret]
ENV[REFRESH_TOKEN_VAR] = cred_json[:refresh_token] ENV[REFRESH_TOKEN_VAR] = cred_json[:refresh_token]
ENV[ACCOUNT_TYPE_VAR] = cred_json[:type] ENV[ACCOUNT_TYPE_VAR] = cred_json[:type]
creds = @clz.from_env(@scope) creds = @clz.from_env @scope
expect(creds).to_not be_nil expect(creds).to_not be_nil
expect(creds.client_id).to eq(cred_json[:client_id]) expect(creds.client_id).to eq(cred_json[:client_id])
expect(creds.client_secret).to eq(cred_json[:client_secret]) expect(creds.client_secret).to eq(cred_json[:client_secret])
expect(creds.refresh_token).to eq(cred_json[:refresh_token]) expect(creds.refresh_token).to eq(cred_json[:refresh_token])
end end
it 'sets project_id when the PROJECT_ID_VAR env var is set' do it "sets project_id when the PROJECT_ID_VAR env var is set" do
ENV[ENV_VAR] = nil ENV[ENV_VAR] = nil
ENV[CLIENT_ID_VAR] = cred_json[:client_id] ENV[CLIENT_ID_VAR] = cred_json[:client_id]
ENV[CLIENT_SECRET_VAR] = cred_json[:client_secret] ENV[CLIENT_SECRET_VAR] = cred_json[:client_secret]
ENV[REFRESH_TOKEN_VAR] = cred_json[:refresh_token] ENV[REFRESH_TOKEN_VAR] = cred_json[:refresh_token]
ENV[ACCOUNT_TYPE_VAR] = cred_json[:type] ENV[ACCOUNT_TYPE_VAR] = cred_json[:type]
ENV[PROJECT_ID_VAR] = @project_id ENV[PROJECT_ID_VAR] = @project_id
creds = @clz.from_env(@scope) creds = @clz.from_env @scope
expect(creds.project_id).to eq(@project_id) expect(creds.project_id).to eq(@project_id)
end end
end end
describe '#from_well_known_path' do describe "#from_well_known_path" do
before(:example) do before :example do
@home = ENV['HOME'] @home = ENV["HOME"]
@app_data = ENV['APPDATA'] @app_data = ENV["APPDATA"]
@scope = 'https://www.googleapis.com/auth/userinfo.profile' @scope = "https://www.googleapis.com/auth/userinfo.profile"
@known_path = WELL_KNOWN_PATH @known_path = WELL_KNOWN_PATH
@clz = UserRefreshCredentials @clz = UserRefreshCredentials
end end
after(:example) do after :example do
ENV['HOME'] = @home unless @home == ENV['HOME'] ENV["HOME"] = @home unless @home == ENV["HOME"]
ENV['APPDATA'] = @app_data unless @app_data == ENV['APPDATA'] ENV["APPDATA"] = @app_data unless @app_data == ENV["APPDATA"]
end end
it 'is nil if no file exists' do it "is nil if no file exists" do
ENV['HOME'] = File.dirname(__FILE__) ENV["HOME"] = File.dirname __FILE__
expect(UserRefreshCredentials.from_well_known_path(@scope)).to be_nil expect(UserRefreshCredentials.from_well_known_path(@scope)).to be_nil
end end
it 'fails if the file is invalid' do it "fails if the file is invalid" do
needed = %w(client_id client_secret refresh_token) needed = %w[client_id client_secret refresh_token]
needed.each do |missing| needed.each do |missing|
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', @known_path) key_path = File.join dir, ".config", @known_path
key_path = File.join(dir, @known_path) if OS.windows? key_path = File.join dir, @known_path if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text(missing)) File.write key_path, cred_json_text(missing)
ENV['HOME'] = dir ENV["HOME"] = dir
ENV['APPDATA'] = dir ENV["APPDATA"] = dir
expect { @clz.from_well_known_path(@scope) } expect { @clz.from_well_known_path @scope }
.to raise_error RuntimeError .to raise_error RuntimeError
end end
end end
end end
it 'successfully loads the file when it is present' do it "successfully loads the file when it is present" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', @known_path) key_path = File.join dir, ".config", @known_path
key_path = File.join(dir, @known_path) if OS.windows? key_path = File.join dir, @known_path if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV['HOME'] = dir ENV["HOME"] = dir
ENV['APPDATA'] = dir ENV["APPDATA"] = dir
expect(@clz.from_well_known_path(@scope)).to_not be_nil expect(@clz.from_well_known_path(@scope)).to_not be_nil
end end
end end
it 'checks gcloud config for project_id if none was provided' do it "checks gcloud config for project_id if none was provided" do
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
key_path = File.join(dir, '.config', @known_path) key_path = File.join dir, ".config", @known_path
key_path = File.join(dir, @known_path) if OS.windows? key_path = File.join dir, @known_path if OS.windows?
FileUtils.mkdir_p(File.dirname(key_path)) FileUtils.mkdir_p File.dirname(key_path)
File.write(key_path, cred_json_text) File.write key_path, cred_json_text
ENV['HOME'] = dir ENV["HOME"] = dir
ENV['APPDATA'] = dir ENV["APPDATA"] = dir
ENV[PROJECT_ID_VAR] = nil ENV[PROJECT_ID_VAR] = nil
expect(Google::Auth::CredentialsLoader).to receive(:load_gcloud_project_id).with(no_args) expect(Google::Auth::CredentialsLoader).to receive(:load_gcloud_project_id).with(no_args)
@clz.from_well_known_path(@scope) @clz.from_well_known_path @scope
end end
end end
end end
describe '#from_system_default_path' do describe "#from_system_default_path" do
before(:example) do before :example do
@scope = 'https://www.googleapis.com/auth/userinfo.profile' @scope = "https://www.googleapis.com/auth/userinfo.profile"
@prefix = OS.windows? ? '/etc/Google/Auth/' : '/etc/google/auth/' @prefix = OS.windows? ? "/etc/Google/Auth/" : "/etc/google/auth/"
@path = File.join(@prefix, CREDENTIALS_FILE_NAME) @path = File.join @prefix, CREDENTIALS_FILE_NAME
@program_data = ENV['ProgramData'] @program_data = ENV["ProgramData"]
@clz = UserRefreshCredentials @clz = UserRefreshCredentials
end end
after(:example) do after :example do
ENV['ProgramData'] = @program_data ENV["ProgramData"] = @program_data
end end
it 'is nil if no file exists' do it "is nil if no file exists" do
FakeFS do FakeFS do
expect(UserRefreshCredentials.from_system_default_path(@scope)) expect(UserRefreshCredentials.from_system_default_path(@scope))
.to be_nil .to be_nil
end end
end end
it 'fails if the file is invalid' do it "fails if the file is invalid" do
needed = %w(client_id client_secret refresh_token) needed = %w[client_id client_secret refresh_token]
needed.each do |missing| needed.each do |missing|
FakeFS do FakeFS do
ENV['ProgramData'] = '/etc' ENV["ProgramData"] = "/etc"
FileUtils.mkdir_p(File.dirname(@path)) FileUtils.mkdir_p File.dirname(@path)
File.write(@path, cred_json_text(missing)) File.write @path, cred_json_text(missing)
expect { @clz.from_system_default_path(@scope) } expect { @clz.from_system_default_path @scope }
.to raise_error RuntimeError .to raise_error RuntimeError
File.delete(@path) File.delete @path
end end
end end
end end
it 'successfully loads the file when it is present' do it "successfully loads the file when it is present" do
FakeFS do FakeFS do
ENV['ProgramData'] = '/etc' ENV["ProgramData"] = "/etc"
FileUtils.mkdir_p(File.dirname(@path)) FileUtils.mkdir_p File.dirname(@path)
File.write(@path, cred_json_text) File.write @path, cred_json_text
expect(@clz.from_system_default_path(@scope)).to_not be_nil expect(@clz.from_system_default_path(@scope)).to_not be_nil
File.delete(@path) File.delete @path
end end
end end
end end
shared_examples 'revoked token' do shared_examples "revoked token" do
it 'should nil the refresh token' do it "should nil the refresh token" do
expect(@client.refresh_token).to be_nil expect(@client.refresh_token).to be_nil
end end
it 'should nil the access token' do it "should nil the access token" do
expect(@client.access_token).to be_nil expect(@client.access_token).to be_nil
end end
it 'should mark the token as expired' do it "should mark the token as expired" do
expect(@client.expired?).to be_truthy expect(@client.expired?).to be_truthy
end end
end end
describe 'when revoking a refresh token' do describe "when revoking a refresh token" do
let(:stub) do let :stub do
stub_request(:post, 'https://oauth2.googleapis.com/revoke') stub_request(:post, "https://oauth2.googleapis.com/revoke")
.with(body: hash_including('token' => 'refreshtoken')) .with(body: hash_including("token" => "refreshtoken"))
.to_return(status: 200, .to_return(status: 200,
headers: { 'Content-Type' => 'application/json' }) headers: { "Content-Type" => "application/json" })
end end
before(:example) do before :example do
stub stub
@client.revoke! @client.revoke!
end end
it_behaves_like 'revoked token' it_behaves_like "revoked token"
end end
describe 'when revoking an access token' do describe "when revoking an access token" do
let(:stub) do let :stub do
stub_request(:post, 'https://oauth2.googleapis.com/revoke') stub_request(:post, "https://oauth2.googleapis.com/revoke")
.with(body: hash_including('token' => 'accesstoken')) .with(body: hash_including("token" => "accesstoken"))
.to_return(status: 200, .to_return(status: 200,
headers: { 'Content-Type' => 'application/json' }) headers: { "Content-Type" => "application/json" })
end end
before(:example) do before :example do
stub stub
@client.refresh_token = nil @client.refresh_token = nil
@client.access_token = 'accesstoken' @client.access_token = "accesstoken"
@client.revoke! @client.revoke!
end end
it_behaves_like 'revoked token' it_behaves_like "revoked token"
end end
describe 'when revoking an invalid token' do describe "when revoking an invalid token" do
let(:stub) do let :stub do
stub_request(:post, 'https://oauth2.googleapis.com/revoke') stub_request(:post, "https://oauth2.googleapis.com/revoke")
.with(body: hash_including('token' => 'refreshtoken')) .with(body: hash_including("token" => "refreshtoken"))
.to_return(status: 400, .to_return(status: 400,
headers: { 'Content-Type' => 'application/json' }) headers: { "Content-Type" => "application/json" })
end end
it 'raises an authorization error' do it "raises an authorization error" do
stub stub
expect { @client.revoke! }.to raise_error( expect { @client.revoke! }.to raise_error(
Signet::AuthorizationError Signet::AuthorizationError
@ -336,15 +336,15 @@ describe Google::Auth::UserRefreshCredentials do
end end
end end
describe 'when errors occurred with request' do describe "when errors occurred with request" do
it 'should fail with Signet::AuthorizationError if request times out' do it "should fail with Signet::AuthorizationError if request times out" do
allow_any_instance_of(Faraday::Connection).to receive(:post) allow_any_instance_of(Faraday::Connection).to receive(:post)
.and_raise(Faraday::TimeoutError) .and_raise(Faraday::TimeoutError)
expect { @client.revoke! } expect { @client.revoke! }
.to raise_error Signet::AuthorizationError .to raise_error Signet::AuthorizationError
end end
it 'should fail with Signet::AuthorizationError if request fails' do it "should fail with Signet::AuthorizationError if request fails" do
allow_any_instance_of(Faraday::Connection).to receive(:post) allow_any_instance_of(Faraday::Connection).to receive(:post)
.and_raise(Faraday::ConnectionFailed, nil) .and_raise(Faraday::ConnectionFailed, nil)
expect { @client.revoke! } expect { @client.revoke! }

View File

@ -27,140 +27,140 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.join(File.dirname(__FILE__))) spec_dir = File.expand_path File.join(File.dirname(__FILE__))
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
require 'googleauth' require "googleauth"
require 'googleauth/web_user_authorizer' require "googleauth/web_user_authorizer"
require 'uri' require "uri"
require 'multi_json' require "multi_json"
require 'spec_helper' require "spec_helper"
require 'rack' require "rack"
describe Google::Auth::WebUserAuthorizer do describe Google::Auth::WebUserAuthorizer do
include TestHelpers include TestHelpers
let(:client_id) { Google::Auth::ClientId.new('testclient', 'notasecret') } let(:client_id) { Google::Auth::ClientId.new "testclient", "notasecret" }
let(:scope) { %w(email profile) } let(:scope) { %w[email profile] }
let(:token_store) { DummyTokenStore.new } let(:token_store) { DummyTokenStore.new }
let(:authorizer) do let :authorizer do
Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store) Google::Auth::WebUserAuthorizer.new client_id, scope, token_store
end end
describe '#get_authorization_url' do describe "#get_authorization_url" do
let(:env) do let :env do
Rack::MockRequest.env_for( Rack::MockRequest.env_for(
'http://example.com:8080/test', "http://example.com:8080/test",
'REMOTE_ADDR' => '10.10.10.10' "REMOTE_ADDR" => "10.10.10.10"
) )
end end
let(:request) { Rack::Request.new(env) } let(:request) { Rack::Request.new env }
it 'should include current url in state' do it "should include current url in state" do
url = authorizer.get_authorization_url(request: request) url = authorizer.get_authorization_url request: request
expect(url).to match( expect(url).to match(
%r{%22current_uri%22:%22http://example.com:8080/test%22} %r{%22current_uri%22:%22http://example.com:8080/test%22}
) )
end end
it 'should include request forgery token in state' do it "should include request forgery token in state" do
expect(SecureRandom).to receive(:base64).and_return('aGVsbG8=') expect(SecureRandom).to receive(:base64).and_return("aGVsbG8=")
url = authorizer.get_authorization_url(request: request) url = authorizer.get_authorization_url request: request
expect(url).to match(/%22session_id%22:%22aGVsbG8=%22/) expect(url).to match(/%22session_id%22:%22aGVsbG8=%22/)
end end
it 'should include request forgery token in session' do it "should include request forgery token in session" do
expect(SecureRandom).to receive(:base64).and_return('aGVsbG8=') expect(SecureRandom).to receive(:base64).and_return("aGVsbG8=")
authorizer.get_authorization_url(request: request) authorizer.get_authorization_url request: request
expect(request.session['g-xsrf-token']).to eq 'aGVsbG8=' expect(request.session["g-xsrf-token"]).to eq "aGVsbG8="
end end
it 'should resolve callback against base URL' do it "should resolve callback against base URL" do
url = authorizer.get_authorization_url(request: request) url = authorizer.get_authorization_url request: request
expect(url).to match( expect(url).to match(
%r{redirect_uri=http://example.com:8080/oauth2callback} %r{redirect_uri=http://example.com:8080/oauth2callback}
) )
end end
it 'should allow overriding the current URL' do it "should allow overriding the current URL" do
url = authorizer.get_authorization_url( url = authorizer.get_authorization_url(
request: request, request: request,
redirect_to: '/foo' redirect_to: "/foo"
) )
expect(url).to match %r{%22current_uri%22:%22/foo%22} expect(url).to match %r{%22current_uri%22:%22/foo%22}
end end
it 'should pass through login hint' do it "should pass through login hint" do
url = authorizer.get_authorization_url( url = authorizer.get_authorization_url(
request: request, request: request,
login_hint: 'user@example.com' login_hint: "user@example.com"
) )
expect(url).to match(/login_hint=user@example.com/) expect(url).to match(/login_hint=user@example.com/)
end end
end end
shared_examples 'handles callback' do shared_examples "handles callback" do
let(:token_json) do let :token_json do
MultiJson.dump('access_token' => '1/abc123', MultiJson.dump("access_token" => "1/abc123",
'token_type' => 'Bearer', "token_type" => "Bearer",
'expires_in' => 3600) "expires_in" => 3600)
end end
before(:example) do before :example do
stub_request(:post, 'https://oauth2.googleapis.com/token') stub_request(:post, "https://oauth2.googleapis.com/token")
.to_return(body: token_json, .to_return(body: token_json,
status: 200, status: 200,
headers: { 'Content-Type' => 'application/json' }) headers: { "Content-Type" => "application/json" })
end end
let(:env) do let :env do
Rack::MockRequest.env_for( Rack::MockRequest.env_for(
'http://example.com:8080/oauth2callback?code=authcode&'\ "http://example.com:8080/oauth2callback?code=authcode&"\
'state=%7B%22current_uri%22%3A%22%2Ffoo%22%2C%22'\ "state=%7B%22current_uri%22%3A%22%2Ffoo%22%2C%22"\
'session_id%22%3A%22abc%22%7D', "session_id%22%3A%22abc%22%7D",
'REMOTE_ADDR' => '10.10.10.10' "REMOTE_ADDR" => "10.10.10.10"
) )
end end
let(:request) { Rack::Request.new(env) } let(:request) { Rack::Request.new env }
before(:example) do before :example do
request.session['g-xsrf-token'] = 'abc' request.session["g-xsrf-token"] = "abc"
end end
it 'should return credentials when valid code present' do it "should return credentials when valid code present" do
expect(credentials).to be_instance_of( expect(credentials).to be_instance_of(
Google::Auth::UserRefreshCredentials Google::Auth::UserRefreshCredentials
) )
end end
it 'should return next URL to redirect to' do it "should return next URL to redirect to" do
expect(next_url).to eq '/foo' expect(next_url).to eq "/foo"
end end
it 'should fail if xrsf token in session and does not match request' do it "should fail if xrsf token in session and does not match request" do
request.session['g-xsrf-token'] = '123' request.session["g-xsrf-token"] = "123"
expect { credentials }.to raise_error(Signet::AuthorizationError) expect { credentials }.to raise_error(Signet::AuthorizationError)
end end
end end
describe '#handle_auth_callback' do describe "#handle_auth_callback" do
let(:result) { authorizer.handle_auth_callback('user1', request) } let(:result) { authorizer.handle_auth_callback "user1", request }
let(:credentials) { result[0] } let(:credentials) { result[0] }
let(:next_url) { result[1] } let(:next_url) { result[1] }
it_behaves_like 'handles callback' it_behaves_like "handles callback"
end end
describe '#handle_auth_callback_deferred and #get_credentials' do describe "#handle_auth_callback_deferred and #get_credentials" do
let(:next_url) do let :next_url do
Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request) Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred request
end end
let(:credentials) do let :credentials do
next_url next_url
authorizer.get_credentials('user1', request) authorizer.get_credentials "user1", request
end end
it_behaves_like 'handles callback' it_behaves_like "handles callback"
end end
end end

View File

@ -27,17 +27,17 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
spec_dir = File.expand_path(File.dirname(__FILE__)) spec_dir = __dir__
root_dir = File.expand_path(File.join(spec_dir, '..')) root_dir = File.expand_path File.join(spec_dir, "..")
lib_dir = File.expand_path(File.join(root_dir, 'lib')) lib_dir = File.expand_path File.join(root_dir, "lib")
$LOAD_PATH.unshift(spec_dir) $LOAD_PATH.unshift spec_dir
$LOAD_PATH.unshift(lib_dir) $LOAD_PATH.unshift lib_dir
$LOAD_PATH.uniq! $LOAD_PATH.uniq!
# set up coverage # set up coverage
require 'simplecov' require "simplecov"
require 'coveralls' require "coveralls"
SimpleCov.formatters = [ SimpleCov.formatters = [
Coveralls::SimpleCov::Formatter, Coveralls::SimpleCov::Formatter,
@ -45,18 +45,18 @@ SimpleCov.formatters = [
] ]
SimpleCov.start SimpleCov.start
require 'faraday' require "faraday"
require 'rspec' require "rspec"
require 'logging' require "logging"
require 'rspec/logging_helper' require "rspec/logging_helper"
require 'webmock/rspec' require "webmock/rspec"
require 'multi_json' require "multi_json"
# Preload adapter to work around Rubinius error with FakeFS # Preload adapter to work around Rubinius error with FakeFS
MultiJson.use(:json_gem) MultiJson.use :json_gem
# Allow Faraday to support test stubs # Allow Faraday to support test stubs
Faraday::Adapter.load_middleware(:test) Faraday::Adapter.load_middleware :test
# Configure RSpec to capture log messages for each test. The output from the # 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. # logs will be stored in the @log_output variable. It is a StringIO instance.
@ -78,15 +78,15 @@ class DummyTokenStore
@tokens = {} @tokens = {}
end end
def load(id) def load id
@tokens[id] @tokens[id]
end end
def store(id, token) def store id, token
@tokens[id] = token @tokens[id] = token
end end
def delete(id) def delete id
@tokens.delete(id) @tokens.delete id
end end
end end