googleauth 1.14.0 → 1.15.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 +15 -0
- data/Credentials.md +106 -0
- data/Errors.md +152 -0
- 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 +23 -6
- data/lib/googleauth/credentials_loader.rb +9 -4
- data/lib/googleauth/default_credentials.rb +16 -4
- 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 +30 -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 +64 -17
- 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 +12 -1
- 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 +25 -7
- 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
@@ -15,6 +15,8 @@
|
|
15
15
|
require "os"
|
16
16
|
require "rbconfig"
|
17
17
|
|
18
|
+
require "googleauth/errors"
|
19
|
+
|
18
20
|
module Google
|
19
21
|
# Module Auth provides classes that provide Google-specific authorization
|
20
22
|
# used to access Google APIs.
|
@@ -71,11 +73,12 @@ module Google
|
|
71
73
|
# The following keys are recognized:
|
72
74
|
# * `:default_connection` The connection object to use.
|
73
75
|
# * `:connection_builder` A `Proc` that returns a connection.
|
76
|
+
# @raise [Google::Auth::InitializationError] If the credentials file cannot be read
|
74
77
|
def from_env scope = nil, options = {}
|
75
78
|
options = interpret_options scope, options
|
76
79
|
if ENV.key?(ENV_VAR) && !ENV[ENV_VAR].empty?
|
77
80
|
path = ENV[ENV_VAR]
|
78
|
-
raise "file #{path} does not exist" unless File.exist? path
|
81
|
+
raise InitializationError, "file #{path} does not exist" unless File.exist? path
|
79
82
|
File.open path do |f|
|
80
83
|
return make_creds options.merge(json_key_io: f)
|
81
84
|
end
|
@@ -83,7 +86,7 @@ module Google
|
|
83
86
|
make_creds options
|
84
87
|
end
|
85
88
|
rescue StandardError => e
|
86
|
-
raise "#{NOT_FOUND_ERROR}: #{e}"
|
89
|
+
raise InitializationError, "#{NOT_FOUND_ERROR}: #{e}"
|
87
90
|
end
|
88
91
|
|
89
92
|
# Creates an instance from a well known path.
|
@@ -97,6 +100,7 @@ module Google
|
|
97
100
|
# The following keys are recognized:
|
98
101
|
# * `:default_connection` The connection object to use.
|
99
102
|
# * `:connection_builder` A `Proc` that returns a connection.
|
103
|
+
# @raise [Google::Auth::InitializationError] If the credentials file cannot be read
|
100
104
|
def from_well_known_path scope = nil, options = {}
|
101
105
|
options = interpret_options scope, options
|
102
106
|
home_var = OS.windows? ? "APPDATA" : "HOME"
|
@@ -109,7 +113,7 @@ module Google
|
|
109
113
|
return make_creds options.merge(json_key_io: f)
|
110
114
|
end
|
111
115
|
rescue StandardError => e
|
112
|
-
raise "#{WELL_KNOWN_ERROR}: #{e}"
|
116
|
+
raise InitializationError, "#{WELL_KNOWN_ERROR}: #{e}"
|
113
117
|
end
|
114
118
|
|
115
119
|
# Creates an instance from the system default path
|
@@ -123,6 +127,7 @@ module Google
|
|
123
127
|
# The following keys are recognized:
|
124
128
|
# * `:default_connection` The connection object to use.
|
125
129
|
# * `:connection_builder` A `Proc` that returns a connection.
|
130
|
+
# @raise [Google::Auth::InitializationError] If the credentials file cannot be read or is invalid
|
126
131
|
def from_system_default_path scope = nil, options = {}
|
127
132
|
options = interpret_options scope, options
|
128
133
|
if OS.windows?
|
@@ -137,7 +142,7 @@ module Google
|
|
137
142
|
return make_creds options.merge(json_key_io: f)
|
138
143
|
end
|
139
144
|
rescue StandardError => e
|
140
|
-
raise "#{SYSTEM_DEFAULT_ERROR}: #{e}"
|
145
|
+
raise InitializationError, "#{SYSTEM_DEFAULT_ERROR}: #{e}"
|
141
146
|
end
|
142
147
|
|
143
148
|
module_function
|
@@ -16,6 +16,7 @@ require "multi_json"
|
|
16
16
|
require "stringio"
|
17
17
|
|
18
18
|
require "googleauth/credentials_loader"
|
19
|
+
require "googleauth/errors"
|
19
20
|
require "googleauth/external_account"
|
20
21
|
require "googleauth/service_account"
|
21
22
|
require "googleauth/service_account_jwt_header"
|
@@ -42,6 +43,9 @@ module Google
|
|
42
43
|
# information, refer to [Validate credential configurations from external
|
43
44
|
# sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
|
44
45
|
#
|
46
|
+
# @param options [Hash] Options for creating the credentials
|
47
|
+
# @return [Google::Auth::Credentials] The credentials instance
|
48
|
+
# @raise [Google::Auth::InitializationError] If the credentials cannot be determined
|
45
49
|
def self.make_creds options = {}
|
46
50
|
json_key_io = options[:json_key_io]
|
47
51
|
if json_key_io
|
@@ -54,10 +58,14 @@ module Google
|
|
54
58
|
end
|
55
59
|
end
|
56
60
|
|
61
|
+
# Reads the credential type from environment and returns the appropriate class
|
62
|
+
#
|
63
|
+
# @return [Class] The credential class to use
|
64
|
+
# @raise [Google::Auth::InitializationError] If the credentials type is undefined or unsupported
|
57
65
|
def self.read_creds
|
58
66
|
env_var = CredentialsLoader::ACCOUNT_TYPE_VAR
|
59
67
|
type = ENV[env_var]
|
60
|
-
raise "#{env_var} is undefined in env" unless type
|
68
|
+
raise InitializationError, "#{env_var} is undefined in env" unless type
|
61
69
|
case type
|
62
70
|
when "service_account"
|
63
71
|
ServiceAccountCredentials
|
@@ -66,15 +74,19 @@ module Google
|
|
66
74
|
when "external_account"
|
67
75
|
ExternalAccount::Credentials
|
68
76
|
else
|
69
|
-
raise "credentials type '#{type}' is not supported"
|
77
|
+
raise InitializationError, "credentials type '#{type}' is not supported"
|
70
78
|
end
|
71
79
|
end
|
72
80
|
|
73
81
|
# Reads the input json and determines which creds class to use.
|
82
|
+
#
|
83
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
84
|
+
# @return [Array(Hash, Class)] The JSON key and the credential class to use
|
85
|
+
# @raise [Google::Auth::InitializationError] If the JSON is missing the type field or has an unsupported type
|
74
86
|
def self.determine_creds_class json_key_io
|
75
87
|
json_key = MultiJson.load json_key_io.read
|
76
88
|
key = "type"
|
77
|
-
raise "the json is missing the '#{key}' field" unless json_key.key? key
|
89
|
+
raise InitializationError, "the json is missing the '#{key}' field" unless json_key.key? key
|
78
90
|
type = json_key[key]
|
79
91
|
case type
|
80
92
|
when "service_account"
|
@@ -84,7 +96,7 @@ module Google
|
|
84
96
|
when "external_account"
|
85
97
|
[json_key, ExternalAccount::Credentials]
|
86
98
|
else
|
87
|
-
raise "credentials type '#{type}' is not supported"
|
99
|
+
raise InitializationError, "credentials type '#{type}' is not supported"
|
88
100
|
end
|
89
101
|
end
|
90
102
|
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "signet/oauth_2/client"
|
4
|
+
|
5
|
+
# Copyright 2025 Google LLC
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
module Google
|
20
|
+
module Auth
|
21
|
+
##
|
22
|
+
# Error mixin module for Google Auth errors
|
23
|
+
# All Google Auth errors should include this module
|
24
|
+
#
|
25
|
+
module Error; end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Mixin module that contains detailed error information
|
29
|
+
# typically this is available if credentials initialization
|
30
|
+
# succeeds and credentials object is valid
|
31
|
+
#
|
32
|
+
module DetailedError
|
33
|
+
include Error
|
34
|
+
|
35
|
+
# The type of the credentials that the error was originated from
|
36
|
+
# @return [String, nil] The class name of the credential that raised the error
|
37
|
+
attr_reader :credential_type_name
|
38
|
+
|
39
|
+
# The principal for the authentication flow. Typically obtained from credentials
|
40
|
+
# @return [String, Symbol, nil] The principal identifier associated with the credentials
|
41
|
+
attr_reader :principal
|
42
|
+
|
43
|
+
# All details passed in the options hash when creating the error
|
44
|
+
# @return [Hash] Additional details about the error
|
45
|
+
attr_reader :details
|
46
|
+
|
47
|
+
# @private
|
48
|
+
def self.included base
|
49
|
+
base.extend ClassMethods
|
50
|
+
end
|
51
|
+
|
52
|
+
# Class methods to be added to including classes
|
53
|
+
module ClassMethods
|
54
|
+
# Creates a new error with detailed information
|
55
|
+
# @param message [String] The error message
|
56
|
+
# @param credential_type_name [String] The credential type that raised the error
|
57
|
+
# @param principal [String, Symbol] The principal for the authentication flow
|
58
|
+
# @return [Error] The new error with details
|
59
|
+
def with_details message, credential_type_name:, principal:
|
60
|
+
new(message).tap do |error|
|
61
|
+
error.instance_variable_set :@credential_type_name, credential_type_name
|
62
|
+
error.instance_variable_set :@principal, principal
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Error raised during Credentials initialization.
|
70
|
+
# All new code should use this instead of ArgumentError during initializtion.
|
71
|
+
#
|
72
|
+
class InitializationError < StandardError
|
73
|
+
include Error
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Generic error raised during operation of Credentials
|
78
|
+
# This should be used for all purposes not covered by other errors.
|
79
|
+
#
|
80
|
+
class CredentialsError < StandardError
|
81
|
+
include DetailedError
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# An error indicating the remote server refused to authorize the client.
|
86
|
+
# Maintains backward compatibility with Signet.
|
87
|
+
#
|
88
|
+
# Should not be used in the new code, even when wrapping `Signet::AuthorizationError`.
|
89
|
+
# New code should use CredentialsError instead.
|
90
|
+
#
|
91
|
+
class AuthorizationError < Signet::AuthorizationError
|
92
|
+
include DetailedError
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# An error indicating that the server sent an unexpected http status.
|
97
|
+
# Maintains backward compatibility with Signet.
|
98
|
+
#
|
99
|
+
# Should not be used in the new code, even when wrapping `Signet::UnexpectedStatusError`.
|
100
|
+
# New code should use CredentialsError instead.
|
101
|
+
#
|
102
|
+
class UnexpectedStatusError < Signet::UnexpectedStatusError
|
103
|
+
include DetailedError
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# An error indicating the client failed to parse a value.
|
108
|
+
# Maintains backward compatibility with Signet.
|
109
|
+
#
|
110
|
+
# Should not be used in the new code, even when wrapping `Signet::ParseError`.
|
111
|
+
# New code should use CredentialsError instead.
|
112
|
+
#
|
113
|
+
class ParseError < Signet::ParseError
|
114
|
+
include DetailedError
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -13,6 +13,7 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
15
|
require "time"
|
16
|
+
require "googleauth/errors"
|
16
17
|
require "googleauth/external_account/base_credentials"
|
17
18
|
require "googleauth/external_account/external_account_utils"
|
18
19
|
|
@@ -106,17 +107,32 @@ module Google
|
|
106
107
|
|
107
108
|
private
|
108
109
|
|
110
|
+
# Retrieves an IMDSv2 session token or returns a cached token if valid
|
111
|
+
#
|
112
|
+
# @return [String] The IMDSv2 session token
|
113
|
+
# @raise [Google::Auth::CredentialsError] If the token URL is missing or there's an error retrieving the token
|
109
114
|
def imdsv2_session_token
|
110
115
|
return @imdsv2_session_token unless imdsv2_session_token_invalid?
|
111
|
-
|
116
|
+
if @imdsv2_session_token_url.nil?
|
117
|
+
raise CredentialsError.with_details(
|
118
|
+
"IMDSV2 token url must be provided",
|
119
|
+
credential_type_name: self.class.name,
|
120
|
+
principal: principal
|
121
|
+
)
|
122
|
+
end
|
112
123
|
begin
|
113
124
|
response = connection.put @imdsv2_session_token_url do |req|
|
114
125
|
req.headers["x-aws-ec2-metadata-token-ttl-seconds"] = IMDSV2_TOKEN_EXPIRATION_IN_SECONDS.to_s
|
115
126
|
end
|
127
|
+
raise Faraday::Error unless response.success?
|
116
128
|
rescue Faraday::Error => e
|
117
|
-
raise
|
129
|
+
raise CredentialsError.with_details(
|
130
|
+
"Fetching AWS IMDSV2 token error: #{e}",
|
131
|
+
credential_type_name: self.class.name,
|
132
|
+
principal: principal
|
133
|
+
)
|
118
134
|
end
|
119
|
-
|
135
|
+
|
120
136
|
@imdsv2_session_token = response.body
|
121
137
|
@imdsv2_session_token_expiry = Time.now + IMDSV2_TOKEN_EXPIRATION_IN_SECONDS
|
122
138
|
@imdsv2_session_token
|
@@ -127,6 +143,14 @@ module Google
|
|
127
143
|
@imdsv2_session_token_expiry.nil? || @imdsv2_session_token_expiry < Time.now
|
128
144
|
end
|
129
145
|
|
146
|
+
# Makes a request to an AWS resource endpoint
|
147
|
+
#
|
148
|
+
# @param [String] url The AWS endpoint URL
|
149
|
+
# @param [String] name Resource name for error messages
|
150
|
+
# @param [Hash, nil] data Optional data to send in POST requests
|
151
|
+
# @param [Hash] headers Optional request headers
|
152
|
+
# @return [Faraday::Response] The successful response
|
153
|
+
# @raise [Google::Auth::CredentialsError] If the request fails
|
130
154
|
def get_aws_resource url, name, data: nil, headers: {}
|
131
155
|
begin
|
132
156
|
headers["x-aws-ec2-metadata-token"] = imdsv2_session_token
|
@@ -136,11 +160,14 @@ module Google
|
|
136
160
|
else
|
137
161
|
connection.get url, nil, headers
|
138
162
|
end
|
139
|
-
|
140
163
|
raise Faraday::Error unless response.success?
|
141
164
|
response
|
142
165
|
rescue Faraday::Error
|
143
|
-
raise
|
166
|
+
raise CredentialsError.with_details(
|
167
|
+
"Failed to retrieve AWS #{name}.",
|
168
|
+
credential_type_name: self.class.name,
|
169
|
+
principal: principal
|
170
|
+
)
|
144
171
|
end
|
145
172
|
end
|
146
173
|
|
@@ -181,9 +208,16 @@ module Google
|
|
181
208
|
# Retrieves the AWS role currently attached to the current AWS workload by querying the AWS metadata server.
|
182
209
|
# This is needed for the AWS metadata server security credentials endpoint in order to retrieve the AWS security
|
183
210
|
# credentials needed to sign requests to AWS APIs.
|
211
|
+
#
|
212
|
+
# @return [String] The AWS role name
|
213
|
+
# @raise [Google::Auth::CredentialsError] If the credential verification URL is not set or if the request fails
|
184
214
|
def fetch_metadata_role_name
|
185
215
|
unless @credential_verification_url
|
186
|
-
raise
|
216
|
+
raise CredentialsError.with_details(
|
217
|
+
"Unable to determine the AWS metadata server security credentials endpoint",
|
218
|
+
credential_type_name: self.class.name,
|
219
|
+
principal: principal
|
220
|
+
)
|
187
221
|
end
|
188
222
|
|
189
223
|
get_aws_resource(@credential_verification_url, "IAM Role").body
|
@@ -195,11 +229,22 @@ module Google
|
|
195
229
|
MultiJson.load response.body
|
196
230
|
end
|
197
231
|
|
232
|
+
# Reads the name of the AWS region from the environment
|
233
|
+
#
|
234
|
+
# @return [String] The name of the AWS region
|
235
|
+
# @raise [Google::Auth::CredentialsError] If the region is not set in the environment
|
236
|
+
# and the region_url was not set in credentials source
|
198
237
|
def region
|
199
238
|
@region = ENV[CredentialsLoader::AWS_REGION_VAR] || ENV[CredentialsLoader::AWS_DEFAULT_REGION_VAR]
|
200
239
|
|
201
240
|
unless @region
|
202
|
-
|
241
|
+
unless @region_url
|
242
|
+
raise CredentialsError.with_details(
|
243
|
+
"region_url or region must be set for external account credentials",
|
244
|
+
credential_type_name: self.class.name,
|
245
|
+
principal: principal
|
246
|
+
)
|
247
|
+
end
|
203
248
|
|
204
249
|
@region ||= get_aws_resource(@region_url, "region").body[0..-2]
|
205
250
|
end
|
@@ -220,23 +265,45 @@ module Google
|
|
220
265
|
@region_name = region_name
|
221
266
|
end
|
222
267
|
|
223
|
-
# Generates
|
224
|
-
#
|
268
|
+
# Generates an AWS signature version 4 signed request.
|
269
|
+
#
|
270
|
+
# Creates a signed request following the AWS Signature Version 4 process, which
|
271
|
+
# provides secure authentication for AWS API calls. The process includes creating
|
272
|
+
# canonical request strings, calculating signatures using the AWS credentials, and
|
273
|
+
# building proper authorization headers.
|
274
|
+
#
|
275
|
+
# For detailed information on the signing process, see:
|
225
276
|
# https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
|
226
277
|
#
|
227
|
-
# @param [Hash
|
228
|
-
#
|
229
|
-
#
|
230
|
-
#
|
231
|
-
# @param [
|
232
|
-
#
|
278
|
+
# @param [Hash] aws_credentials The AWS security credentials with the following keys:
|
279
|
+
# @option aws_credentials [String] :access_key_id The AWS access key ID
|
280
|
+
# @option aws_credentials [String] :secret_access_key The AWS secret access key
|
281
|
+
# @option aws_credentials [String, nil] :session_token Optional AWS session token
|
282
|
+
# @param [Hash] original_request The request to sign with the following keys:
|
283
|
+
# @option original_request [String] :url The AWS service URL (must be HTTPS)
|
284
|
+
# @option original_request [String] :method The HTTP method (GET, POST, etc.)
|
285
|
+
# @option original_request [Hash, nil] :headers Optional request headers
|
286
|
+
# @option original_request [String, nil] :data Optional request payload
|
287
|
+
#
|
288
|
+
# @return [Hash] The signed request with the following keys:
|
289
|
+
# * :url - The original URL as a string
|
290
|
+
# * :headers - A hash of headers with the authorization header added
|
291
|
+
# * :method - The HTTP method
|
292
|
+
# * :data - The request payload (if present)
|
233
293
|
#
|
234
|
-
# @
|
235
|
-
# The AWS signed request dictionary object.
|
294
|
+
# @raise [Google::Auth::CredentialsError] If the AWS service URL is invalid
|
236
295
|
#
|
237
296
|
def generate_signed_request aws_credentials, original_request
|
238
297
|
uri = Addressable::URI.parse original_request[:url]
|
239
|
-
|
298
|
+
unless uri.hostname && uri.scheme == "https"
|
299
|
+
# NOTE: We use AwsCredentials name but can't access its principal since AwsRequestSigner
|
300
|
+
# is a separate class and not a credential object with access to the audience
|
301
|
+
raise CredentialsError.with_details(
|
302
|
+
"Invalid AWS service URL",
|
303
|
+
credential_type_name: AwsCredentials.name,
|
304
|
+
principal: "aws"
|
305
|
+
)
|
306
|
+
end
|
240
307
|
service_name = uri.host.split(".").first
|
241
308
|
|
242
309
|
datetime = Time.now.utc.strftime "%Y%m%dT%H%M%SZ"
|
@@ -13,6 +13,7 @@
|
|
13
13
|
# limitations under the License.require "time"
|
14
14
|
|
15
15
|
require "googleauth/base_client"
|
16
|
+
require "googleauth/errors"
|
16
17
|
require "googleauth/helpers/connection"
|
17
18
|
require "googleauth/oauth2/sts_client"
|
18
19
|
|
@@ -89,6 +90,14 @@ module Google
|
|
89
90
|
%r{/iam\.googleapis\.com/locations/[^/]+/workforcePools/}.match?(@audience || "")
|
90
91
|
end
|
91
92
|
|
93
|
+
# For external account credentials, the principal is
|
94
|
+
# represented by the audience, such as a workforce pool
|
95
|
+
# @private
|
96
|
+
# @return [String] the GCP principal, e.g. a workforce pool
|
97
|
+
def principal
|
98
|
+
@audience
|
99
|
+
end
|
100
|
+
|
92
101
|
private
|
93
102
|
|
94
103
|
def token_type
|
@@ -96,6 +105,8 @@ module Google
|
|
96
105
|
:access_token
|
97
106
|
end
|
98
107
|
|
108
|
+
# A common method for Other credentials to call during initialization
|
109
|
+
# @raise [Google::Auth::InitializationError] If workforce_pool_user_project is incorrectly set
|
99
110
|
def base_setup options
|
100
111
|
self.default_connection = options[:connection]
|
101
112
|
|
@@ -121,9 +132,11 @@ module Google
|
|
121
132
|
connection: default_connection
|
122
133
|
)
|
123
134
|
return unless @workforce_pool_user_project && !is_workforce_pool?
|
124
|
-
raise "workforce_pool_user_project should not be set for non-workforce pool credentials."
|
135
|
+
raise InitializationError, "workforce_pool_user_project should not be set for non-workforce pool credentials."
|
125
136
|
end
|
126
137
|
|
138
|
+
# Exchange tokens at STS endpoint
|
139
|
+
# @raise [Google::Auth::AuthorizationError] If the token exchange request fails
|
127
140
|
def exchange_token
|
128
141
|
additional_options = nil
|
129
142
|
if @client_id.nil? && @workforce_pool_user_project
|
@@ -140,6 +153,12 @@ module Google
|
|
140
153
|
}
|
141
154
|
log_token_request token_request
|
142
155
|
@sts_client.exchange_token token_request
|
156
|
+
rescue Google::Auth::AuthorizationError => e
|
157
|
+
raise Google::Auth::AuthorizationError.with_details(
|
158
|
+
e.message,
|
159
|
+
credential_type_name: self.class.name,
|
160
|
+
principal: principal
|
161
|
+
)
|
143
162
|
end
|
144
163
|
|
145
164
|
def log_token_request token_request
|
@@ -160,6 +179,12 @@ module Google
|
|
160
179
|
end
|
161
180
|
end
|
162
181
|
|
182
|
+
# Exchanges a token for an impersonated service account access token
|
183
|
+
#
|
184
|
+
# @param [String] token The token to exchange
|
185
|
+
# @param [Hash] _options Additional options (not used)
|
186
|
+
# @return [Hash] The response containing the impersonated access token
|
187
|
+
# @raise [Google::Auth::CredentialsError] If the impersonation request fails
|
163
188
|
def get_impersonated_access_token token, _options = {}
|
164
189
|
log_impersonated_token_request token
|
165
190
|
response = connection.post @service_account_impersonation_url do |req|
|
@@ -169,7 +194,11 @@ module Google
|
|
169
194
|
end
|
170
195
|
|
171
196
|
if response.status != 200
|
172
|
-
raise
|
197
|
+
raise CredentialsError.with_details(
|
198
|
+
"Service account impersonation failed with status #{response.status}",
|
199
|
+
credential_type_name: self.class.name,
|
200
|
+
principal: principal
|
201
|
+
)
|
173
202
|
end
|
174
203
|
|
175
204
|
MultiJson.load response.body
|
@@ -13,6 +13,7 @@
|
|
13
13
|
# limitations under the License.require "time"
|
14
14
|
|
15
15
|
require "googleauth/base_client"
|
16
|
+
require "googleauth/errors"
|
16
17
|
require "googleauth/helpers/connection"
|
17
18
|
require "googleauth/oauth2/sts_client"
|
18
19
|
|
@@ -36,8 +37,8 @@ module Google
|
|
36
37
|
# call this API or the required scopes may not be selected:
|
37
38
|
# https://cloud.google.com/resource-manager/reference/rest/v1/projects/get#authorization-scopes
|
38
39
|
#
|
39
|
-
# @return [
|
40
|
-
#
|
40
|
+
# @return [String, nil] The project ID corresponding to the workload identity
|
41
|
+
# pool or workforce pool if determinable
|
41
42
|
#
|
42
43
|
def project_id
|
43
44
|
return @project_id unless @project_id.nil?
|
@@ -65,7 +66,8 @@ module Google
|
|
65
66
|
# STS audience pattern:
|
66
67
|
# `//iam.googleapis.com/projects/$PROJECT_NUMBER/locations/...`
|
67
68
|
#
|
68
|
-
# @return [
|
69
|
+
# @return [String, nil] The project number extracted from the audience string,
|
70
|
+
# or nil if it cannot be determined
|
69
71
|
#
|
70
72
|
def project_number
|
71
73
|
segments = @audience.split "/"
|
@@ -74,6 +76,11 @@ module Google
|
|
74
76
|
segments[idx + 1]
|
75
77
|
end
|
76
78
|
|
79
|
+
# Normalizes a timestamp value to a Time object
|
80
|
+
#
|
81
|
+
# @param time [Time, String, nil] The timestamp to normalize
|
82
|
+
# @return [Time, nil] The normalized timestamp or nil if input is nil
|
83
|
+
# @raise [Google::Auth::CredentialsError] If the time value is not nil, Time, or String
|
77
84
|
def normalize_timestamp time
|
78
85
|
case time
|
79
86
|
when NilClass
|
@@ -83,10 +90,14 @@ module Google
|
|
83
90
|
when String
|
84
91
|
Time.parse time
|
85
92
|
else
|
86
|
-
raise "Invalid time value #{time}"
|
93
|
+
raise CredentialsError, "Invalid time value #{time}"
|
87
94
|
end
|
88
95
|
end
|
89
96
|
|
97
|
+
# Extracts the service account email from the impersonation URL
|
98
|
+
#
|
99
|
+
# @return [String, nil] The service account email extracted from the
|
100
|
+
# service_account_impersonation_url, or nil if it cannot be determined
|
90
101
|
def service_account_email
|
91
102
|
return nil if @service_account_impersonation_url.nil?
|
92
103
|
start_idx = @service_account_impersonation_url.rindex "/"
|