googleauth 1.8.1 → 1.14.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 +96 -0
- data/README.md +49 -1
- data/lib/googleauth/api_key.rb +155 -0
- data/lib/googleauth/application_default.rb +1 -5
- data/lib/googleauth/base_client.rb +16 -4
- data/lib/googleauth/bearer_token.rb +148 -0
- data/lib/googleauth/compute_engine.rb +180 -44
- data/lib/googleauth/credentials.rb +169 -56
- data/lib/googleauth/credentials_loader.rb +2 -2
- data/lib/googleauth/default_credentials.rb +13 -2
- data/lib/googleauth/external_account/base_credentials.rb +37 -5
- data/lib/googleauth/external_account.rb +2 -1
- data/lib/googleauth/helpers/connection.rb +7 -1
- data/lib/googleauth/id_tokens.rb +0 -2
- data/lib/googleauth/impersonated_service_account.rb +282 -0
- data/lib/googleauth/json_key_reader.rb +2 -1
- data/lib/googleauth/service_account.rb +72 -103
- data/lib/googleauth/service_account_jwt_header.rb +180 -0
- data/lib/googleauth/signet.rb +149 -2
- data/lib/googleauth/token_store.rb +3 -3
- data/lib/googleauth/user_authorizer.rb +54 -4
- data/lib/googleauth/user_refresh.rb +47 -2
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +15 -4
- data/lib/googleauth.rb +7 -0
- metadata +40 -11
@@ -12,7 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
require "
|
15
|
+
require "google-cloud-env"
|
16
16
|
require "googleauth/signet"
|
17
17
|
|
18
18
|
module Google
|
@@ -33,94 +33,230 @@ module Google
|
|
33
33
|
# Extends Signet::OAuth2::Client so that the auth token is obtained from
|
34
34
|
# the GCE metadata server.
|
35
35
|
class GCECredentials < Signet::OAuth2::Client
|
36
|
-
#
|
37
|
-
# systems.
|
36
|
+
# @private Unused and deprecated but retained to prevent breaking changes
|
38
37
|
DEFAULT_METADATA_HOST = "169.254.169.254".freeze
|
39
38
|
|
40
|
-
# @private Unused and deprecated
|
39
|
+
# @private Unused and deprecated but retained to prevent breaking changes
|
41
40
|
COMPUTE_AUTH_TOKEN_URI =
|
42
41
|
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token".freeze
|
43
|
-
# @private Unused and deprecated
|
42
|
+
# @private Unused and deprecated but retained to prevent breaking changes
|
44
43
|
COMPUTE_ID_TOKEN_URI =
|
45
44
|
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/identity".freeze
|
46
|
-
# @private Unused and deprecated
|
45
|
+
# @private Unused and deprecated but retained to prevent breaking changes
|
47
46
|
COMPUTE_CHECK_URI = "http://169.254.169.254".freeze
|
48
47
|
|
49
|
-
@on_gce_cache = {}
|
50
|
-
|
51
48
|
class << self
|
49
|
+
# @private Unused and deprecated
|
52
50
|
def metadata_host
|
53
51
|
ENV.fetch "GCE_METADATA_HOST", DEFAULT_METADATA_HOST
|
54
52
|
end
|
55
53
|
|
54
|
+
# @private Unused and deprecated
|
56
55
|
def compute_check_uri
|
57
56
|
"http://#{metadata_host}".freeze
|
58
57
|
end
|
59
58
|
|
59
|
+
# @private Unused and deprecated
|
60
60
|
def compute_auth_token_uri
|
61
61
|
"#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/token".freeze
|
62
62
|
end
|
63
63
|
|
64
|
+
# @private Unused and deprecated
|
64
65
|
def compute_id_token_uri
|
65
66
|
"#{compute_check_uri}/computeMetadata/v1/instance/service-accounts/default/identity".freeze
|
66
67
|
end
|
67
68
|
|
68
69
|
# Detect if this appear to be a GCE instance, by checking if metadata
|
69
70
|
# is available.
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
@on_gce_cache.fetch options do
|
74
|
-
@on_gce_cache[options] = begin
|
75
|
-
# TODO: This should use google-cloud-env instead.
|
76
|
-
c = options[:connection] || Faraday.default_connection
|
77
|
-
headers = { "Metadata-Flavor" => "Google" }
|
78
|
-
resp = c.get compute_check_uri, nil, headers do |req|
|
79
|
-
req.options.timeout = 1.0
|
80
|
-
req.options.open_timeout = 0.1
|
81
|
-
end
|
82
|
-
return false unless resp.status == 200
|
83
|
-
resp.headers["Metadata-Flavor"] == "Google"
|
84
|
-
rescue Faraday::TimeoutError, Faraday::ConnectionFailed
|
85
|
-
false
|
86
|
-
end
|
87
|
-
end
|
71
|
+
# The parameters are deprecated and unused.
|
72
|
+
def on_gce? _options = {}, _reload = false # rubocop:disable Style/OptionalBooleanParameter
|
73
|
+
Google::Cloud.env.metadata?
|
88
74
|
end
|
89
75
|
|
90
76
|
def reset_cache
|
91
|
-
|
77
|
+
Google::Cloud.env.compute_metadata.reset_existence!
|
78
|
+
Google::Cloud.env.compute_metadata.cache.expire_all!
|
92
79
|
end
|
93
80
|
alias unmemoize_all reset_cache
|
94
81
|
end
|
95
82
|
|
83
|
+
# @private Temporary; remove when universe domain metadata endpoint is stable (see b/349488459).
|
84
|
+
attr_accessor :disable_universe_domain_check
|
85
|
+
|
86
|
+
# Construct a GCECredentials
|
87
|
+
def initialize options = {}
|
88
|
+
# Override the constructor to remember whether the universe domain was
|
89
|
+
# overridden by a constructor argument.
|
90
|
+
@universe_domain_overridden = options["universe_domain"] || options[:universe_domain]
|
91
|
+
# TODO: Remove when universe domain metadata endpoint is stable (see b/349488459).
|
92
|
+
@disable_universe_domain_check = true
|
93
|
+
super options
|
94
|
+
end
|
95
|
+
|
96
|
+
# Creates a duplicate of these credentials
|
97
|
+
# without the Signet::OAuth2::Client-specific
|
98
|
+
# transient state (e.g. cached tokens)
|
99
|
+
#
|
100
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
101
|
+
# The following keys are recognized in addition to keys in the
|
102
|
+
# Signet::OAuth2::Client
|
103
|
+
# * `:universe_domain_overridden` Whether the universe domain was
|
104
|
+
# overriden during credentials creation
|
105
|
+
def duplicate options = {}
|
106
|
+
options = deep_hash_normalize options
|
107
|
+
super(
|
108
|
+
{
|
109
|
+
universe_domain_overridden: @universe_domain_overridden
|
110
|
+
}.merge(options)
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
# @private
|
115
|
+
# Overrides universe_domain getter to fetch lazily if it hasn't been
|
116
|
+
# fetched yet. This is necessary specifically for Compute Engine because
|
117
|
+
# the universe comes from the metadata service, and isn't known
|
118
|
+
# immediately on credential construction. All other credential types read
|
119
|
+
# the universe from their json key or other immediate input.
|
120
|
+
def universe_domain
|
121
|
+
value = super
|
122
|
+
return value unless value.nil?
|
123
|
+
fetch_access_token!
|
124
|
+
super
|
125
|
+
end
|
126
|
+
|
96
127
|
# Overrides the super class method to change how access tokens are
|
97
128
|
# fetched.
|
98
|
-
def fetch_access_token
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
129
|
+
def fetch_access_token _options = {}
|
130
|
+
query, entry =
|
131
|
+
if token_type == :id_token
|
132
|
+
[{ "audience" => target_audience, "format" => "full" }, "service-accounts/default/identity"]
|
133
|
+
else
|
134
|
+
[{}, "service-accounts/default/token"]
|
135
|
+
end
|
136
|
+
query[:scopes] = Array(scope).join "," if scope
|
137
|
+
begin
|
138
|
+
log_fetch_query
|
139
|
+
resp = Google::Cloud.env.lookup_metadata_response "instance", entry, query: query
|
140
|
+
log_fetch_resp resp
|
105
141
|
case resp.status
|
106
142
|
when 200
|
107
|
-
|
108
|
-
if ["text/html", "application/text"].include? content_type
|
109
|
-
{ (target_audience ? "id_token" : "access_token") => resp.body }
|
110
|
-
else
|
111
|
-
Signet::OAuth2.parse_credentials resp.body, content_type
|
112
|
-
end
|
143
|
+
build_token_hash resp.body, resp.headers["content-type"], resp.retrieval_monotonic_time
|
113
144
|
when 403, 500
|
114
|
-
|
115
|
-
raise Signet::UnexpectedStatusError, msg
|
145
|
+
raise Signet::UnexpectedStatusError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
|
116
146
|
when 404
|
117
147
|
raise Signet::AuthorizationError, NO_METADATA_SERVER_ERROR
|
118
148
|
else
|
119
|
-
|
120
|
-
raise Signet::AuthorizationError, msg
|
149
|
+
raise Signet::AuthorizationError, "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
|
121
150
|
end
|
151
|
+
rescue Google::Cloud::Env::MetadataServerNotResponding => e
|
152
|
+
log_fetch_err e
|
153
|
+
raise Signet::AuthorizationError, e.message
|
122
154
|
end
|
123
155
|
end
|
156
|
+
|
157
|
+
# Destructively updates these credentials.
|
158
|
+
#
|
159
|
+
# This method is called by `Signet::OAuth2::Client`'s constructor
|
160
|
+
#
|
161
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
162
|
+
# The following keys are recognized in addition to keys in the
|
163
|
+
# Signet::OAuth2::Client
|
164
|
+
# * `:universe_domain_overridden` Whether the universe domain was
|
165
|
+
# overriden during credentials creation
|
166
|
+
# @return [Google::Auth::GCECredentials]
|
167
|
+
def update! options = {}
|
168
|
+
# Normalize all keys to symbols to allow indifferent access.
|
169
|
+
options = deep_hash_normalize options
|
170
|
+
|
171
|
+
@universe_domain_overridden = options[:universe_domain_overridden] if options.key? :universe_domain_overridden
|
172
|
+
|
173
|
+
super(options)
|
174
|
+
|
175
|
+
self
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def log_fetch_query
|
181
|
+
if token_type == :id_token
|
182
|
+
logger&.info do
|
183
|
+
Google::Logging::Message.from(
|
184
|
+
message: "Requesting id token from MDS with aud=#{target_audience}",
|
185
|
+
"credentialsId" => object_id
|
186
|
+
)
|
187
|
+
end
|
188
|
+
else
|
189
|
+
logger&.info do
|
190
|
+
Google::Logging::Message.from(
|
191
|
+
message: "Requesting access token from MDS",
|
192
|
+
"credentialsId" => object_id
|
193
|
+
)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def log_fetch_resp resp
|
199
|
+
logger&.info do
|
200
|
+
Google::Logging::Message.from(
|
201
|
+
message: "Received #{resp.status} from MDS",
|
202
|
+
"credentialsId" => object_id
|
203
|
+
)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def log_fetch_err _err
|
208
|
+
logger&.info do
|
209
|
+
Google::Logging::Message.from(
|
210
|
+
message: "MDS did not respond to token request",
|
211
|
+
"credentialsId" => object_id
|
212
|
+
)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def build_token_hash body, content_type, retrieval_time
|
217
|
+
hash =
|
218
|
+
if ["text/html", "application/text"].include? content_type
|
219
|
+
parse_encoded_token body
|
220
|
+
else
|
221
|
+
Signet::OAuth2.parse_credentials body, content_type
|
222
|
+
end
|
223
|
+
add_universe_domain_to hash
|
224
|
+
adjust_for_stale_expires_in hash, retrieval_time
|
225
|
+
hash
|
226
|
+
end
|
227
|
+
|
228
|
+
def parse_encoded_token body
|
229
|
+
hash = { token_type.to_s => body }
|
230
|
+
if token_type == :id_token
|
231
|
+
expires_at = expires_at_from_id_token body
|
232
|
+
hash["expires_at"] = expires_at if expires_at
|
233
|
+
end
|
234
|
+
hash
|
235
|
+
end
|
236
|
+
|
237
|
+
def add_universe_domain_to hash
|
238
|
+
return if @universe_domain_overridden
|
239
|
+
universe_domain =
|
240
|
+
if disable_universe_domain_check
|
241
|
+
# TODO: Remove when universe domain metadata endpoint is stable (see b/349488459).
|
242
|
+
"googleapis.com"
|
243
|
+
else
|
244
|
+
Google::Cloud.env.lookup_metadata "universe", "universe-domain"
|
245
|
+
end
|
246
|
+
universe_domain = "googleapis.com" if !universe_domain || universe_domain.empty?
|
247
|
+
hash["universe_domain"] = universe_domain.strip
|
248
|
+
end
|
249
|
+
|
250
|
+
# The response might have been cached, which means expires_in might be
|
251
|
+
# stale. Update it based on the time since the data was retrieved.
|
252
|
+
# We also ensure expires_in is conservative; subtracting at least 1
|
253
|
+
# second to offset any skew from metadata server latency.
|
254
|
+
def adjust_for_stale_expires_in hash, retrieval_time
|
255
|
+
return unless hash["expires_in"].is_a? Numeric
|
256
|
+
offset = 1 + (Process.clock_gettime(Process::CLOCK_MONOTONIC) - retrieval_time).round
|
257
|
+
hash["expires_in"] -= offset if offset.positive?
|
258
|
+
hash["expires_in"] = 0 if hash["expires_in"].negative?
|
259
|
+
end
|
124
260
|
end
|
125
261
|
end
|
126
262
|
end
|
@@ -26,6 +26,14 @@ module Google
|
|
26
26
|
# In most cases, it is subclassed by API-specific credential classes that
|
27
27
|
# can be instantiated by clients.
|
28
28
|
#
|
29
|
+
# **Important:** If you accept a credential configuration (credential
|
30
|
+
# JSON/File/Stream) from an external source for authentication to Google
|
31
|
+
# Cloud, you must validate it before providing it to any Google API or
|
32
|
+
# library. Providing an unvalidated credential configuration to Google APIs
|
33
|
+
# can compromise the security of your systems and data. For more
|
34
|
+
# information, refer to [Validate credential configurations from external
|
35
|
+
# sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
|
36
|
+
#
|
29
37
|
# ## Options
|
30
38
|
#
|
31
39
|
# Credentials classes are configured with options that dictate default
|
@@ -259,7 +267,7 @@ module Google
|
|
259
267
|
# @return [Object] The value
|
260
268
|
#
|
261
269
|
def self.lookup_auth_param name, method_name = name
|
262
|
-
val = instance_variable_get "@#{name}"
|
270
|
+
val = instance_variable_get :"@#{name}"
|
263
271
|
val = yield if val.nil? && block_given?
|
264
272
|
return val unless val.nil?
|
265
273
|
return superclass.send method_name if superclass.respond_to? method_name
|
@@ -299,6 +307,12 @@ module Google
|
|
299
307
|
#
|
300
308
|
attr_reader :quota_project_id
|
301
309
|
|
310
|
+
# @private Temporary; remove when universe domain metadata endpoint is stable (see b/349488459).
|
311
|
+
def disable_universe_domain_check
|
312
|
+
return false unless @client.respond_to? :disable_universe_domain_check
|
313
|
+
@client.disable_universe_domain_check
|
314
|
+
end
|
315
|
+
|
302
316
|
# @private Delegate client methods to the client object.
|
303
317
|
extend Forwardable
|
304
318
|
|
@@ -315,9 +329,6 @@ module Google
|
|
315
329
|
# @return [String, Array<String>] The scope for this client. A scope is an access range
|
316
330
|
# defined by the authorization server. The scope can be a single value or a list of values.
|
317
331
|
#
|
318
|
-
# @!attribute [r] target_audience
|
319
|
-
# @return [String] The final target audience for ID tokens returned by this credential.
|
320
|
-
#
|
321
332
|
# @!attribute [r] issuer
|
322
333
|
# @return [String] The issuer ID associated with this client.
|
323
334
|
#
|
@@ -328,42 +339,74 @@ module Google
|
|
328
339
|
# @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method,
|
329
340
|
# suitable for passing as a closure.
|
330
341
|
#
|
342
|
+
# @!attribute [r] target_audience
|
343
|
+
# @return [String] The final target audience for ID tokens returned by this credential.
|
344
|
+
#
|
345
|
+
# @!attribute [rw] universe_domain
|
346
|
+
# @return [String] The universe domain issuing these credentials.
|
347
|
+
#
|
348
|
+
# @!attribute [rw] logger
|
349
|
+
# @return [Logger] The logger used to log credential operations such as token refresh.
|
350
|
+
#
|
331
351
|
def_delegators :@client,
|
332
352
|
:token_credential_uri, :audience,
|
333
|
-
:scope, :issuer, :signing_key, :updater_proc, :target_audience
|
353
|
+
:scope, :issuer, :signing_key, :updater_proc, :target_audience,
|
354
|
+
:universe_domain, :universe_domain=, :logger, :logger=
|
334
355
|
|
335
356
|
##
|
336
357
|
# Creates a new Credentials instance with the provided auth credentials, and with the default
|
337
358
|
# values configured on the class.
|
338
359
|
#
|
339
|
-
# @param [String, Hash, Signet::OAuth2::Client]
|
340
|
-
# The
|
360
|
+
# @param [String, Hash, Signet::OAuth2::Client] source_creds
|
361
|
+
# The source of credentials. It can be provided as one of the following:
|
362
|
+
#
|
363
|
+
# * The path to a JSON keyfile (as a `String`)
|
364
|
+
# * The contents of a JSON keyfile (as a `Hash`)
|
365
|
+
# * A `Signet::OAuth2::Client` credentials object
|
366
|
+
# * Any credentials object that supports the methods this wrapper delegates to an inner client.
|
367
|
+
#
|
368
|
+
# If this parameter is an object (`Signet::OAuth2::Client` or other) it will be used as an inner client.
|
369
|
+
# Otherwise the inner client will be constructed from the JSON keyfile or the contens of the hash.
|
341
370
|
#
|
342
|
-
# * The path to a JSON keyfile (as a +String+)
|
343
|
-
# * The contents of a JSON keyfile (as a +Hash+)
|
344
|
-
# * A +Signet::OAuth2::Client+ object
|
345
371
|
# @param [Hash] options
|
346
|
-
# The options for configuring
|
372
|
+
# The options for configuring this wrapper credentials object and the inner client.
|
373
|
+
# The options hash is used in two ways:
|
347
374
|
#
|
348
|
-
#
|
349
|
-
#
|
350
|
-
#
|
351
|
-
#
|
375
|
+
# 1. **Configuring the wrapper object:** Some options are used to directly
|
376
|
+
# configure the wrapper `Credentials` instance. These include:
|
377
|
+
#
|
378
|
+
# * `:project_id` (and optionally `:project`) - the project identifier for the client
|
379
|
+
# * `:quota_project_id` - the quota project identifier for the client
|
380
|
+
# * `:logger` - the logger used to log credential operations such as token refresh.
|
381
|
+
#
|
382
|
+
# 2. **Configuring the inner client:** When the `source_creds` parameter
|
383
|
+
# is a `String` or `Hash`, a new `Signet::OAuth2::Client` is created
|
384
|
+
# internally. The following options are used to configure this inner client:
|
352
385
|
#
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
386
|
+
# * `:scope` - the scope for the client
|
387
|
+
# * `:target_audience` - the target audience for the client
|
388
|
+
#
|
389
|
+
# Any other options in the `options` hash are passed directly to the
|
390
|
+
# inner client constructor. This allows you to configure additional
|
391
|
+
# parameters of the `Signet::OAuth2::Client`, such as connection parameters,
|
392
|
+
# timeouts, etc.
|
393
|
+
#
|
394
|
+
def initialize source_creds, options = {}
|
395
|
+
raise "The source credentials passed to Google::Auth::Credentials.new were nil." if source_creds.nil?
|
396
|
+
|
397
|
+
options = symbolize_hash_keys options
|
398
|
+
@project_id = options[:project_id] || options[:project]
|
399
|
+
@quota_project_id = options[:quota_project_id]
|
400
|
+
case source_creds
|
401
|
+
when String
|
402
|
+
update_from_filepath source_creds, options
|
360
403
|
when Hash
|
361
|
-
update_from_hash
|
404
|
+
update_from_hash source_creds, options
|
362
405
|
else
|
363
|
-
|
406
|
+
update_from_client source_creds
|
364
407
|
end
|
408
|
+
setup_logging logger: options.fetch(:logger, :default)
|
365
409
|
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
366
|
-
@client.fetch_access_token! if @client.needs_access_token?
|
367
410
|
@env_vars = nil
|
368
411
|
@paths = nil
|
369
412
|
@scope = nil
|
@@ -457,7 +500,8 @@ module Google
|
|
457
500
|
audience: options[:audience] || audience
|
458
501
|
}
|
459
502
|
client = Google::Auth::DefaultCredentials.make_creds creds_input
|
460
|
-
|
503
|
+
options = options.select { |k, _v| k == :logger }
|
504
|
+
new client, options
|
461
505
|
end
|
462
506
|
|
463
507
|
private_class_method :from_env_vars,
|
@@ -465,14 +509,50 @@ module Google
|
|
465
509
|
:from_application_default,
|
466
510
|
:from_io
|
467
511
|
|
468
|
-
protected
|
469
512
|
|
470
|
-
#
|
471
|
-
|
472
|
-
|
473
|
-
|
513
|
+
# Creates a duplicate of these credentials. This method tries to create the duplicate of the
|
514
|
+
# wrapped credentials if they support duplication and use them as is if they don't.
|
515
|
+
#
|
516
|
+
# The wrapped credentials are typically `Signet::OAuth2::Client` objects and they keep
|
517
|
+
# the transient state (token, refresh token, etc). The duplication discards that state,
|
518
|
+
# allowing e.g. to get the token with a different scope.
|
519
|
+
#
|
520
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
521
|
+
#
|
522
|
+
# The options hash is used in two ways:
|
523
|
+
#
|
524
|
+
# 1. **Configuring the duplicate of the wrapper object:** Some options are used to directly
|
525
|
+
# configure the wrapper `Credentials` instance. These include:
|
526
|
+
#
|
527
|
+
# * `:project_id` (and optionally `:project`) - the project identifier for the credentials
|
528
|
+
# * `:quota_project_id` - the quota project identifier for the credentials
|
529
|
+
#
|
530
|
+
# 2. **Configuring the duplicate of the inner client:** If the inner client supports duplication
|
531
|
+
# the options hash is passed to it. This allows for configuration of additional parameters,
|
532
|
+
# most importantly (but not limited to) the following:
|
533
|
+
#
|
534
|
+
# * `:scope` - the scope for the client
|
535
|
+
#
|
536
|
+
# @return [Credentials]
|
537
|
+
def duplicate options = {}
|
538
|
+
options = deep_hash_normalize options
|
539
|
+
|
540
|
+
options = {
|
541
|
+
project_id: @project_id,
|
542
|
+
quota_project_id: @quota_project_id
|
543
|
+
}.merge(options)
|
544
|
+
|
545
|
+
new_client = if @client.respond_to? :duplicate
|
546
|
+
@client.duplicate options
|
547
|
+
else
|
548
|
+
@client
|
549
|
+
end
|
550
|
+
|
551
|
+
self.class.new new_client, options
|
474
552
|
end
|
475
553
|
|
554
|
+
protected
|
555
|
+
|
476
556
|
# Verify that the keyfile argument is a file.
|
477
557
|
def verify_keyfile_exists! keyfile
|
478
558
|
exists = ::File.file? keyfile
|
@@ -480,10 +560,11 @@ module Google
|
|
480
560
|
end
|
481
561
|
|
482
562
|
# Initializes the Signet client.
|
483
|
-
def init_client
|
484
|
-
|
485
|
-
|
486
|
-
|
563
|
+
def init_client hash, options = {}
|
564
|
+
options = update_client_options options
|
565
|
+
io = StringIO.new JSON.generate hash
|
566
|
+
options.merge! json_key_io: io
|
567
|
+
Google::Auth::DefaultCredentials.make_creds options
|
487
568
|
end
|
488
569
|
|
489
570
|
# returns a new Hash with string keys instead of symbol keys.
|
@@ -491,42 +572,40 @@ module Google
|
|
491
572
|
hash.to_h.transform_keys(&:to_s)
|
492
573
|
end
|
493
574
|
|
494
|
-
#
|
575
|
+
# returns a new Hash with symbol keys instead of string keys.
|
576
|
+
def symbolize_hash_keys hash
|
577
|
+
hash.to_h.transform_keys(&:to_sym)
|
578
|
+
end
|
579
|
+
|
580
|
+
def update_client_options options
|
581
|
+
options = options.dup
|
495
582
|
|
496
|
-
|
497
|
-
|
498
|
-
options[
|
499
|
-
options[
|
500
|
-
options[
|
501
|
-
options["target_audience"] ||= self.class.target_audience
|
583
|
+
# options have higher priority over constructor defaults
|
584
|
+
options[:token_credential_uri] ||= self.class.token_credential_uri
|
585
|
+
options[:audience] ||= self.class.audience
|
586
|
+
options[:scope] ||= self.class.scope
|
587
|
+
options[:target_audience] ||= self.class.target_audience
|
502
588
|
|
503
|
-
if !Array(options[
|
589
|
+
if !Array(options[:scope]).empty? && options[:target_audience]
|
504
590
|
raise ArgumentError, "Cannot specify both scope and target_audience"
|
505
591
|
end
|
592
|
+
options.delete :scope unless options[:target_audience].nil?
|
506
593
|
|
507
|
-
|
508
|
-
# client options for initializing signet client
|
509
|
-
{ token_credential_uri: options["token_credential_uri"],
|
510
|
-
audience: options["audience"],
|
511
|
-
scope: (needs_scope ? Array(options["scope"]) : nil),
|
512
|
-
target_audience: options["target_audience"],
|
513
|
-
issuer: options["client_email"],
|
514
|
-
signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
|
594
|
+
options
|
515
595
|
end
|
516
596
|
|
517
|
-
|
518
|
-
|
519
|
-
def update_from_signet client
|
597
|
+
def update_from_client client
|
520
598
|
@project_id ||= client.project_id if client.respond_to? :project_id
|
521
599
|
@quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id
|
522
600
|
@client = client
|
523
601
|
end
|
602
|
+
alias update_from_signet update_from_client
|
524
603
|
|
525
604
|
def update_from_hash hash, options
|
526
605
|
hash = stringify_hash_keys hash
|
527
606
|
hash["scope"] ||= options[:scope]
|
528
607
|
hash["target_audience"] ||= options[:target_audience]
|
529
|
-
@project_id ||=
|
608
|
+
@project_id ||= hash["project_id"] || hash["project"]
|
530
609
|
@quota_project_id ||= hash["quota_project_id"]
|
531
610
|
@client = init_client hash, options
|
532
611
|
end
|
@@ -536,10 +615,44 @@ module Google
|
|
536
615
|
json = JSON.parse ::File.read(path)
|
537
616
|
json["scope"] ||= options[:scope]
|
538
617
|
json["target_audience"] ||= options[:target_audience]
|
539
|
-
@project_id ||=
|
618
|
+
@project_id ||= json["project_id"] || json["project"]
|
540
619
|
@quota_project_id ||= json["quota_project_id"]
|
541
620
|
@client = init_client json, options
|
542
621
|
end
|
622
|
+
|
623
|
+
def setup_logging logger: :default
|
624
|
+
return unless @client.respond_to? :logger=
|
625
|
+
logging_env = ENV["GOOGLE_SDK_RUBY_LOGGING_GEMS"].to_s.downcase
|
626
|
+
if ["false", "none"].include? logging_env
|
627
|
+
logger = nil
|
628
|
+
elsif @client.logger
|
629
|
+
logger = @client.logger
|
630
|
+
elsif logger == :default
|
631
|
+
logger = nil
|
632
|
+
if ["true", "all"].include?(logging_env) || logging_env.split(",").include?("googleauth")
|
633
|
+
formatter = Google::Logging::StructuredFormatter.new if Google::Cloud::Env.get.logging_agent_expected?
|
634
|
+
logger = Logger.new $stderr, progname: "googleauth", formatter: formatter
|
635
|
+
end
|
636
|
+
end
|
637
|
+
@client.logger = logger
|
638
|
+
end
|
639
|
+
|
640
|
+
private
|
641
|
+
|
642
|
+
# Convert all keys in this hash (nested) to symbols for uniform retrieval
|
643
|
+
def recursive_hash_normalize_keys val
|
644
|
+
if val.is_a? Hash
|
645
|
+
deep_hash_normalize val
|
646
|
+
else
|
647
|
+
val
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
def deep_hash_normalize old_hash
|
652
|
+
sym_hash = {}
|
653
|
+
old_hash&.each { |k, v| sym_hash[k.to_sym] = recursive_hash_normalize_keys v }
|
654
|
+
sym_hash
|
655
|
+
end
|
543
656
|
end
|
544
657
|
end
|
545
658
|
end
|
@@ -37,7 +37,7 @@ module Google
|
|
37
37
|
AWS_SESSION_TOKEN_VAR = "AWS_SESSION_TOKEN".freeze
|
38
38
|
GCLOUD_POSIX_COMMAND = "gcloud".freeze
|
39
39
|
GCLOUD_WINDOWS_COMMAND = "gcloud.cmd".freeze
|
40
|
-
GCLOUD_CONFIG_COMMAND = "config config-helper --format json --verbosity none".freeze
|
40
|
+
GCLOUD_CONFIG_COMMAND = "config config-helper --format json --verbosity none --quiet".freeze
|
41
41
|
|
42
42
|
CREDENTIALS_FILE_NAME = "application_default_credentials.json".freeze
|
43
43
|
NOT_FOUND_ERROR = "Unable to read the credential file specified by #{ENV_VAR}".freeze
|
@@ -146,7 +146,7 @@ module Google
|
|
146
146
|
def load_gcloud_project_id
|
147
147
|
gcloud = GCLOUD_WINDOWS_COMMAND if OS.windows?
|
148
148
|
gcloud = GCLOUD_POSIX_COMMAND unless OS.windows?
|
149
|
-
gcloud_json = IO.popen("#{gcloud} #{GCLOUD_CONFIG_COMMAND}",
|
149
|
+
gcloud_json = IO.popen("#{gcloud} #{GCLOUD_CONFIG_COMMAND}", err: :close, &:read)
|
150
150
|
config = MultiJson.load gcloud_json
|
151
151
|
config["configuration"]["properties"]["core"]["project"]
|
152
152
|
rescue StandardError
|
@@ -16,9 +16,10 @@ require "multi_json"
|
|
16
16
|
require "stringio"
|
17
17
|
|
18
18
|
require "googleauth/credentials_loader"
|
19
|
+
require "googleauth/external_account"
|
19
20
|
require "googleauth/service_account"
|
21
|
+
require "googleauth/service_account_jwt_header"
|
20
22
|
require "googleauth/user_refresh"
|
21
|
-
require "googleauth/external_account"
|
22
23
|
|
23
24
|
module Google
|
24
25
|
# Module Auth provides classes that provide Google-specific authorization
|
@@ -29,8 +30,18 @@ module Google
|
|
29
30
|
class DefaultCredentials
|
30
31
|
extend CredentialsLoader
|
31
32
|
|
32
|
-
|
33
|
+
##
|
34
|
+
# Override CredentialsLoader#make_creds to use the class determined by
|
33
35
|
# loading the json.
|
36
|
+
#
|
37
|
+
# **Important:** If you accept a credential configuration (credential
|
38
|
+
# JSON/File/Stream) from an external source for authentication to Google
|
39
|
+
# Cloud, you must validate it before providing it to any Google API or
|
40
|
+
# library. Providing an unvalidated credential configuration to Google
|
41
|
+
# APIs can compromise the security of your systems and data. For more
|
42
|
+
# information, refer to [Validate credential configurations from external
|
43
|
+
# sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
|
44
|
+
#
|
34
45
|
def self.make_creds options = {}
|
35
46
|
json_key_io = options[:json_key_io]
|
36
47
|
if json_key_io
|