googleauth 1.14.0 → 1.16.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/CHANGELOG.md +30 -0
- data/Credentials.md +110 -0
- data/Errors.md +152 -0
- data/README.md +0 -1
- data/lib/googleauth/api_key.rb +9 -0
- data/lib/googleauth/application_default.rb +3 -1
- data/lib/googleauth/base_client.rb +5 -0
- data/lib/googleauth/bearer_token.rb +16 -2
- data/lib/googleauth/client_id.rb +9 -5
- data/lib/googleauth/compute_engine.rb +64 -18
- data/lib/googleauth/credentials.rb +67 -35
- data/lib/googleauth/credentials_loader.rb +24 -4
- data/lib/googleauth/default_credentials.rb +67 -32
- data/lib/googleauth/errors.rb +117 -0
- data/lib/googleauth/external_account/aws_credentials.rb +85 -18
- data/lib/googleauth/external_account/base_credentials.rb +31 -2
- data/lib/googleauth/external_account/external_account_utils.rb +15 -4
- data/lib/googleauth/external_account/identity_pool_credentials.rb +40 -15
- data/lib/googleauth/external_account/pluggable_credentials.rb +34 -19
- data/lib/googleauth/external_account.rb +44 -6
- data/lib/googleauth/iam.rb +19 -3
- data/lib/googleauth/id_tokens/errors.rb +13 -7
- data/lib/googleauth/id_tokens/key_sources.rb +13 -7
- data/lib/googleauth/id_tokens/verifier.rb +2 -3
- data/lib/googleauth/id_tokens.rb +4 -4
- data/lib/googleauth/impersonated_service_account.rb +117 -18
- data/lib/googleauth/json_key_reader.rb +11 -2
- data/lib/googleauth/oauth2/sts_client.rb +9 -4
- data/lib/googleauth/scope_util.rb +1 -1
- data/lib/googleauth/service_account.rb +37 -10
- data/lib/googleauth/service_account_jwt_header.rb +9 -2
- data/lib/googleauth/signet.rb +24 -6
- data/lib/googleauth/user_authorizer.rb +35 -7
- data/lib/googleauth/user_refresh.rb +42 -16
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +46 -9
- data/lib/googleauth.rb +1 -0
- metadata +8 -5
data/lib/googleauth/iam.rb
CHANGED
|
@@ -27,8 +27,9 @@ module Google
|
|
|
27
27
|
|
|
28
28
|
# Initializes an IAMCredentials.
|
|
29
29
|
#
|
|
30
|
-
# @param selector
|
|
31
|
-
# @param token
|
|
30
|
+
# @param selector [String] The IAM selector.
|
|
31
|
+
# @param token [String] The IAM token.
|
|
32
|
+
# @raise [TypeError] If selector or token is not a String
|
|
32
33
|
def initialize selector, token
|
|
33
34
|
raise TypeError unless selector.is_a? String
|
|
34
35
|
raise TypeError unless token.is_a? String
|
|
@@ -37,13 +38,19 @@ module Google
|
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
# Adds the credential fields to the hash.
|
|
41
|
+
#
|
|
42
|
+
# @param a_hash [Hash] The hash to update with credentials
|
|
43
|
+
# @return [Hash] The updated hash with credentials
|
|
40
44
|
def apply! a_hash
|
|
41
45
|
a_hash[SELECTOR_KEY] = @selector
|
|
42
46
|
a_hash[TOKEN_KEY] = @token
|
|
43
47
|
a_hash
|
|
44
48
|
end
|
|
45
49
|
|
|
46
|
-
# Returns a clone of a_hash updated with the
|
|
50
|
+
# Returns a clone of a_hash updated with the authorization header
|
|
51
|
+
#
|
|
52
|
+
# @param a_hash [Hash] The hash to clone and update with credentials
|
|
53
|
+
# @return [Hash] A new hash with credentials
|
|
47
54
|
def apply a_hash
|
|
48
55
|
a_copy = a_hash.clone
|
|
49
56
|
apply! a_copy
|
|
@@ -52,9 +59,18 @@ module Google
|
|
|
52
59
|
|
|
53
60
|
# Returns a reference to the #apply method, suitable for passing as
|
|
54
61
|
# a closure
|
|
62
|
+
#
|
|
63
|
+
# @return [Proc] A procedure that updates a hash with credentials
|
|
55
64
|
def updater_proc
|
|
56
65
|
proc { |a_hash, _opts = {}| apply a_hash }
|
|
57
66
|
end
|
|
67
|
+
|
|
68
|
+
# Returns the IAM authority selector as the principal
|
|
69
|
+
# @private
|
|
70
|
+
# @return [String] the IAM authoirty selector
|
|
71
|
+
def principal
|
|
72
|
+
@selector
|
|
73
|
+
end
|
|
58
74
|
end
|
|
59
75
|
end
|
|
60
76
|
end
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
|
+
require "googleauth/errors"
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
module Google
|
|
19
21
|
module Auth
|
|
@@ -21,35 +23,39 @@ module Google
|
|
|
21
23
|
##
|
|
22
24
|
# Failed to obtain keys from the key source.
|
|
23
25
|
#
|
|
24
|
-
class KeySourceError < StandardError
|
|
26
|
+
class KeySourceError < StandardError
|
|
27
|
+
include Google::Auth::Error
|
|
28
|
+
end
|
|
25
29
|
|
|
26
30
|
##
|
|
27
31
|
# Failed to verify a token.
|
|
28
32
|
#
|
|
29
|
-
class VerificationError < StandardError
|
|
33
|
+
class VerificationError < StandardError
|
|
34
|
+
include Google::Auth::Error
|
|
35
|
+
end
|
|
30
36
|
|
|
31
37
|
##
|
|
32
|
-
# Failed to verify
|
|
38
|
+
# Failed to verify token because it is expired.
|
|
33
39
|
#
|
|
34
40
|
class ExpiredTokenError < VerificationError; end
|
|
35
41
|
|
|
36
42
|
##
|
|
37
|
-
# Failed to verify
|
|
43
|
+
# Failed to verify token because its signature did not match.
|
|
38
44
|
#
|
|
39
45
|
class SignatureError < VerificationError; end
|
|
40
46
|
|
|
41
47
|
##
|
|
42
|
-
# Failed to verify
|
|
48
|
+
# Failed to verify token because its issuer did not match.
|
|
43
49
|
#
|
|
44
50
|
class IssuerMismatchError < VerificationError; end
|
|
45
51
|
|
|
46
52
|
##
|
|
47
|
-
# Failed to verify
|
|
53
|
+
# Failed to verify token because its audience did not match.
|
|
48
54
|
#
|
|
49
55
|
class AudienceMismatchError < VerificationError; end
|
|
50
56
|
|
|
51
57
|
##
|
|
52
|
-
# Failed to verify
|
|
58
|
+
# Failed to verify token because its authorized party did not match.
|
|
53
59
|
#
|
|
54
60
|
class AuthorizedPartyMismatchError < VerificationError; end
|
|
55
61
|
end
|
|
@@ -72,8 +72,8 @@ module Google
|
|
|
72
72
|
#
|
|
73
73
|
# @param jwk [Hash,String] The JWK specification.
|
|
74
74
|
# @return [KeyInfo]
|
|
75
|
-
# @raise [KeySourceError] If the key could not be extracted from the
|
|
76
|
-
# JWK.
|
|
75
|
+
# @raise [Google::Auth::IDTokens::KeySourceError] If the key could not be extracted from the
|
|
76
|
+
# JWK due to invalid type, malformed JSON, or invalid key data.
|
|
77
77
|
#
|
|
78
78
|
def from_jwk jwk
|
|
79
79
|
jwk = symbolize_keys ensure_json_parsed jwk
|
|
@@ -94,10 +94,10 @@ module Google
|
|
|
94
94
|
# Create an array of KeyInfo from a JWK Set, which may be given as
|
|
95
95
|
# either a hash or an unparsed JSON string.
|
|
96
96
|
#
|
|
97
|
-
# @param
|
|
97
|
+
# @param jwk_set [Hash,String] The JWK Set specification.
|
|
98
98
|
# @return [Array<KeyInfo>]
|
|
99
|
-
# @raise [KeySourceError] If a key could not be extracted from the
|
|
100
|
-
# JWK Set.
|
|
99
|
+
# @raise [Google::Auth::IDTokens::KeySourceError] If a key could not be extracted from the
|
|
100
|
+
# JWK Set, or if the set contains no keys.
|
|
101
101
|
#
|
|
102
102
|
def from_jwk_set jwk_set
|
|
103
103
|
jwk_set = symbolize_keys ensure_json_parsed jwk_set
|
|
@@ -261,7 +261,8 @@ module Google
|
|
|
261
261
|
# return the new keys.
|
|
262
262
|
#
|
|
263
263
|
# @return [Array<KeyInfo>]
|
|
264
|
-
# @raise [KeySourceError]
|
|
264
|
+
# @raise [Google::Auth::IDTokens::KeySourceError] If key retrieval fails, JSON parsing
|
|
265
|
+
# fails, or the data cannot be interpreted as keys
|
|
265
266
|
#
|
|
266
267
|
def refresh_keys
|
|
267
268
|
@monitor.synchronize do
|
|
@@ -310,6 +311,11 @@ module Google
|
|
|
310
311
|
|
|
311
312
|
protected
|
|
312
313
|
|
|
314
|
+
# Interpret JSON data as X509 certificates
|
|
315
|
+
#
|
|
316
|
+
# @param data [Hash] The JSON data containing certificate strings
|
|
317
|
+
# @return [Array<KeyInfo>] Array of key info objects
|
|
318
|
+
# @raise [Google::Auth::IDTokens::KeySourceError] If X509 certificates cannot be parsed
|
|
313
319
|
def interpret_json data
|
|
314
320
|
data.map do |id, cert_str|
|
|
315
321
|
key = OpenSSL::X509::Certificate.new(cert_str).public_key
|
|
@@ -371,7 +377,7 @@ module Google
|
|
|
371
377
|
# Attempt to refresh keys and return the new keys.
|
|
372
378
|
#
|
|
373
379
|
# @return [Array<KeyInfo>]
|
|
374
|
-
# @raise [KeySourceError]
|
|
380
|
+
# @raise [Google::Auth::IDTokens::KeySourceError] If key retrieval failed for any source.
|
|
375
381
|
#
|
|
376
382
|
def refresh_keys
|
|
377
383
|
@sources.flat_map(&:refresh_keys)
|
|
@@ -61,10 +61,9 @@ module Google
|
|
|
61
61
|
# @param iss [String,nil] If given, override the `iss` check.
|
|
62
62
|
#
|
|
63
63
|
# @return [Hash] the decoded payload, if verification succeeded.
|
|
64
|
-
# @raise [KeySourceError] if the key source failed to obtain public keys
|
|
65
|
-
# @raise [VerificationError] if the token verification failed.
|
|
64
|
+
# @raise [Google::Auth::IDTokens::KeySourceError] if the key source failed to obtain public keys
|
|
65
|
+
# @raise [Google::Auth::IDTokens::VerificationError] if the token verification failed.
|
|
66
66
|
# Additional data may be available in the error subclass and message.
|
|
67
|
-
#
|
|
68
67
|
def verify token,
|
|
69
68
|
key_source: :default,
|
|
70
69
|
aud: :default,
|
data/lib/googleauth/id_tokens.rb
CHANGED
|
@@ -160,8 +160,8 @@ module Google
|
|
|
160
160
|
# checking is performed. Default is to check against {OIDC_ISSUERS}.
|
|
161
161
|
#
|
|
162
162
|
# @return [Hash] The decoded token payload.
|
|
163
|
-
# @raise [KeySourceError] if the key source failed to obtain public keys
|
|
164
|
-
# @raise [VerificationError] if the token verification failed.
|
|
163
|
+
# @raise [Google::Auth::IDTokens::KeySourceError] if the key source failed to obtain public keys
|
|
164
|
+
# @raise [Google::Auth::IDTokens::VerificationError] if the token verification failed.
|
|
165
165
|
# Additional data may be available in the error subclass and message.
|
|
166
166
|
#
|
|
167
167
|
def verify_oidc token,
|
|
@@ -197,8 +197,8 @@ module Google
|
|
|
197
197
|
# checking is performed. Default is to check against {IAP_ISSUERS}.
|
|
198
198
|
#
|
|
199
199
|
# @return [Hash] The decoded token payload.
|
|
200
|
-
# @raise [KeySourceError] if the key source failed to obtain public keys
|
|
201
|
-
# @raise [VerificationError] if the token verification failed.
|
|
200
|
+
# @raise [Google::Auth::IDTokens::KeySourceError] if the key source failed to obtain public keys
|
|
201
|
+
# @raise [Google::Auth::IDTokens::VerificationError] if the token verification failed.
|
|
202
202
|
# Additional data may be available in the error subclass and message.
|
|
203
203
|
#
|
|
204
204
|
def verify_iap token,
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
require "googleauth/signet"
|
|
16
15
|
require "googleauth/base_client"
|
|
16
|
+
require "googleauth/errors"
|
|
17
17
|
require "googleauth/helpers/connection"
|
|
18
18
|
|
|
19
19
|
module Google
|
|
@@ -23,6 +23,9 @@ module Google
|
|
|
23
23
|
# and then that claim is exchanged for a short-lived token at an IAMCredentials endpoint.
|
|
24
24
|
# The short-lived token and its expiration time are cached.
|
|
25
25
|
class ImpersonatedServiceAccountCredentials
|
|
26
|
+
# @private
|
|
27
|
+
CREDENTIAL_TYPE_NAME = "impersonated_service_account".freeze
|
|
28
|
+
|
|
26
29
|
# @private
|
|
27
30
|
ERROR_SUFFIX = <<~ERROR.freeze
|
|
28
31
|
when trying to get security access token
|
|
@@ -69,6 +72,15 @@ module Google
|
|
|
69
72
|
# and request short-lived credentials for a service account
|
|
70
73
|
# that has the authorization that your use case requires.
|
|
71
74
|
#
|
|
75
|
+
# @note Warning:
|
|
76
|
+
# This method does not validate the credential configuration. A security
|
|
77
|
+
# risk occurs when a credential configuration configured with malicious urls
|
|
78
|
+
# is used.
|
|
79
|
+
# When the credential configuration is accepted from an
|
|
80
|
+
# untrusted source, you should validate it before using with this method.
|
|
81
|
+
# See https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
|
|
82
|
+
# for more details.
|
|
83
|
+
#
|
|
72
84
|
# @param options [Hash] A hash of options to configure the credentials.
|
|
73
85
|
# @option options [Object] :base_credentials (required) The authenticated principal.
|
|
74
86
|
# It will be used as following:
|
|
@@ -84,12 +96,51 @@ module Google
|
|
|
84
96
|
# defining the permissions required for the token.
|
|
85
97
|
# @option options [Object] :source_credentials The authenticated principal that will be used
|
|
86
98
|
# to fetch the short-lived impersonation access token. It is an alternative to providing the base credentials.
|
|
99
|
+
# @option options [IO] :json_key_io The IO object that contains the credential configuration.
|
|
100
|
+
# It is exclusive with `:base_credentials` and `:source_credentials` options.
|
|
87
101
|
#
|
|
88
102
|
# @return [Google::Auth::ImpersonatedServiceAccountCredentials]
|
|
89
103
|
def self.make_creds options = {}
|
|
90
|
-
|
|
104
|
+
if options[:json_key_io]
|
|
105
|
+
make_creds_from_json options
|
|
106
|
+
else
|
|
107
|
+
new options
|
|
108
|
+
end
|
|
91
109
|
end
|
|
92
110
|
|
|
111
|
+
# @private
|
|
112
|
+
def self.make_creds_from_json options
|
|
113
|
+
json_key_io = options[:json_key_io]
|
|
114
|
+
if options[:base_credentials] || options[:source_credentials]
|
|
115
|
+
raise Google::Auth::InitializationError,
|
|
116
|
+
"json_key_io is not compatible with base_credentials or source_credentials"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
require "googleauth/default_credentials"
|
|
120
|
+
impersonated_json = MultiJson.load json_key_io.read
|
|
121
|
+
source_credentials_info = impersonated_json["source_credentials"]
|
|
122
|
+
|
|
123
|
+
if source_credentials_info["type"] == CREDENTIAL_TYPE_NAME
|
|
124
|
+
raise Google::Auth::InitializationError,
|
|
125
|
+
"Source credentials can't be of type impersonated_service_account, " \
|
|
126
|
+
"use delegates to chain impersonation."
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
source_credentials = DefaultCredentials.make_creds(
|
|
130
|
+
json_key_io: StringIO.new(MultiJson.dump(source_credentials_info))
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
impersonation_url = impersonated_json["service_account_impersonation_url"]
|
|
134
|
+
scope = options[:scope] || impersonated_json["scopes"]
|
|
135
|
+
|
|
136
|
+
new(
|
|
137
|
+
source_credentials: source_credentials,
|
|
138
|
+
impersonation_url: impersonation_url,
|
|
139
|
+
scope: scope
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
private_class_method :make_creds_from_json
|
|
143
|
+
|
|
93
144
|
# Initializes a new instance of ImpersonatedServiceAccountCredentials.
|
|
94
145
|
#
|
|
95
146
|
# @param options [Hash] A hash of options to configure the credentials.
|
|
@@ -105,6 +156,7 @@ module Google
|
|
|
105
156
|
# - `{source_sa_email}` is the email address of the service account to impersonate.
|
|
106
157
|
# @option options [Array<String>, String] :scope (required) The scope(s) for the short-lived impersonation token,
|
|
107
158
|
# defining the permissions required for the token.
|
|
159
|
+
# It will override the scope from the `json_key_io` file if provided.
|
|
108
160
|
# @option options [Object] :source_credentials The authenticated principal that will be used
|
|
109
161
|
# to fetch the short-lived impersonation access token. It is an alternative to providing the base credentials.
|
|
110
162
|
# It is redundant to provide both source and base credentials as only source will be used,
|
|
@@ -191,6 +243,20 @@ module Google
|
|
|
191
243
|
self.class.new options
|
|
192
244
|
end
|
|
193
245
|
|
|
246
|
+
# The principal behind the credentials. This class allows custom source credentials type
|
|
247
|
+
# that might not implement `principal`, in which case `:unknown` is returned.
|
|
248
|
+
#
|
|
249
|
+
# @private
|
|
250
|
+
# @return [String, Symbol] The string representation of the principal,
|
|
251
|
+
# the token type in lieu of the principal, or :unknown if source principal is unknown.
|
|
252
|
+
def principal
|
|
253
|
+
if @source_credentials.respond_to? :principal
|
|
254
|
+
@source_credentials.principal
|
|
255
|
+
else
|
|
256
|
+
:unknown
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
194
260
|
private
|
|
195
261
|
|
|
196
262
|
# Generates a new impersonation access token by exchanging the source credentials' token
|
|
@@ -200,21 +266,16 @@ module Google
|
|
|
200
266
|
# for an impersonation token using the specified impersonation URL. The generated token and
|
|
201
267
|
# its expiration time are cached for subsequent use.
|
|
202
268
|
#
|
|
269
|
+
# @private
|
|
203
270
|
# @param _options [Hash] (optional) Additional options for token retrieval (currently unused).
|
|
204
271
|
#
|
|
205
|
-
# @raise [
|
|
206
|
-
# @raise [
|
|
272
|
+
# @raise [Google::Auth::UnexpectedStatusError] If the response status is 403 or 500.
|
|
273
|
+
# @raise [Google::Auth::AuthorizationError] For other unexpected response statuses.
|
|
207
274
|
#
|
|
208
275
|
# @return [String] The newly generated impersonation access token.
|
|
209
276
|
def fetch_access_token! _options = {}
|
|
210
|
-
auth_header =
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
resp = connection.post @impersonation_url do |req|
|
|
214
|
-
req.headers.merge! auth_header
|
|
215
|
-
req.headers["Content-Type"] = "application/json"
|
|
216
|
-
req.body = MultiJson.dump({ scope: @scope })
|
|
217
|
-
end
|
|
277
|
+
auth_header = prepare_auth_header
|
|
278
|
+
resp = make_impersonation_request auth_header
|
|
218
279
|
|
|
219
280
|
case resp.status
|
|
220
281
|
when 200
|
|
@@ -223,14 +284,51 @@ module Google
|
|
|
223
284
|
@access_token = response["accessToken"]
|
|
224
285
|
access_token
|
|
225
286
|
when 403, 500
|
|
226
|
-
|
|
227
|
-
raise Signet::UnexpectedStatusError, msg
|
|
287
|
+
handle_error_response resp, UnexpectedStatusError
|
|
228
288
|
else
|
|
229
|
-
|
|
230
|
-
|
|
289
|
+
handle_error_response resp, AuthorizationError
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Prepares the authorization header for the impersonation request
|
|
294
|
+
# by fetching a token from source credentials.
|
|
295
|
+
#
|
|
296
|
+
# @private
|
|
297
|
+
# @return [Hash] The authorization header with the source credentials' token
|
|
298
|
+
def prepare_auth_header
|
|
299
|
+
auth_header = {}
|
|
300
|
+
@source_credentials.updater_proc.call auth_header
|
|
301
|
+
auth_header
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Makes the HTTP request to the impersonation endpoint.
|
|
305
|
+
#
|
|
306
|
+
# @private
|
|
307
|
+
# @param [Hash] auth_header The authorization header containing the source token
|
|
308
|
+
# @return [Faraday::Response] The HTTP response from the impersonation endpoint
|
|
309
|
+
def make_impersonation_request auth_header
|
|
310
|
+
connection.post @impersonation_url do |req|
|
|
311
|
+
req.headers.merge! auth_header
|
|
312
|
+
req.headers["Content-Type"] = "application/json"
|
|
313
|
+
req.body = MultiJson.dump({ scope: @scope })
|
|
231
314
|
end
|
|
232
315
|
end
|
|
233
316
|
|
|
317
|
+
# Creates and raises an appropriate error based on the response.
|
|
318
|
+
#
|
|
319
|
+
# @private
|
|
320
|
+
# @param [Faraday::Response] resp The HTTP response
|
|
321
|
+
# @param [Class] error_class The error class to instantiate
|
|
322
|
+
# @raise [StandardError] The appropriate error with details
|
|
323
|
+
def handle_error_response resp, error_class
|
|
324
|
+
msg = "Unexpected error code #{resp.status}.\n #{resp.env.response_body} #{ERROR_SUFFIX}"
|
|
325
|
+
raise error_class.with_details(
|
|
326
|
+
msg,
|
|
327
|
+
credential_type_name: self.class.name,
|
|
328
|
+
principal: principal
|
|
329
|
+
)
|
|
330
|
+
end
|
|
331
|
+
|
|
234
332
|
# Setter for the expires_at value that makes sure it is converted
|
|
235
333
|
# to Time object.
|
|
236
334
|
def expires_at= new_expires_at
|
|
@@ -249,7 +347,7 @@ module Google
|
|
|
249
347
|
#
|
|
250
348
|
# @return [Time, nil] The normalized Time object, or nil if the input is nil.
|
|
251
349
|
#
|
|
252
|
-
# @raise [
|
|
350
|
+
# @raise [Google::Auth::CredentialsError] If the input is not a Time, String, or nil.
|
|
253
351
|
def normalize_timestamp time
|
|
254
352
|
case time
|
|
255
353
|
when NilClass
|
|
@@ -259,7 +357,8 @@ module Google
|
|
|
259
357
|
when String
|
|
260
358
|
Time.parse time
|
|
261
359
|
else
|
|
262
|
-
|
|
360
|
+
message = "Invalid time value #{time}"
|
|
361
|
+
raise CredentialsError.with_details(message, credential_type_name: self.class.name, principal: principal)
|
|
263
362
|
end
|
|
264
363
|
end
|
|
265
364
|
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
require "googleauth/errors"
|
|
16
|
+
|
|
15
17
|
module Google
|
|
16
18
|
# Module Auth provides classes that provide Google-specific authorization
|
|
17
19
|
# used to access Google APIs.
|
|
@@ -19,10 +21,17 @@ module Google
|
|
|
19
21
|
# JsonKeyReader contains the behaviour used to read private key and
|
|
20
22
|
# client email fields from the service account
|
|
21
23
|
module JsonKeyReader
|
|
24
|
+
# Reads a JSON key from an IO object and extracts common fields.
|
|
25
|
+
#
|
|
26
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
|
27
|
+
# @return [Array(String, String, String, String, String)] An array containing:
|
|
28
|
+
# private_key, client_email, project_id, quota_project_id, and universe_domain
|
|
29
|
+
# @raise [Google::Auth::InitializationError] If client_email or private_key
|
|
30
|
+
# fields are missing from the JSON
|
|
22
31
|
def read_json_key json_key_io
|
|
23
32
|
json_key = MultiJson.load json_key_io.read
|
|
24
|
-
raise "missing client_email" unless json_key.key? "client_email"
|
|
25
|
-
raise "missing private_key" unless json_key.key? "private_key"
|
|
33
|
+
raise InitializationError, "missing client_email" unless json_key.key? "client_email"
|
|
34
|
+
raise InitializationError, "missing private_key" unless json_key.key? "private_key"
|
|
26
35
|
[
|
|
27
36
|
json_key["private_key"],
|
|
28
37
|
json_key["client_email"],
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
require "googleauth/errors"
|
|
15
16
|
require "googleauth/helpers/connection"
|
|
16
17
|
|
|
17
18
|
module Google
|
|
@@ -36,10 +37,12 @@ module Google
|
|
|
36
37
|
|
|
37
38
|
# Create a new instance of the STSClient.
|
|
38
39
|
#
|
|
39
|
-
# @param [
|
|
40
|
-
#
|
|
40
|
+
# @param [Hash] options Configuration options
|
|
41
|
+
# @option options [String] :token_exchange_endpoint The token exchange endpoint
|
|
42
|
+
# @option options [Faraday::Connection] :connection The Faraday connection to use
|
|
43
|
+
# @raise [Google::Auth::InitializationError] If token_exchange_endpoint is nil
|
|
41
44
|
def initialize options = {}
|
|
42
|
-
raise "Token exchange endpoint can not be nil" if options[:token_exchange_endpoint].nil?
|
|
45
|
+
raise InitializationError, "Token exchange endpoint can not be nil" if options[:token_exchange_endpoint].nil?
|
|
43
46
|
self.default_connection = options[:connection]
|
|
44
47
|
@token_exchange_endpoint = options[:token_exchange_endpoint]
|
|
45
48
|
end
|
|
@@ -67,6 +70,8 @@ module Google
|
|
|
67
70
|
# The optional additional headers to pass to the token exchange endpoint.
|
|
68
71
|
#
|
|
69
72
|
# @return [Hash] A hash containing the token exchange response.
|
|
73
|
+
# @raise [ArgumentError] If required options are missing
|
|
74
|
+
# @raise [Google::Auth::AuthorizationError] If the token exchange request fails
|
|
70
75
|
def exchange_token options = {}
|
|
71
76
|
missing_required_opts = [:grant_type, :subject_token, :subject_token_type] - options.keys
|
|
72
77
|
unless missing_required_opts.empty?
|
|
@@ -81,7 +86,7 @@ module Google
|
|
|
81
86
|
response = connection.post @token_exchange_endpoint, URI.encode_www_form(request_body), headers
|
|
82
87
|
|
|
83
88
|
if response.status != 200
|
|
84
|
-
raise "Token exchange failed with status #{response.status}"
|
|
89
|
+
raise AuthorizationError, "Token exchange failed with status #{response.status}"
|
|
85
90
|
end
|
|
86
91
|
|
|
87
92
|
MultiJson.load response.body
|
|
@@ -57,7 +57,7 @@ module Google
|
|
|
57
57
|
#
|
|
58
58
|
# @param scope [String,Array<String>] Input scope(s)
|
|
59
59
|
# @return [Array<String>] Always an array of strings
|
|
60
|
-
# @raise ArgumentError If the input is not a string or array of strings
|
|
60
|
+
# @raise [ArgumentError] If the input is not a string or array of strings
|
|
61
61
|
#
|
|
62
62
|
def self.as_array scope
|
|
63
63
|
case scope
|
|
@@ -41,6 +41,10 @@ module Google
|
|
|
41
41
|
attr_reader :project_id
|
|
42
42
|
attr_reader :quota_project_id
|
|
43
43
|
|
|
44
|
+
# @private
|
|
45
|
+
# @type [::String] The type name for this credential.
|
|
46
|
+
CREDENTIAL_TYPE_NAME = "service_account".freeze
|
|
47
|
+
|
|
44
48
|
def enable_self_signed_jwt?
|
|
45
49
|
# Use a self-singed JWT if there's no information that can be used to
|
|
46
50
|
# obtain an OAuth token, OR if there are scopes but also an assertion
|
|
@@ -51,23 +55,22 @@ module Google
|
|
|
51
55
|
|
|
52
56
|
# Creates a ServiceAccountCredentials.
|
|
53
57
|
#
|
|
54
|
-
# @param json_key_io [IO]
|
|
58
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
|
55
59
|
# @param scope [string|array|nil] the scope(s) to access
|
|
60
|
+
# @raise [ArgumentError] If both scope and target_audience are specified
|
|
56
61
|
def self.make_creds options = {}
|
|
57
62
|
json_key_io, scope, enable_self_signed_jwt, target_audience, audience, token_credential_uri =
|
|
58
63
|
options.values_at :json_key_io, :scope, :enable_self_signed_jwt, :target_audience,
|
|
59
64
|
:audience, :token_credential_uri
|
|
60
65
|
raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
universe_domain = nil
|
|
70
|
-
end
|
|
67
|
+
private_key, client_email, project_id, quota_project_id, universe_domain =
|
|
68
|
+
if json_key_io
|
|
69
|
+
CredentialsLoader.load_and_verify_json_key_type json_key_io, CREDENTIAL_TYPE_NAME
|
|
70
|
+
read_json_key json_key_io
|
|
71
|
+
else
|
|
72
|
+
creds_from_env
|
|
73
|
+
end
|
|
71
74
|
project_id ||= CredentialsLoader.load_gcloud_project_id
|
|
72
75
|
|
|
73
76
|
new(token_credential_uri: token_credential_uri || TOKEN_CRED_URI,
|
|
@@ -110,6 +113,9 @@ module Google
|
|
|
110
113
|
# Handles certain escape sequences that sometimes appear in input.
|
|
111
114
|
# Specifically, interprets the "\n" sequence for newline, and removes
|
|
112
115
|
# enclosing quotes.
|
|
116
|
+
#
|
|
117
|
+
# @param str [String] The string to unescape
|
|
118
|
+
# @return [String] The unescaped string
|
|
113
119
|
def self.unescape str
|
|
114
120
|
str = str.gsub '\n', "\n"
|
|
115
121
|
str = str[1..-2] if str.start_with?('"') && str.end_with?('"')
|
|
@@ -164,6 +170,13 @@ module Google
|
|
|
164
170
|
self
|
|
165
171
|
end
|
|
166
172
|
|
|
173
|
+
# Returns the client email as the principal for service account credentials
|
|
174
|
+
# @private
|
|
175
|
+
# @return [String] the email address of the service account
|
|
176
|
+
def principal
|
|
177
|
+
@issuer
|
|
178
|
+
end
|
|
179
|
+
|
|
167
180
|
private
|
|
168
181
|
|
|
169
182
|
def apply_self_signed_jwt! a_hash
|
|
@@ -179,6 +192,20 @@ module Google
|
|
|
179
192
|
alt.logger = logger
|
|
180
193
|
alt.apply! a_hash
|
|
181
194
|
end
|
|
195
|
+
|
|
196
|
+
# @private
|
|
197
|
+
# Loads service account credential details from environment variables.
|
|
198
|
+
#
|
|
199
|
+
# @return [Array<String, String, String, nil, nil>] An array containing private_key,
|
|
200
|
+
# client_email, project_id, quota_project_id, and universe_domain.
|
|
201
|
+
def self.creds_from_env
|
|
202
|
+
private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
|
203
|
+
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
|
204
|
+
project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
|
205
|
+
[private_key, client_email, project_id, nil, nil]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
private_class_method :creds_from_env
|
|
182
209
|
end
|
|
183
210
|
end
|
|
184
211
|
end
|
|
@@ -47,7 +47,7 @@ module Google
|
|
|
47
47
|
|
|
48
48
|
# Create a ServiceAccountJwtHeaderCredentials.
|
|
49
49
|
#
|
|
50
|
-
# @param json_key_io [IO]
|
|
50
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
|
51
51
|
# @param scope [string|array|nil] the scope(s) to access
|
|
52
52
|
def self.make_creds options = {}
|
|
53
53
|
json_key_io, scope = options.values_at :json_key_io, :scope
|
|
@@ -56,7 +56,7 @@ module Google
|
|
|
56
56
|
|
|
57
57
|
# Initializes a ServiceAccountJwtHeaderCredentials.
|
|
58
58
|
#
|
|
59
|
-
# @param json_key_io [IO]
|
|
59
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
|
60
60
|
def initialize options = {}
|
|
61
61
|
json_key_io = options[:json_key_io]
|
|
62
62
|
if json_key_io
|
|
@@ -159,6 +159,13 @@ module Google
|
|
|
159
159
|
false
|
|
160
160
|
end
|
|
161
161
|
|
|
162
|
+
# Returns the client email as the principal for service account JWT header credentials
|
|
163
|
+
# @private
|
|
164
|
+
# @return [String] the email address of the service account
|
|
165
|
+
def principal
|
|
166
|
+
@issuer
|
|
167
|
+
end
|
|
168
|
+
|
|
162
169
|
private
|
|
163
170
|
|
|
164
171
|
def deep_hash_normalize old_hash
|
data/lib/googleauth/signet.rb
CHANGED
|
@@ -16,6 +16,7 @@ require "base64"
|
|
|
16
16
|
require "json"
|
|
17
17
|
require "signet/oauth_2/client"
|
|
18
18
|
require "googleauth/base_client"
|
|
19
|
+
require "googleauth/errors"
|
|
19
20
|
|
|
20
21
|
module Signet
|
|
21
22
|
# OAuth2 supports OAuth2 authentication.
|
|
@@ -109,17 +110,29 @@ module Signet
|
|
|
109
110
|
end
|
|
110
111
|
end
|
|
111
112
|
|
|
113
|
+
# rubocop:disable Metrics/MethodLength
|
|
114
|
+
|
|
115
|
+
# Retries the provided block with exponential backoff, handling and wrapping errors.
|
|
116
|
+
#
|
|
117
|
+
# @param [Integer] max_retry_count The maximum number of retries before giving up
|
|
118
|
+
# @yield The block to execute and potentially retry
|
|
119
|
+
# @return [Object] The result of the block if successful
|
|
120
|
+
# @raise [Google::Auth::AuthorizationError] If a Signet::AuthorizationError occurs or if retries are exhausted
|
|
121
|
+
# @raise [Google::Auth::ParseError] If a Signet::ParseError occurs during token parsing
|
|
112
122
|
def retry_with_error max_retry_count = 5
|
|
113
123
|
retry_count = 0
|
|
114
124
|
|
|
115
125
|
begin
|
|
116
126
|
yield.tap { |resp| log_response resp }
|
|
127
|
+
rescue Signet::AuthorizationError, Signet::ParseError => e
|
|
128
|
+
log_auth_error e
|
|
129
|
+
error_class = e.is_a?(Signet::ParseError) ? Google::Auth::ParseError : Google::Auth::AuthorizationError
|
|
130
|
+
raise error_class.with_details(
|
|
131
|
+
e.message,
|
|
132
|
+
credential_type_name: self.class.name,
|
|
133
|
+
principal: respond_to?(:principal) ? principal : :signet_client
|
|
134
|
+
)
|
|
117
135
|
rescue StandardError => e
|
|
118
|
-
if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError)
|
|
119
|
-
log_auth_error e
|
|
120
|
-
raise e
|
|
121
|
-
end
|
|
122
|
-
|
|
123
136
|
if retry_count < max_retry_count
|
|
124
137
|
log_transient_error e
|
|
125
138
|
retry_count += 1
|
|
@@ -128,10 +141,15 @@ module Signet
|
|
|
128
141
|
else
|
|
129
142
|
log_retries_exhausted e
|
|
130
143
|
msg = "Unexpected error: #{e.inspect}"
|
|
131
|
-
raise
|
|
144
|
+
raise Google::Auth::AuthorizationError.with_details(
|
|
145
|
+
msg,
|
|
146
|
+
credential_type_name: self.class.name,
|
|
147
|
+
principal: respond_to?(:principal) ? principal : :signet_client
|
|
148
|
+
)
|
|
132
149
|
end
|
|
133
150
|
end
|
|
134
151
|
end
|
|
152
|
+
# rubocop:enable Metrics/MethodLength
|
|
135
153
|
|
|
136
154
|
# Creates a duplicate of these credentials
|
|
137
155
|
# without the Signet::OAuth2::Client-specific
|