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.
@@ -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 "faraday"
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
- # The IP Address is used in the URIs to speed up failures on non-GCE
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
- def on_gce? options = {}, reload = false # rubocop:disable Style/OptionalBooleanParameter
71
- # We can follow OptionalBooleanParameter here because it's a public interface, we can't change it.
72
- @on_gce_cache.delete options if reload
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
- @on_gce_cache.clear
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 options = {}
99
- c = options[:connection] || Faraday.default_connection
100
- retry_with_error do
101
- uri = target_audience ? GCECredentials.compute_id_token_uri : GCECredentials.compute_auth_token_uri
102
- query = target_audience ? { "audience" => target_audience, "format" => "full" } : {}
103
- query[:scopes] = Array(scope).join "," if scope
104
- resp = c.get uri, query, "Metadata-Flavor" => "Google"
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
- content_type = resp.headers["content-type"]
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
- msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
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
- msg = "Unexpected error code #{resp.status} #{UNEXPECTED_ERROR_SUFFIX}"
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}".to_sym
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] keyfile
340
- # The keyfile can be provided as one of the following:
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 the credentials instance. The following is supported:
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
- # * +:scope+ - the scope for the client
349
- # * +"project_id"+ (and optionally +"project"+) - the project identifier for the client
350
- # * +:connection_builder+ - the connection builder to use for the client
351
- # * +:default_connection+ - the default connection to use for the client
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
- def initialize keyfile, options = {}
354
- verify_keyfile_provided! keyfile
355
- @project_id = options["project_id"] || options["project"]
356
- @quota_project_id = options["quota_project_id"]
357
- case keyfile
358
- when Google::Auth::BaseClient
359
- update_from_signet keyfile
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 keyfile, options
404
+ update_from_hash source_creds, options
362
405
  else
363
- update_from_filepath keyfile, options
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
- new client
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
- # Verify that the keyfile argument is provided.
471
- def verify_keyfile_provided! keyfile
472
- return unless keyfile.nil?
473
- raise "The keyfile passed to Google::Auth::Credentials.new was nil."
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 keyfile, connection_options = {}
484
- client_opts = client_options keyfile
485
- Signet::OAuth2::Client.new(client_opts)
486
- .configure_connection(connection_options)
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
- # rubocop:disable Metrics/AbcSize
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
- def client_options options
497
- # Keyfile options have higher priority over constructor defaults
498
- options["token_credential_uri"] ||= self.class.token_credential_uri
499
- options["audience"] ||= self.class.audience
500
- options["scope"] ||= self.class.scope
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["scope"]).empty? && options["target_audience"]
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
- needs_scope = options["target_audience"].nil?
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
- # rubocop:enable Metrics/AbcSize
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 ||= (hash["project_id"] || hash["project"])
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 ||= (json["project_id"] || json["project"])
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}", in: :close, err: :close, &:read)
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
- # override CredentialsLoader#make_creds to use the class determined by
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