googleauth 0.8.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.kokoro/build.bat +9 -1
- data/.kokoro/continuous/linux.cfg +12 -2
- data/.kokoro/continuous/osx.cfg +5 -0
- data/.kokoro/continuous/post.cfg +30 -0
- data/.kokoro/continuous/windows.cfg +27 -1
- data/.kokoro/presubmit/linux.cfg +11 -1
- data/.kokoro/presubmit/osx.cfg +5 -0
- data/.kokoro/presubmit/windows.cfg +27 -1
- data/.kokoro/release.cfg +42 -1
- data/.kokoro/trampoline.bat +10 -0
- data/.repo-metadata.json +5 -0
- data/.rubocop.yml +10 -2
- data/CHANGELOG.md +34 -0
- data/Gemfile +8 -3
- data/README.md +7 -12
- data/Rakefile +48 -5
- data/googleauth.gemspec +6 -3
- data/integration/helper.rb +31 -0
- data/integration/id_tokens/key_source_test.rb +74 -0
- data/lib/googleauth.rb +1 -0
- data/lib/googleauth/application_default.rb +1 -1
- data/lib/googleauth/compute_engine.rb +19 -17
- data/lib/googleauth/credentials.rb +318 -63
- data/lib/googleauth/credentials_loader.rb +10 -8
- data/lib/googleauth/id_tokens.rb +233 -0
- data/lib/googleauth/id_tokens/errors.rb +71 -0
- data/lib/googleauth/id_tokens/key_sources.rb +394 -0
- data/lib/googleauth/id_tokens/verifier.rb +144 -0
- data/lib/googleauth/json_key_reader.rb +6 -2
- data/lib/googleauth/service_account.rb +16 -7
- data/lib/googleauth/signet.rb +8 -5
- data/lib/googleauth/user_authorizer.rb +6 -1
- data/lib/googleauth/user_refresh.rb +2 -2
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +13 -8
- data/rakelib/devsite_builder.rb +45 -0
- data/rakelib/link_checker.rb +64 -0
- data/rakelib/repo_metadata.rb +59 -0
- data/spec/googleauth/apply_auth_examples.rb +28 -5
- data/spec/googleauth/compute_engine_spec.rb +25 -13
- data/spec/googleauth/credentials_spec.rb +366 -161
- data/spec/googleauth/service_account_spec.rb +23 -16
- data/spec/googleauth/signet_spec.rb +46 -7
- data/spec/googleauth/user_authorizer_spec.rb +21 -1
- data/spec/googleauth/user_refresh_spec.rb +1 -1
- data/spec/googleauth/web_user_authorizer_spec.rb +6 -0
- data/test/helper.rb +33 -0
- data/test/id_tokens/key_sources_test.rb +240 -0
- data/test/id_tokens/verifier_test.rb +269 -0
- metadata +46 -12
- data/.kokoro/windows.sh +0 -4
data/googleauth.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |gem|
|
|
9
9
|
gem.version = Google::Auth::VERSION
|
10
10
|
gem.authors = ["Tim Emiola"]
|
11
11
|
gem.email = "temiola@google.com"
|
12
|
-
gem.homepage = "https://github.com/
|
12
|
+
gem.homepage = "https://github.com/googleapis/google-auth-library-ruby"
|
13
13
|
gem.summary = "Google Auth Library for Ruby"
|
14
14
|
gem.license = "Apache-2.0"
|
15
15
|
gem.description = <<-DESCRIPTION
|
@@ -25,11 +25,14 @@ Gem::Specification.new do |gem|
|
|
25
25
|
end
|
26
26
|
gem.require_paths = ["lib"]
|
27
27
|
gem.platform = Gem::Platform::RUBY
|
28
|
+
gem.required_ruby_version = ">= 2.4.0"
|
28
29
|
|
29
|
-
gem.add_dependency "faraday", "
|
30
|
+
gem.add_dependency "faraday", ">= 0.17.3", "< 2.0"
|
30
31
|
gem.add_dependency "jwt", ">= 1.4", "< 3.0"
|
31
32
|
gem.add_dependency "memoist", "~> 0.16"
|
32
33
|
gem.add_dependency "multi_json", "~> 1.11"
|
33
34
|
gem.add_dependency "os", ">= 0.9", "< 2.0"
|
34
|
-
gem.add_dependency "signet", "~> 0.
|
35
|
+
gem.add_dependency "signet", "~> 0.14"
|
36
|
+
|
37
|
+
gem.add_development_dependency "yard", "~> 0.9"
|
35
38
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Copyright 2020 Google LLC
|
2
|
+
#
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
4
|
+
# modification, are permitted provided that the following conditions are
|
5
|
+
# met:
|
6
|
+
#
|
7
|
+
# * Redistributions of source code must retain the above copyright
|
8
|
+
# notice, this list of conditions and the following disclaimer.
|
9
|
+
# * Redistributions in binary form must reproduce the above
|
10
|
+
# copyright notice, this list of conditions and the following disclaimer
|
11
|
+
# in the documentation and/or other materials provided with the
|
12
|
+
# distribution.
|
13
|
+
# * Neither the name of Google Inc. nor the names of its
|
14
|
+
# contributors may be used to endorse or promote products derived from
|
15
|
+
# this software without specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
18
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
19
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
20
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
21
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
22
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
23
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
24
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
25
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
|
29
|
+
require "minitest/autorun"
|
30
|
+
require "minitest/focus"
|
31
|
+
require "googleauth"
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Copyright 2020 Google LLC
|
2
|
+
#
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
4
|
+
# modification, are permitted provided that the following conditions are
|
5
|
+
# met:
|
6
|
+
#
|
7
|
+
# * Redistributions of source code must retain the above copyright
|
8
|
+
# notice, this list of conditions and the following disclaimer.
|
9
|
+
# * Redistributions in binary form must reproduce the above
|
10
|
+
# copyright notice, this list of conditions and the following disclaimer
|
11
|
+
# in the documentation and/or other materials provided with the
|
12
|
+
# distribution.
|
13
|
+
# * Neither the name of Google Inc. nor the names of its
|
14
|
+
# contributors may be used to endorse or promote products derived from
|
15
|
+
# this software without specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
18
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
19
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
20
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
21
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
22
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
23
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
24
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
25
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
|
29
|
+
require "helper"
|
30
|
+
|
31
|
+
describe Google::Auth::IDTokens do
|
32
|
+
describe "key source" do
|
33
|
+
let(:legacy_oidc_key_source) {
|
34
|
+
Google::Auth::IDTokens::X509CertHttpKeySource.new "https://www.googleapis.com/oauth2/v1/certs"
|
35
|
+
}
|
36
|
+
let(:oidc_key_source) { Google::Auth::IDTokens.oidc_key_source }
|
37
|
+
let(:iap_key_source) { Google::Auth::IDTokens.iap_key_source }
|
38
|
+
|
39
|
+
it "Gets real keys from the OAuth2 V1 cert URL" do
|
40
|
+
keys = legacy_oidc_key_source.refresh_keys
|
41
|
+
refute_empty keys
|
42
|
+
keys.each do |key|
|
43
|
+
assert_kind_of OpenSSL::PKey::RSA, key.key
|
44
|
+
refute key.key.private?
|
45
|
+
assert_equal "RS256", key.algorithm
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "Gets real keys from the OAuth2 V3 cert URL" do
|
50
|
+
keys = oidc_key_source.refresh_keys
|
51
|
+
refute_empty keys
|
52
|
+
keys.each do |key|
|
53
|
+
assert_kind_of OpenSSL::PKey::RSA, key.key
|
54
|
+
refute key.key.private?
|
55
|
+
assert_equal "RS256", key.algorithm
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "Gets the same keys from the OAuth2 V1 and V3 cert URLs" do
|
60
|
+
keys_v1 = legacy_oidc_key_source.refresh_keys.map(&:key).map(&:export).sort
|
61
|
+
keys_v3 = oidc_key_source.refresh_keys.map(&:key).map(&:export).sort
|
62
|
+
assert_equal keys_v1, keys_v3
|
63
|
+
end
|
64
|
+
|
65
|
+
it "Gets real keys from the IAP public key URL" do
|
66
|
+
keys = iap_key_source.refresh_keys
|
67
|
+
refute_empty keys
|
68
|
+
keys.each do |key|
|
69
|
+
assert_kind_of OpenSSL::PKey::EC, key.key
|
70
|
+
assert_equal "ES256", key.algorithm
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/googleauth.rb
CHANGED
@@ -31,5 +31,6 @@ require "googleauth/application_default"
|
|
31
31
|
require "googleauth/client_id"
|
32
32
|
require "googleauth/credentials"
|
33
33
|
require "googleauth/default_credentials"
|
34
|
+
require "googleauth/id_tokens"
|
34
35
|
require "googleauth/user_authorizer"
|
35
36
|
require "googleauth/web_user_authorizer"
|
@@ -47,7 +47,7 @@ module Google
|
|
47
47
|
#
|
48
48
|
# Use this to obtain the Application Default Credentials for accessing
|
49
49
|
# Google APIs. Application Default Credentials are described in detail
|
50
|
-
# at
|
50
|
+
# at https://cloud.google.com/docs/authentication/production.
|
51
51
|
#
|
52
52
|
# If supplied, scope is used to create the credentials instance, when it can
|
53
53
|
# be applied. E.g, on google compute engine and for user credentials the
|
@@ -51,30 +51,26 @@ module Google
|
|
51
51
|
class GCECredentials < Signet::OAuth2::Client
|
52
52
|
# The IP Address is used in the URIs to speed up failures on non-GCE
|
53
53
|
# systems.
|
54
|
-
COMPUTE_AUTH_TOKEN_URI =
|
55
|
-
|
54
|
+
COMPUTE_AUTH_TOKEN_URI =
|
55
|
+
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
|
56
|
+
COMPUTE_ID_TOKEN_URI =
|
57
|
+
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity".freeze
|
56
58
|
COMPUTE_CHECK_URI = "http://169.254.169.254".freeze
|
57
59
|
|
58
60
|
class << self
|
59
61
|
extend Memoist
|
60
62
|
|
61
63
|
# Detect if this appear to be a GCE instance, by checking if metadata
|
62
|
-
# is available
|
64
|
+
# is available.
|
63
65
|
def on_gce? options = {}
|
66
|
+
# TODO: This should use google-cloud-env instead.
|
64
67
|
c = options[:connection] || Faraday.default_connection
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
# issue is that resolving an unknown host on some networks will take
|
70
|
-
# 20-30 seconds; making this timeout short fixes the issue, but
|
71
|
-
# could lead to false negatives in the event that we are on GCE, but
|
72
|
-
# the metadata resolution was particularly slow. The latter case is
|
73
|
-
# "unlikely".
|
74
|
-
req.options.timeout = 0.1
|
68
|
+
headers = { "Metadata-Flavor" => "Google" }
|
69
|
+
resp = c.get COMPUTE_CHECK_URI, nil, headers do |req|
|
70
|
+
req.options.timeout = 1.0
|
71
|
+
req.options.open_timeout = 0.1
|
75
72
|
end
|
76
73
|
return false unless resp.status == 200
|
77
|
-
return false unless resp.headers.key? "Metadata-Flavor"
|
78
74
|
resp.headers["Metadata-Flavor"] == "Google"
|
79
75
|
rescue Faraday::TimeoutError, Faraday::ConnectionFailed
|
80
76
|
false
|
@@ -88,12 +84,18 @@ module Google
|
|
88
84
|
def fetch_access_token options = {}
|
89
85
|
c = options[:connection] || Faraday.default_connection
|
90
86
|
retry_with_error do
|
87
|
+
uri = target_audience ? COMPUTE_ID_TOKEN_URI : COMPUTE_AUTH_TOKEN_URI
|
88
|
+
query = target_audience ? { "audience" => target_audience, "format" => "full" } : nil
|
91
89
|
headers = { "Metadata-Flavor" => "Google" }
|
92
|
-
resp = c.get
|
90
|
+
resp = c.get uri, query, headers
|
93
91
|
case resp.status
|
94
92
|
when 200
|
95
|
-
|
96
|
-
|
93
|
+
content_type = resp.headers["content-type"]
|
94
|
+
if content_type == "text/html"
|
95
|
+
{ (target_audience ? "id_token" : "access_token") => resp.body }
|
96
|
+
else
|
97
|
+
Signet::OAuth2.parse_credentials resp.body, content_type
|
98
|
+
end
|
97
99
|
when 404
|
98
100
|
raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
|
99
101
|
else
|
@@ -35,63 +35,294 @@ require "googleauth/credentials_loader"
|
|
35
35
|
|
36
36
|
module Google
|
37
37
|
module Auth
|
38
|
-
|
39
|
-
#
|
38
|
+
##
|
39
|
+
# Credentials is responsible for representing the authentication when connecting to an API. This
|
40
|
+
# class is also intended to be inherited by API-specific classes.
|
40
41
|
class Credentials
|
42
|
+
##
|
43
|
+
# The default token credential URI to be used when none is provided during initialization.
|
41
44
|
TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token".freeze
|
45
|
+
|
46
|
+
##
|
47
|
+
# The default target audience ID to be used when none is provided during initialization.
|
42
48
|
AUDIENCE = "https://oauth2.googleapis.com/token".freeze
|
43
|
-
SCOPE = [].freeze
|
44
|
-
PATH_ENV_VARS = [].freeze
|
45
|
-
JSON_ENV_VARS = [].freeze
|
46
|
-
DEFAULT_PATHS = [].freeze
|
47
49
|
|
50
|
+
@audience = @scope = @target_audience = @env_vars = @paths = nil
|
51
|
+
|
52
|
+
##
|
53
|
+
# The default token credential URI to be used when none is provided during initialization.
|
54
|
+
# The URI is the authorization server's HTTP endpoint capable of issuing tokens and
|
55
|
+
# refreshing expired tokens.
|
56
|
+
#
|
57
|
+
# @return [String]
|
58
|
+
#
|
59
|
+
def self.token_credential_uri
|
60
|
+
return @token_credential_uri unless @token_credential_uri.nil?
|
61
|
+
|
62
|
+
const_get :TOKEN_CREDENTIAL_URI if const_defined? :TOKEN_CREDENTIAL_URI
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Set the default token credential URI to be used when none is provided during initialization.
|
67
|
+
#
|
68
|
+
# @param [String] new_token_credential_uri
|
69
|
+
# @return [String]
|
70
|
+
#
|
71
|
+
def self.token_credential_uri= new_token_credential_uri
|
72
|
+
@token_credential_uri = new_token_credential_uri
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# The default target audience ID to be used when none is provided during initialization.
|
77
|
+
# Used only by the assertion grant type.
|
78
|
+
#
|
79
|
+
# @return [String]
|
80
|
+
#
|
81
|
+
def self.audience
|
82
|
+
return @audience unless @audience.nil?
|
83
|
+
|
84
|
+
const_get :AUDIENCE if const_defined? :AUDIENCE
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Sets the default target audience ID to be used when none is provided during initialization.
|
89
|
+
#
|
90
|
+
# @param [String] new_audience
|
91
|
+
# @return [String]
|
92
|
+
#
|
93
|
+
def self.audience= new_audience
|
94
|
+
@audience = new_audience
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# The default scope to be used when none is provided during initialization.
|
99
|
+
# A scope is an access range defined by the authorization server.
|
100
|
+
# The scope can be a single value or a list of values.
|
101
|
+
#
|
102
|
+
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
|
103
|
+
# If {#scope} is set, this credential will produce access tokens.
|
104
|
+
# If {#target_audience} is set, this credential will produce ID tokens.
|
105
|
+
#
|
106
|
+
# @return [String, Array<String>]
|
107
|
+
#
|
108
|
+
def self.scope
|
109
|
+
return @scope unless @scope.nil?
|
110
|
+
|
111
|
+
Array(const_get(:SCOPE)).flatten.uniq if const_defined? :SCOPE
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Sets the default scope to be used when none is provided during initialization.
|
116
|
+
#
|
117
|
+
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
|
118
|
+
# If {#scope} is set, this credential will produce access tokens.
|
119
|
+
# If {#target_audience} is set, this credential will produce ID tokens.
|
120
|
+
#
|
121
|
+
# @param [String, Array<String>] new_scope
|
122
|
+
# @return [String, Array<String>]
|
123
|
+
#
|
124
|
+
def self.scope= new_scope
|
125
|
+
new_scope = Array new_scope unless new_scope.nil?
|
126
|
+
@scope = new_scope
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# The default final target audience for ID tokens, to be used when none
|
131
|
+
# is provided during initialization.
|
132
|
+
#
|
133
|
+
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
|
134
|
+
# If {#scope} is set, this credential will produce access tokens.
|
135
|
+
# If {#target_audience} is set, this credential will produce ID tokens.
|
136
|
+
#
|
137
|
+
# @return [String]
|
138
|
+
#
|
139
|
+
def self.target_audience
|
140
|
+
@target_audience
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Sets the default final target audience for ID tokens, to be used when none
|
145
|
+
# is provided during initialization.
|
146
|
+
#
|
147
|
+
# Either {#scope} or {#target_audience}, but not both, should be non-nil.
|
148
|
+
# If {#scope} is set, this credential will produce access tokens.
|
149
|
+
# If {#target_audience} is set, this credential will produce ID tokens.
|
150
|
+
#
|
151
|
+
# @param [String] new_target_audience
|
152
|
+
#
|
153
|
+
def self.target_audience= new_target_audience
|
154
|
+
@target_audience = new_target_audience
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# The environment variables to search for credentials. Values can either be a file path to the
|
159
|
+
# credentials file, or the JSON contents of the credentials file.
|
160
|
+
#
|
161
|
+
# @return [Array<String>]
|
162
|
+
#
|
163
|
+
def self.env_vars
|
164
|
+
return @env_vars unless @env_vars.nil?
|
165
|
+
|
166
|
+
# Pull values when PATH_ENV_VARS or JSON_ENV_VARS constants exists.
|
167
|
+
tmp_env_vars = []
|
168
|
+
tmp_env_vars << const_get(:PATH_ENV_VARS) if const_defined? :PATH_ENV_VARS
|
169
|
+
tmp_env_vars << const_get(:JSON_ENV_VARS) if const_defined? :JSON_ENV_VARS
|
170
|
+
tmp_env_vars.flatten.uniq
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# Sets the environment variables to search for credentials.
|
175
|
+
#
|
176
|
+
# @param [Array<String>] new_env_vars
|
177
|
+
# @return [Array<String>]
|
178
|
+
#
|
179
|
+
def self.env_vars= new_env_vars
|
180
|
+
new_env_vars = Array new_env_vars unless new_env_vars.nil?
|
181
|
+
@env_vars = new_env_vars
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# The file paths to search for credentials files.
|
186
|
+
#
|
187
|
+
# @return [Array<String>]
|
188
|
+
#
|
189
|
+
def self.paths
|
190
|
+
return @paths unless @paths.nil?
|
191
|
+
|
192
|
+
tmp_paths = []
|
193
|
+
# Pull in values is the DEFAULT_PATHS constant exists.
|
194
|
+
tmp_paths << const_get(:DEFAULT_PATHS) if const_defined? :DEFAULT_PATHS
|
195
|
+
tmp_paths.flatten.uniq
|
196
|
+
end
|
197
|
+
|
198
|
+
##
|
199
|
+
# Set the file paths to search for credentials files.
|
200
|
+
#
|
201
|
+
# @param [Array<String>] new_paths
|
202
|
+
# @return [Array<String>]
|
203
|
+
#
|
204
|
+
def self.paths= new_paths
|
205
|
+
new_paths = Array new_paths unless new_paths.nil?
|
206
|
+
@paths = new_paths
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# The Signet::OAuth2::Client object the Credentials instance is using.
|
211
|
+
#
|
212
|
+
# @return [Signet::OAuth2::Client]
|
213
|
+
#
|
48
214
|
attr_accessor :client
|
49
|
-
attr_reader :project_id
|
50
215
|
|
51
|
-
|
216
|
+
##
|
217
|
+
# Identifier for the project the client is authenticating with.
|
218
|
+
#
|
219
|
+
# @return [String]
|
220
|
+
#
|
221
|
+
attr_reader :project_id
|
222
|
+
|
223
|
+
##
|
224
|
+
# Identifier for a separate project used for billing/quota, if any.
|
225
|
+
#
|
226
|
+
# @return [String,nil]
|
227
|
+
#
|
228
|
+
attr_reader :quota_project_id
|
229
|
+
|
230
|
+
# @private Delegate client methods to the client object.
|
52
231
|
extend Forwardable
|
232
|
+
|
233
|
+
##
|
234
|
+
# @!attribute [r] token_credential_uri
|
235
|
+
# @return [String] The token credential URI. The URI is the authorization server's HTTP
|
236
|
+
# endpoint capable of issuing tokens and refreshing expired tokens.
|
237
|
+
#
|
238
|
+
# @!attribute [r] audience
|
239
|
+
# @return [String] The target audience ID when issuing assertions. Used only by the
|
240
|
+
# assertion grant type.
|
241
|
+
#
|
242
|
+
# @!attribute [r] scope
|
243
|
+
# @return [String, Array<String>] The scope for this client. A scope is an access range
|
244
|
+
# defined by the authorization server. The scope can be a single value or a list of values.
|
245
|
+
#
|
246
|
+
# @!attribute [r] target_audience
|
247
|
+
# @return [String] The final target audience for ID tokens returned by this credential.
|
248
|
+
#
|
249
|
+
# @!attribute [r] issuer
|
250
|
+
# @return [String] The issuer ID associated with this client.
|
251
|
+
#
|
252
|
+
# @!attribute [r] signing_key
|
253
|
+
# @return [String, OpenSSL::PKey] The signing key associated with this client.
|
254
|
+
#
|
255
|
+
# @!attribute [r] updater_proc
|
256
|
+
# @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method,
|
257
|
+
# suitable for passing as a closure.
|
258
|
+
#
|
53
259
|
def_delegators :@client,
|
54
260
|
:token_credential_uri, :audience,
|
55
|
-
:scope, :issuer, :signing_key, :updater_proc
|
261
|
+
:scope, :issuer, :signing_key, :updater_proc, :target_audience
|
56
262
|
|
57
|
-
|
263
|
+
##
|
264
|
+
# Creates a new Credentials instance with the provided auth credentials, and with the default
|
265
|
+
# values configured on the class.
|
266
|
+
#
|
267
|
+
# @param [String, Hash, Signet::OAuth2::Client] keyfile
|
268
|
+
# The keyfile can be provided as one of the following:
|
269
|
+
#
|
270
|
+
# * The path to a JSON keyfile (as a +String+)
|
271
|
+
# * The contents of a JSON keyfile (as a +Hash+)
|
272
|
+
# * A +Signet::OAuth2::Client+ object
|
273
|
+
# @param [Hash] options
|
274
|
+
# The options for configuring the credentials instance. The following is supported:
|
275
|
+
#
|
276
|
+
# * +:scope+ - the scope for the client
|
277
|
+
# * +"project_id"+ (and optionally +"project"+) - the project identifier for the client
|
278
|
+
# * +:connection_builder+ - the connection builder to use for the client
|
279
|
+
# * +:default_connection+ - the default connection to use for the client
|
280
|
+
#
|
58
281
|
def initialize keyfile, options = {}
|
59
|
-
scope = options[:scope]
|
60
282
|
verify_keyfile_provided! keyfile
|
61
283
|
@project_id = options["project_id"] || options["project"]
|
284
|
+
@quota_project_id = options["quota_project_id"]
|
62
285
|
if keyfile.is_a? Signet::OAuth2::Client
|
63
|
-
|
64
|
-
@project_id ||= keyfile.project_id if keyfile.respond_to? :project_id
|
286
|
+
update_from_signet keyfile
|
65
287
|
elsif keyfile.is_a? Hash
|
66
|
-
|
67
|
-
hash["scope"] ||= scope
|
68
|
-
@client = init_client hash, options
|
69
|
-
@project_id ||= (hash["project_id"] || hash["project"])
|
288
|
+
update_from_hash keyfile, options
|
70
289
|
else
|
71
|
-
|
72
|
-
json = JSON.parse ::File.read(keyfile)
|
73
|
-
json["scope"] ||= scope
|
74
|
-
@project_id ||= (json["project_id"] || json["project"])
|
75
|
-
@client = init_client json, options
|
290
|
+
update_from_filepath keyfile, options
|
76
291
|
end
|
77
292
|
CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id
|
78
293
|
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
79
294
|
@client.fetch_access_token!
|
295
|
+
@env_vars = nil
|
296
|
+
@paths = nil
|
297
|
+
@scope = nil
|
80
298
|
end
|
81
|
-
# rubocop:enable Metrics/AbcSize
|
82
299
|
|
83
|
-
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
300
|
+
##
|
301
|
+
# Creates a new Credentials instance with auth credentials acquired by searching the
|
302
|
+
# environment variables and paths configured on the class, and with the default values
|
303
|
+
# configured on the class.
|
304
|
+
#
|
305
|
+
# The auth credentials are searched for in the following order:
|
306
|
+
#
|
307
|
+
# 1. configured environment variables (see {Credentials.env_vars})
|
308
|
+
# 2. configured default file paths (see {Credentials.paths})
|
309
|
+
# 3. application default (see {Google::Auth.get_application_default})
|
310
|
+
#
|
311
|
+
# @param [Hash] options
|
312
|
+
# The options for configuring the credentials instance. The following is supported:
|
313
|
+
#
|
314
|
+
# * +:scope+ - the scope for the client
|
315
|
+
# * +"project_id"+ (and optionally +"project"+) - the project identifier for the client
|
316
|
+
# * +:connection_builder+ - the connection builder to use for the client
|
317
|
+
# * +:default_connection+ - the default connection to use for the client
|
318
|
+
#
|
319
|
+
# @return [Credentials]
|
320
|
+
#
|
87
321
|
def self.default options = {}
|
88
|
-
# First try to find keyfile file from environment variables.
|
89
|
-
client =
|
90
|
-
|
91
|
-
# Second try to find keyfile json from environment variables.
|
92
|
-
client ||= from_json_vars options
|
322
|
+
# First try to find keyfile file or json from environment variables.
|
323
|
+
client = from_env_vars options
|
93
324
|
|
94
|
-
#
|
325
|
+
# Second try to find keyfile file from known file paths.
|
95
326
|
client ||= from_default_paths options
|
96
327
|
|
97
328
|
# Finally get instantiated client from Google::Auth
|
@@ -99,33 +330,22 @@ module Google
|
|
99
330
|
client
|
100
331
|
end
|
101
332
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
nil
|
111
|
-
end
|
112
|
-
|
113
|
-
def self.from_json_vars options
|
114
|
-
json = lambda do |v|
|
115
|
-
unless ENV[v].nil?
|
116
|
-
begin
|
117
|
-
JSON.parse ENV[v]
|
118
|
-
rescue StandardError
|
119
|
-
nil
|
120
|
-
end
|
121
|
-
end
|
333
|
+
##
|
334
|
+
# @private Lookup Credentials from environment variables.
|
335
|
+
def self.from_env_vars options
|
336
|
+
env_vars.each do |env_var|
|
337
|
+
str = ENV[env_var]
|
338
|
+
next if str.nil?
|
339
|
+
return new str, options if ::File.file? str
|
340
|
+
return new ::JSON.parse(str), options rescue nil
|
122
341
|
end
|
123
|
-
self::JSON_ENV_VARS.map(&json).compact.each { |hash| return new hash, options }
|
124
342
|
nil
|
125
343
|
end
|
126
344
|
|
345
|
+
##
|
346
|
+
# @private Lookup Credentials from default file paths.
|
127
347
|
def self.from_default_paths options
|
128
|
-
|
348
|
+
paths
|
129
349
|
.select { |p| ::File.file? p }
|
130
350
|
.each do |file|
|
131
351
|
return new file, options
|
@@ -133,13 +353,16 @@ module Google
|
|
133
353
|
nil
|
134
354
|
end
|
135
355
|
|
356
|
+
##
|
357
|
+
# @private Lookup Credentials using Google::Auth.get_application_default.
|
136
358
|
def self.from_application_default options
|
137
|
-
scope = options[:scope] || self
|
138
|
-
|
359
|
+
scope = options[:scope] || self.scope
|
360
|
+
auth_opts = { target_audience: options[:target_audience] || target_audience }
|
361
|
+
client = Google::Auth.get_application_default scope, auth_opts
|
139
362
|
new client, options
|
140
363
|
end
|
141
|
-
|
142
|
-
|
364
|
+
|
365
|
+
private_class_method :from_env_vars,
|
143
366
|
:from_default_paths,
|
144
367
|
:from_application_default
|
145
368
|
|
@@ -171,17 +394,49 @@ module Google
|
|
171
394
|
|
172
395
|
def client_options options
|
173
396
|
# Keyfile options have higher priority over constructor defaults
|
174
|
-
options["token_credential_uri"] ||= self.class
|
175
|
-
options["audience"] ||= self.class
|
176
|
-
options["scope"] ||= self.class
|
397
|
+
options["token_credential_uri"] ||= self.class.token_credential_uri
|
398
|
+
options["audience"] ||= self.class.audience
|
399
|
+
options["scope"] ||= self.class.scope
|
400
|
+
options["target_audience"] ||= self.class.target_audience
|
401
|
+
|
402
|
+
if !Array(options["scope"]).empty? && options["target_audience"]
|
403
|
+
raise ArgumentError, "Cannot specify both scope and target_audience"
|
404
|
+
end
|
177
405
|
|
406
|
+
needs_scope = options["target_audience"].nil?
|
178
407
|
# client options for initializing signet client
|
179
408
|
{ token_credential_uri: options["token_credential_uri"],
|
180
409
|
audience: options["audience"],
|
181
|
-
scope: Array(options["scope"]),
|
410
|
+
scope: (needs_scope ? Array(options["scope"]) : nil),
|
411
|
+
target_audience: options["target_audience"],
|
182
412
|
issuer: options["client_email"],
|
183
413
|
signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
|
184
414
|
end
|
415
|
+
|
416
|
+
def update_from_signet client
|
417
|
+
@project_id ||= client.project_id if client.respond_to? :project_id
|
418
|
+
@quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id
|
419
|
+
@client = client
|
420
|
+
end
|
421
|
+
|
422
|
+
def update_from_hash hash, options
|
423
|
+
hash = stringify_hash_keys hash
|
424
|
+
hash["scope"] ||= options[:scope]
|
425
|
+
hash["target_audience"] ||= options[:target_audience]
|
426
|
+
@project_id ||= (hash["project_id"] || hash["project"])
|
427
|
+
@quota_project_id ||= hash["quota_project_id"]
|
428
|
+
@client = init_client hash, options
|
429
|
+
end
|
430
|
+
|
431
|
+
def update_from_filepath path, options
|
432
|
+
verify_keyfile_exists! path
|
433
|
+
json = JSON.parse ::File.read(path)
|
434
|
+
json["scope"] ||= options[:scope]
|
435
|
+
json["target_audience"] ||= options[:target_audience]
|
436
|
+
@project_id ||= (json["project_id"] || json["project"])
|
437
|
+
@quota_project_id ||= json["quota_project_id"]
|
438
|
+
@client = init_client json, options
|
439
|
+
end
|
185
440
|
end
|
186
441
|
end
|
187
442
|
end
|