add google-style and refactor (#203)
This commit is contained in:
parent
1c64927249
commit
bf822910fa
31
.rubocop.yml
31
.rubocop.yml
|
@ -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
|
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -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"
|
||||||
|
|
40
Rakefile
40
Rakefile
|
@ -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}"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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! }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue