googleauth 1.8.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 +117 -0
- data/Credentials.md +106 -0
- data/Errors.md +152 -0
- data/README.md +49 -1
- data/lib/googleauth/api_key.rb +164 -0
- data/lib/googleauth/application_default.rb +6 -8
- data/lib/googleauth/base_client.rb +21 -4
- data/lib/googleauth/bearer_token.rb +162 -0
- data/lib/googleauth/client_id.rb +9 -6
- data/lib/googleauth/compute_engine.rb +231 -49
- data/lib/googleauth/credentials.rb +187 -58
- data/lib/googleauth/credentials_loader.rb +11 -20
- data/lib/googleauth/default_credentials.rb +29 -8
- 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 +67 -6
- 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 +32 -7
- data/lib/googleauth/helpers/connection.rb +7 -1
- 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 -6
- data/lib/googleauth/impersonated_service_account.rb +329 -0
- data/lib/googleauth/json_key_reader.rb +13 -3
- data/lib/googleauth/oauth2/sts_client.rb +9 -4
- data/lib/googleauth/scope_util.rb +1 -1
- data/lib/googleauth/service_account.rb +84 -104
- data/lib/googleauth/service_account_jwt_header.rb +187 -0
- data/lib/googleauth/signet.rb +169 -4
- data/lib/googleauth/token_store.rb +3 -3
- data/lib/googleauth/user_authorizer.rb +89 -11
- data/lib/googleauth/user_refresh.rb +72 -9
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +65 -17
- data/lib/googleauth.rb +8 -0
- metadata +45 -13
@@ -14,9 +14,11 @@
|
|
14
14
|
|
15
15
|
require "forwardable"
|
16
16
|
require "json"
|
17
|
+
require "pathname"
|
17
18
|
require "signet/oauth_2/client"
|
18
19
|
|
19
20
|
require "googleauth/credentials_loader"
|
21
|
+
require "googleauth/errors"
|
20
22
|
|
21
23
|
module Google
|
22
24
|
module Auth
|
@@ -26,6 +28,14 @@ module Google
|
|
26
28
|
# In most cases, it is subclassed by API-specific credential classes that
|
27
29
|
# can be instantiated by clients.
|
28
30
|
#
|
31
|
+
# **Important:** If you accept a credential configuration (credential
|
32
|
+
# JSON/File/Stream) from an external source for authentication to Google
|
33
|
+
# Cloud, you must validate it before providing it to any Google API or
|
34
|
+
# library. Providing an unvalidated credential configuration to Google APIs
|
35
|
+
# can compromise the security of your systems and data. For more
|
36
|
+
# information, refer to [Validate credential configurations from external
|
37
|
+
# sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
|
38
|
+
#
|
29
39
|
# ## Options
|
30
40
|
#
|
31
41
|
# Credentials classes are configured with options that dictate default
|
@@ -259,7 +269,7 @@ module Google
|
|
259
269
|
# @return [Object] The value
|
260
270
|
#
|
261
271
|
def self.lookup_auth_param name, method_name = name
|
262
|
-
val = instance_variable_get "@#{name}"
|
272
|
+
val = instance_variable_get :"@#{name}"
|
263
273
|
val = yield if val.nil? && block_given?
|
264
274
|
return val unless val.nil?
|
265
275
|
return superclass.send method_name if superclass.respond_to? method_name
|
@@ -299,6 +309,12 @@ module Google
|
|
299
309
|
#
|
300
310
|
attr_reader :quota_project_id
|
301
311
|
|
312
|
+
# @private Temporary; remove when universe domain metadata endpoint is stable (see b/349488459).
|
313
|
+
def disable_universe_domain_check
|
314
|
+
return false unless @client.respond_to? :disable_universe_domain_check
|
315
|
+
@client.disable_universe_domain_check
|
316
|
+
end
|
317
|
+
|
302
318
|
# @private Delegate client methods to the client object.
|
303
319
|
extend Forwardable
|
304
320
|
|
@@ -315,9 +331,6 @@ module Google
|
|
315
331
|
# @return [String, Array<String>] The scope for this client. A scope is an access range
|
316
332
|
# defined by the authorization server. The scope can be a single value or a list of values.
|
317
333
|
#
|
318
|
-
# @!attribute [r] target_audience
|
319
|
-
# @return [String] The final target audience for ID tokens returned by this credential.
|
320
|
-
#
|
321
334
|
# @!attribute [r] issuer
|
322
335
|
# @return [String] The issuer ID associated with this client.
|
323
336
|
#
|
@@ -328,43 +341,81 @@ module Google
|
|
328
341
|
# @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method,
|
329
342
|
# suitable for passing as a closure.
|
330
343
|
#
|
344
|
+
# @!attribute [r] target_audience
|
345
|
+
# @return [String] The final target audience for ID tokens returned by this credential.
|
346
|
+
#
|
347
|
+
# @!attribute [rw] universe_domain
|
348
|
+
# @return [String] The universe domain issuing these credentials.
|
349
|
+
#
|
350
|
+
# @!attribute [rw] logger
|
351
|
+
# @return [Logger] The logger used to log credential operations such as token refresh.
|
352
|
+
#
|
331
353
|
def_delegators :@client,
|
332
354
|
:token_credential_uri, :audience,
|
333
|
-
:scope, :issuer, :signing_key, :updater_proc, :target_audience
|
355
|
+
:scope, :issuer, :signing_key, :updater_proc, :target_audience,
|
356
|
+
:universe_domain, :universe_domain=, :logger, :logger=
|
334
357
|
|
335
358
|
##
|
336
359
|
# Creates a new Credentials instance with the provided auth credentials, and with the default
|
337
360
|
# values configured on the class.
|
338
361
|
#
|
339
|
-
# @param [String, Hash,
|
340
|
-
# The
|
362
|
+
# @param [String, Pathname, Hash, Google::Auth::BaseClient] source_creds
|
363
|
+
# The source of credentials. It can be provided as one of the following:
|
364
|
+
#
|
365
|
+
# * The path to a JSON keyfile (as a `String` or a `Pathname`)
|
366
|
+
# * The contents of a JSON keyfile (as a `Hash`)
|
367
|
+
# * A `Google::Auth::BaseClient` credentials object, including but not limited to
|
368
|
+
# a `Signet::OAuth2::Client` object.
|
369
|
+
# * Any credentials object that supports the methods this wrapper delegates to an inner client.
|
370
|
+
#
|
371
|
+
# If this parameter is an object (`Signet::OAuth2::Client` or other) it will be used as an inner client.
|
372
|
+
# Otherwise the inner client will be constructed from the JSON keyfile or the contens of the hash.
|
341
373
|
#
|
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
374
|
# @param [Hash] options
|
346
|
-
# The options for configuring
|
375
|
+
# The options for configuring this wrapper credentials object and the inner client.
|
376
|
+
# The options hash is used in two ways:
|
347
377
|
#
|
348
|
-
#
|
349
|
-
#
|
350
|
-
#
|
351
|
-
#
|
378
|
+
# 1. **Configuring the wrapper object:** Some options are used to directly
|
379
|
+
# configure the wrapper `Credentials` instance. These include:
|
380
|
+
#
|
381
|
+
# * `:project_id` (and optionally `:project`) - the project identifier for the client
|
382
|
+
# * `:quota_project_id` - the quota project identifier for the client
|
383
|
+
# * `:logger` - the logger used to log credential operations such as token refresh.
|
384
|
+
#
|
385
|
+
# 2. **Configuring the inner client:** When the `source_creds` parameter
|
386
|
+
# is a `String` or `Hash`, a new `Signet::OAuth2::Client` is created
|
387
|
+
# internally. The following options are used to configure this inner client:
|
388
|
+
#
|
389
|
+
# * `:scope` - the scope for the client
|
390
|
+
# * `:target_audience` - the target audience for the client
|
352
391
|
#
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
392
|
+
# Any other options in the `options` hash are passed directly to the
|
393
|
+
# inner client constructor. This allows you to configure additional
|
394
|
+
# parameters of the `Signet::OAuth2::Client`, such as connection parameters,
|
395
|
+
# timeouts, etc.
|
396
|
+
#
|
397
|
+
# @raise [Google::Auth::InitializationError] If source_creds is nil
|
398
|
+
# @raise [ArgumentError] If both scope and target_audience are specified
|
399
|
+
#
|
400
|
+
def initialize source_creds, options = {}
|
401
|
+
if source_creds.nil?
|
402
|
+
raise InitializationError,
|
403
|
+
"The source credentials passed to Google::Auth::Credentials.new were nil."
|
404
|
+
end
|
405
|
+
|
406
|
+
options = symbolize_hash_keys options
|
407
|
+
@project_id = options[:project_id] || options[:project]
|
408
|
+
@quota_project_id = options[:quota_project_id]
|
409
|
+
case source_creds
|
410
|
+
when String, Pathname
|
411
|
+
update_from_filepath source_creds, options
|
360
412
|
when Hash
|
361
|
-
update_from_hash
|
413
|
+
update_from_hash source_creds, options
|
362
414
|
else
|
363
|
-
|
415
|
+
update_from_client source_creds
|
364
416
|
end
|
365
|
-
|
417
|
+
setup_logging logger: options.fetch(:logger, :default)
|
366
418
|
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
367
|
-
@client.fetch_access_token! if @client.needs_access_token?
|
368
419
|
@env_vars = nil
|
369
420
|
@paths = nil
|
370
421
|
@scope = nil
|
@@ -458,7 +509,8 @@ module Google
|
|
458
509
|
audience: options[:audience] || audience
|
459
510
|
}
|
460
511
|
client = Google::Auth::DefaultCredentials.make_creds creds_input
|
461
|
-
|
512
|
+
options = options.select { |k, _v| k == :logger }
|
513
|
+
new client, options
|
462
514
|
end
|
463
515
|
|
464
516
|
private_class_method :from_env_vars,
|
@@ -466,25 +518,65 @@ module Google
|
|
466
518
|
:from_application_default,
|
467
519
|
:from_io
|
468
520
|
|
469
|
-
protected
|
470
521
|
|
471
|
-
#
|
472
|
-
|
473
|
-
|
474
|
-
|
522
|
+
# Creates a duplicate of these credentials. This method tries to create the duplicate of the
|
523
|
+
# wrapped credentials if they support duplication and use them as is if they don't.
|
524
|
+
#
|
525
|
+
# The wrapped credentials are typically `Signet::OAuth2::Client` objects and they keep
|
526
|
+
# the transient state (token, refresh token, etc). The duplication discards that state,
|
527
|
+
# allowing e.g. to get the token with a different scope.
|
528
|
+
#
|
529
|
+
# @param options [Hash] Overrides for the credentials parameters.
|
530
|
+
#
|
531
|
+
# The options hash is used in two ways:
|
532
|
+
#
|
533
|
+
# 1. **Configuring the duplicate of the wrapper object:** Some options are used to directly
|
534
|
+
# configure the wrapper `Credentials` instance. These include:
|
535
|
+
#
|
536
|
+
# * `:project_id` (and optionally `:project`) - the project identifier for the credentials
|
537
|
+
# * `:quota_project_id` - the quota project identifier for the credentials
|
538
|
+
#
|
539
|
+
# 2. **Configuring the duplicate of the inner client:** If the inner client supports duplication
|
540
|
+
# the options hash is passed to it. This allows for configuration of additional parameters,
|
541
|
+
# most importantly (but not limited to) the following:
|
542
|
+
#
|
543
|
+
# * `:scope` - the scope for the client
|
544
|
+
#
|
545
|
+
# @return [Credentials]
|
546
|
+
def duplicate options = {}
|
547
|
+
options = deep_hash_normalize options
|
548
|
+
|
549
|
+
options = {
|
550
|
+
project_id: @project_id,
|
551
|
+
quota_project_id: @quota_project_id
|
552
|
+
}.merge(options)
|
553
|
+
|
554
|
+
new_client = if @client.respond_to? :duplicate
|
555
|
+
@client.duplicate options
|
556
|
+
else
|
557
|
+
@client
|
558
|
+
end
|
559
|
+
|
560
|
+
self.class.new new_client, options
|
475
561
|
end
|
476
562
|
|
563
|
+
protected
|
564
|
+
|
477
565
|
# Verify that the keyfile argument is a file.
|
566
|
+
#
|
567
|
+
# @param [String] keyfile Path to the keyfile
|
568
|
+
# @raise [Google::Auth::InitializationError] If the keyfile does not exist
|
478
569
|
def verify_keyfile_exists! keyfile
|
479
570
|
exists = ::File.file? keyfile
|
480
|
-
raise "The keyfile '#{keyfile}' is not a valid file." unless exists
|
571
|
+
raise InitializationError, "The keyfile '#{keyfile}' is not a valid file." unless exists
|
481
572
|
end
|
482
573
|
|
483
574
|
# Initializes the Signet client.
|
484
|
-
def init_client
|
485
|
-
|
486
|
-
|
487
|
-
|
575
|
+
def init_client hash, options = {}
|
576
|
+
options = update_client_options options
|
577
|
+
io = StringIO.new JSON.generate hash
|
578
|
+
options.merge! json_key_io: io
|
579
|
+
Google::Auth::DefaultCredentials.make_creds options
|
488
580
|
end
|
489
581
|
|
490
582
|
# returns a new Hash with string keys instead of symbol keys.
|
@@ -492,42 +584,45 @@ module Google
|
|
492
584
|
hash.to_h.transform_keys(&:to_s)
|
493
585
|
end
|
494
586
|
|
495
|
-
#
|
587
|
+
# returns a new Hash with symbol keys instead of string keys.
|
588
|
+
def symbolize_hash_keys hash
|
589
|
+
hash.to_h.transform_keys(&:to_sym)
|
590
|
+
end
|
591
|
+
|
592
|
+
# Updates client options with defaults from the credential class
|
593
|
+
#
|
594
|
+
# @param [Hash] options Options to update
|
595
|
+
# @return [Hash] Updated options hash
|
596
|
+
# @raise [ArgumentError] If both scope and target_audience are specified
|
597
|
+
def update_client_options options
|
598
|
+
options = options.dup
|
496
599
|
|
497
|
-
|
498
|
-
|
499
|
-
options[
|
500
|
-
options[
|
501
|
-
options[
|
502
|
-
options["target_audience"] ||= self.class.target_audience
|
600
|
+
# options have higher priority over constructor defaults
|
601
|
+
options[:token_credential_uri] ||= self.class.token_credential_uri
|
602
|
+
options[:audience] ||= self.class.audience
|
603
|
+
options[:scope] ||= self.class.scope
|
604
|
+
options[:target_audience] ||= self.class.target_audience
|
503
605
|
|
504
|
-
if !Array(options[
|
606
|
+
if !Array(options[:scope]).empty? && options[:target_audience]
|
505
607
|
raise ArgumentError, "Cannot specify both scope and target_audience"
|
506
608
|
end
|
609
|
+
options.delete :scope unless options[:target_audience].nil?
|
507
610
|
|
508
|
-
|
509
|
-
# client options for initializing signet client
|
510
|
-
{ token_credential_uri: options["token_credential_uri"],
|
511
|
-
audience: options["audience"],
|
512
|
-
scope: (needs_scope ? Array(options["scope"]) : nil),
|
513
|
-
target_audience: options["target_audience"],
|
514
|
-
issuer: options["client_email"],
|
515
|
-
signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) }
|
611
|
+
options
|
516
612
|
end
|
517
613
|
|
518
|
-
|
519
|
-
|
520
|
-
def update_from_signet client
|
614
|
+
def update_from_client client
|
521
615
|
@project_id ||= client.project_id if client.respond_to? :project_id
|
522
616
|
@quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id
|
523
617
|
@client = client
|
524
618
|
end
|
619
|
+
alias update_from_signet update_from_client
|
525
620
|
|
526
621
|
def update_from_hash hash, options
|
527
622
|
hash = stringify_hash_keys hash
|
528
623
|
hash["scope"] ||= options[:scope]
|
529
624
|
hash["target_audience"] ||= options[:target_audience]
|
530
|
-
@project_id ||=
|
625
|
+
@project_id ||= hash["project_id"] || hash["project"]
|
531
626
|
@quota_project_id ||= hash["quota_project_id"]
|
532
627
|
@client = init_client hash, options
|
533
628
|
end
|
@@ -537,10 +632,44 @@ module Google
|
|
537
632
|
json = JSON.parse ::File.read(path)
|
538
633
|
json["scope"] ||= options[:scope]
|
539
634
|
json["target_audience"] ||= options[:target_audience]
|
540
|
-
@project_id ||=
|
635
|
+
@project_id ||= json["project_id"] || json["project"]
|
541
636
|
@quota_project_id ||= json["quota_project_id"]
|
542
637
|
@client = init_client json, options
|
543
638
|
end
|
639
|
+
|
640
|
+
def setup_logging logger: :default
|
641
|
+
return unless @client.respond_to? :logger=
|
642
|
+
logging_env = ENV["GOOGLE_SDK_RUBY_LOGGING_GEMS"].to_s.downcase
|
643
|
+
if ["false", "none"].include? logging_env
|
644
|
+
logger = nil
|
645
|
+
elsif @client.logger
|
646
|
+
logger = @client.logger
|
647
|
+
elsif logger == :default
|
648
|
+
logger = nil
|
649
|
+
if ["true", "all"].include?(logging_env) || logging_env.split(",").include?("googleauth")
|
650
|
+
formatter = Google::Logging::StructuredFormatter.new if Google::Cloud::Env.get.logging_agent_expected?
|
651
|
+
logger = Logger.new $stderr, progname: "googleauth", formatter: formatter
|
652
|
+
end
|
653
|
+
end
|
654
|
+
@client.logger = logger
|
655
|
+
end
|
656
|
+
|
657
|
+
private
|
658
|
+
|
659
|
+
# Convert all keys in this hash (nested) to symbols for uniform retrieval
|
660
|
+
def recursive_hash_normalize_keys val
|
661
|
+
if val.is_a? Hash
|
662
|
+
deep_hash_normalize val
|
663
|
+
else
|
664
|
+
val
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
def deep_hash_normalize old_hash
|
669
|
+
sym_hash = {}
|
670
|
+
old_hash&.each { |k, v| sym_hash[k.to_sym] = recursive_hash_normalize_keys v }
|
671
|
+
sym_hash
|
672
|
+
end
|
544
673
|
end
|
545
674
|
end
|
546
675
|
end
|
@@ -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.
|
@@ -37,7 +39,7 @@ module Google
|
|
37
39
|
AWS_SESSION_TOKEN_VAR = "AWS_SESSION_TOKEN".freeze
|
38
40
|
GCLOUD_POSIX_COMMAND = "gcloud".freeze
|
39
41
|
GCLOUD_WINDOWS_COMMAND = "gcloud.cmd".freeze
|
40
|
-
GCLOUD_CONFIG_COMMAND = "config config-helper --format json --verbosity none".freeze
|
42
|
+
GCLOUD_CONFIG_COMMAND = "config config-helper --format json --verbosity none --quiet".freeze
|
41
43
|
|
42
44
|
CREDENTIALS_FILE_NAME = "application_default_credentials.json".freeze
|
43
45
|
NOT_FOUND_ERROR = "Unable to read the credential file specified by #{ENV_VAR}".freeze
|
@@ -49,14 +51,6 @@ module Google
|
|
49
51
|
CLOUD_SDK_CLIENT_ID = "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.app" \
|
50
52
|
"s.googleusercontent.com".freeze
|
51
53
|
|
52
|
-
CLOUD_SDK_CREDENTIALS_WARNING =
|
53
|
-
"You are authenticating using user credentials." \
|
54
|
-
"For production, we recommend using service account credentials." \
|
55
|
-
"To learn more about service account credentials, see" \
|
56
|
-
"http://cloud.google.com/docs/authentication/external/set-up-adc-on-cloud " \
|
57
|
-
"To suppress this message, set the " \
|
58
|
-
"GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS environment variable.".freeze
|
59
|
-
|
60
54
|
# make_creds proxies the construction of a credentials instance
|
61
55
|
#
|
62
56
|
# By default, it calls #new on the current class, but this behaviour can
|
@@ -79,11 +73,12 @@ module Google
|
|
79
73
|
# The following keys are recognized:
|
80
74
|
# * `:default_connection` The connection object to use.
|
81
75
|
# * `:connection_builder` A `Proc` that returns a connection.
|
76
|
+
# @raise [Google::Auth::InitializationError] If the credentials file cannot be read
|
82
77
|
def from_env scope = nil, options = {}
|
83
78
|
options = interpret_options scope, options
|
84
79
|
if ENV.key?(ENV_VAR) && !ENV[ENV_VAR].empty?
|
85
80
|
path = ENV[ENV_VAR]
|
86
|
-
raise "file #{path} does not exist" unless File.exist? path
|
81
|
+
raise InitializationError, "file #{path} does not exist" unless File.exist? path
|
87
82
|
File.open path do |f|
|
88
83
|
return make_creds options.merge(json_key_io: f)
|
89
84
|
end
|
@@ -91,7 +86,7 @@ module Google
|
|
91
86
|
make_creds options
|
92
87
|
end
|
93
88
|
rescue StandardError => e
|
94
|
-
raise "#{NOT_FOUND_ERROR}: #{e}"
|
89
|
+
raise InitializationError, "#{NOT_FOUND_ERROR}: #{e}"
|
95
90
|
end
|
96
91
|
|
97
92
|
# Creates an instance from a well known path.
|
@@ -105,6 +100,7 @@ module Google
|
|
105
100
|
# The following keys are recognized:
|
106
101
|
# * `:default_connection` The connection object to use.
|
107
102
|
# * `:connection_builder` A `Proc` that returns a connection.
|
103
|
+
# @raise [Google::Auth::InitializationError] If the credentials file cannot be read
|
108
104
|
def from_well_known_path scope = nil, options = {}
|
109
105
|
options = interpret_options scope, options
|
110
106
|
home_var = OS.windows? ? "APPDATA" : "HOME"
|
@@ -117,7 +113,7 @@ module Google
|
|
117
113
|
return make_creds options.merge(json_key_io: f)
|
118
114
|
end
|
119
115
|
rescue StandardError => e
|
120
|
-
raise "#{WELL_KNOWN_ERROR}: #{e}"
|
116
|
+
raise InitializationError, "#{WELL_KNOWN_ERROR}: #{e}"
|
121
117
|
end
|
122
118
|
|
123
119
|
# Creates an instance from the system default path
|
@@ -131,6 +127,7 @@ module Google
|
|
131
127
|
# The following keys are recognized:
|
132
128
|
# * `:default_connection` The connection object to use.
|
133
129
|
# * `:connection_builder` A `Proc` that returns a connection.
|
130
|
+
# @raise [Google::Auth::InitializationError] If the credentials file cannot be read or is invalid
|
134
131
|
def from_system_default_path scope = nil, options = {}
|
135
132
|
options = interpret_options scope, options
|
136
133
|
if OS.windows?
|
@@ -145,22 +142,16 @@ module Google
|
|
145
142
|
return make_creds options.merge(json_key_io: f)
|
146
143
|
end
|
147
144
|
rescue StandardError => e
|
148
|
-
raise "#{SYSTEM_DEFAULT_ERROR}: #{e}"
|
145
|
+
raise InitializationError, "#{SYSTEM_DEFAULT_ERROR}: #{e}"
|
149
146
|
end
|
150
147
|
|
151
148
|
module_function
|
152
149
|
|
153
|
-
# Issues warning if cloud sdk client id is used
|
154
|
-
def warn_if_cloud_sdk_credentials client_id
|
155
|
-
return if ENV["GOOGLE_AUTH_SUPPRESS_CREDENTIALS_WARNINGS"]
|
156
|
-
warn CLOUD_SDK_CREDENTIALS_WARNING if client_id == CLOUD_SDK_CLIENT_ID
|
157
|
-
end
|
158
|
-
|
159
150
|
# Finds project_id from gcloud CLI configuration
|
160
151
|
def load_gcloud_project_id
|
161
152
|
gcloud = GCLOUD_WINDOWS_COMMAND if OS.windows?
|
162
153
|
gcloud = GCLOUD_POSIX_COMMAND unless OS.windows?
|
163
|
-
gcloud_json = IO.popen("#{gcloud} #{GCLOUD_CONFIG_COMMAND}",
|
154
|
+
gcloud_json = IO.popen("#{gcloud} #{GCLOUD_CONFIG_COMMAND}", err: :close, &:read)
|
164
155
|
config = MultiJson.load gcloud_json
|
165
156
|
config["configuration"]["properties"]["core"]["project"]
|
166
157
|
rescue StandardError
|
@@ -16,9 +16,11 @@ require "multi_json"
|
|
16
16
|
require "stringio"
|
17
17
|
|
18
18
|
require "googleauth/credentials_loader"
|
19
|
+
require "googleauth/errors"
|
20
|
+
require "googleauth/external_account"
|
19
21
|
require "googleauth/service_account"
|
22
|
+
require "googleauth/service_account_jwt_header"
|
20
23
|
require "googleauth/user_refresh"
|
21
|
-
require "googleauth/external_account"
|
22
24
|
|
23
25
|
module Google
|
24
26
|
# Module Auth provides classes that provide Google-specific authorization
|
@@ -29,26 +31,41 @@ module Google
|
|
29
31
|
class DefaultCredentials
|
30
32
|
extend CredentialsLoader
|
31
33
|
|
32
|
-
|
34
|
+
##
|
35
|
+
# Override CredentialsLoader#make_creds to use the class determined by
|
33
36
|
# loading the json.
|
37
|
+
#
|
38
|
+
# **Important:** If you accept a credential configuration (credential
|
39
|
+
# JSON/File/Stream) from an external source for authentication to Google
|
40
|
+
# Cloud, you must validate it before providing it to any Google API or
|
41
|
+
# library. Providing an unvalidated credential configuration to Google
|
42
|
+
# APIs can compromise the security of your systems and data. For more
|
43
|
+
# information, refer to [Validate credential configurations from external
|
44
|
+
# sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
|
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
|
34
49
|
def self.make_creds options = {}
|
35
50
|
json_key_io = options[:json_key_io]
|
36
51
|
if json_key_io
|
37
52
|
json_key, clz = determine_creds_class json_key_io
|
38
|
-
warn_if_cloud_sdk_credentials json_key["client_id"]
|
39
53
|
io = StringIO.new MultiJson.dump(json_key)
|
40
54
|
clz.make_creds options.merge(json_key_io: io)
|
41
55
|
else
|
42
|
-
warn_if_cloud_sdk_credentials ENV[CredentialsLoader::CLIENT_ID_VAR]
|
43
56
|
clz = read_creds
|
44
57
|
clz.make_creds options
|
45
58
|
end
|
46
59
|
end
|
47
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
|
48
65
|
def self.read_creds
|
49
66
|
env_var = CredentialsLoader::ACCOUNT_TYPE_VAR
|
50
67
|
type = ENV[env_var]
|
51
|
-
raise "#{env_var} is undefined in env" unless type
|
68
|
+
raise InitializationError, "#{env_var} is undefined in env" unless type
|
52
69
|
case type
|
53
70
|
when "service_account"
|
54
71
|
ServiceAccountCredentials
|
@@ -57,15 +74,19 @@ module Google
|
|
57
74
|
when "external_account"
|
58
75
|
ExternalAccount::Credentials
|
59
76
|
else
|
60
|
-
raise "credentials type '#{type}' is not supported"
|
77
|
+
raise InitializationError, "credentials type '#{type}' is not supported"
|
61
78
|
end
|
62
79
|
end
|
63
80
|
|
64
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
|
65
86
|
def self.determine_creds_class json_key_io
|
66
87
|
json_key = MultiJson.load json_key_io.read
|
67
88
|
key = "type"
|
68
|
-
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
|
69
90
|
type = json_key[key]
|
70
91
|
case type
|
71
92
|
when "service_account"
|
@@ -75,7 +96,7 @@ module Google
|
|
75
96
|
when "external_account"
|
76
97
|
[json_key, ExternalAccount::Credentials]
|
77
98
|
else
|
78
|
-
raise "credentials type '#{type}' is not supported"
|
99
|
+
raise InitializationError, "credentials type '#{type}' is not supported"
|
79
100
|
end
|
80
101
|
end
|
81
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
|