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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/Credentials.md +106 -0
  4. data/Errors.md +152 -0
  5. data/lib/googleauth/api_key.rb +9 -0
  6. data/lib/googleauth/application_default.rb +3 -1
  7. data/lib/googleauth/base_client.rb +5 -0
  8. data/lib/googleauth/bearer_token.rb +16 -2
  9. data/lib/googleauth/client_id.rb +9 -5
  10. data/lib/googleauth/compute_engine.rb +64 -18
  11. data/lib/googleauth/credentials.rb +23 -6
  12. data/lib/googleauth/credentials_loader.rb +9 -4
  13. data/lib/googleauth/default_credentials.rb +16 -4
  14. data/lib/googleauth/errors.rb +117 -0
  15. data/lib/googleauth/external_account/aws_credentials.rb +85 -18
  16. data/lib/googleauth/external_account/base_credentials.rb +31 -2
  17. data/lib/googleauth/external_account/external_account_utils.rb +15 -4
  18. data/lib/googleauth/external_account/identity_pool_credentials.rb +40 -15
  19. data/lib/googleauth/external_account/pluggable_credentials.rb +34 -19
  20. data/lib/googleauth/external_account.rb +30 -6
  21. data/lib/googleauth/iam.rb +19 -3
  22. data/lib/googleauth/id_tokens/errors.rb +13 -7
  23. data/lib/googleauth/id_tokens/key_sources.rb +13 -7
  24. data/lib/googleauth/id_tokens/verifier.rb +2 -3
  25. data/lib/googleauth/id_tokens.rb +4 -4
  26. data/lib/googleauth/impersonated_service_account.rb +64 -17
  27. data/lib/googleauth/json_key_reader.rb +11 -2
  28. data/lib/googleauth/oauth2/sts_client.rb +9 -4
  29. data/lib/googleauth/scope_util.rb +1 -1
  30. data/lib/googleauth/service_account.rb +12 -1
  31. data/lib/googleauth/service_account_jwt_header.rb +9 -2
  32. data/lib/googleauth/signet.rb +24 -6
  33. data/lib/googleauth/user_authorizer.rb +35 -7
  34. data/lib/googleauth/user_refresh.rb +25 -7
  35. data/lib/googleauth/version.rb +1 -1
  36. data/lib/googleauth/web_user_authorizer.rb +46 -9
  37. data/lib/googleauth.rb +1 -0
  38. metadata +8 -5
@@ -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
 
@@ -32,10 +33,12 @@ module Google
32
33
 
33
34
  # Initialize from options map.
34
35
  #
35
- # @param [string] audience
36
- # @param [hash{symbol => value}] credential_source
37
- # credential_source is a hash that contains either source file or url.
38
- # credential_source_format is either text or json. To define how we parse the credential response.
36
+ # @param [Hash] options Configuration options
37
+ # @option options [String] :audience The audience for the token
38
+ # @option options [Hash{Symbol => Object}] :credential_source A hash containing either source file or url.
39
+ # credential_source_format is either text or json to define how to parse the credential response.
40
+ # @raise [Google::Auth::InitializationError] If credential_source format is invalid, field_name is missing,
41
+ # contains ambiguous sources, or is missing required fields
39
42
  #
40
43
  def initialize options = {}
41
44
  base_setup options
@@ -51,6 +54,9 @@ module Google
51
54
  end
52
55
 
53
56
  # Implementation of BaseCredentials retrieve_subject_token!
57
+ #
58
+ # @return [String] The subject token
59
+ # @raise [Google::Auth::CredentialsError] If the token can't be parsed from JSON or is missing
54
60
  def retrieve_subject_token!
55
61
  content, resource_name = token_data
56
62
  if @credential_source_format_type == "text"
@@ -60,56 +66,75 @@ module Google
60
66
  response_data = MultiJson.load content, symbolize_keys: true
61
67
  token = response_data[@credential_source_field_name.to_sym]
62
68
  rescue StandardError
63
- raise "Unable to parse subject_token from JSON resource #{resource_name} " \
64
- "using key #{@credential_source_field_name}"
69
+ raise CredentialsError, "Unable to parse subject_token from JSON resource #{resource_name} " \
70
+ "using key #{@credential_source_field_name}"
65
71
  end
66
72
  end
67
- raise "Missing subject_token in the credential_source file/response." unless token
73
+ raise CredentialsError, "Missing subject_token in the credential_source file/response." unless token
68
74
  token
69
75
  end
70
76
 
71
77
  private
72
78
 
79
+ # Validates input
80
+ #
81
+ # @raise [Google::Auth::InitializationError] If credential_source format is invalid, field_name is missing,
82
+ # contains ambiguous sources, or is missing required fields
73
83
  def validate_credential_source
74
84
  # `environment_id` is only supported in AWS or dedicated future external account credentials.
75
85
  unless @credential_source[:environment_id].nil?
76
- raise "Invalid Identity Pool credential_source field 'environment_id'"
86
+ raise InitializationError, "Invalid Identity Pool credential_source field 'environment_id'"
77
87
  end
78
88
  unless ["json", "text"].include? @credential_source_format_type
79
- raise "Invalid credential_source format #{@credential_source_format_type}"
89
+ raise InitializationError, "Invalid credential_source format #{@credential_source_format_type}"
80
90
  end
81
91
  # for JSON types, get the required subject_token field name.
82
92
  @credential_source_field_name = @credential_source_format[:subject_token_field_name]
83
93
  if @credential_source_format_type == "json" && @credential_source_field_name.nil?
84
- raise "Missing subject_token_field_name for JSON credential_source format"
94
+ raise InitializationError, "Missing subject_token_field_name for JSON credential_source format"
85
95
  end
86
96
  # check file or url must be fulfilled and mutually exclusiveness.
87
97
  if @credential_source_file && @credential_source_url
88
- raise "Ambiguous credential_source. 'file' is mutually exclusive with 'url'."
98
+ raise InitializationError, "Ambiguous credential_source. 'file' is mutually exclusive with 'url'."
89
99
  end
90
100
  return unless (@credential_source_file || @credential_source_url).nil?
91
- raise "Missing credential_source. A 'file' or 'url' must be provided."
101
+ raise InitializationError, "Missing credential_source. A 'file' or 'url' must be provided."
92
102
  end
93
103
 
94
104
  def token_data
95
105
  @credential_source_file.nil? ? url_data : file_data
96
106
  end
97
107
 
108
+ # Reads data from a file source
109
+ #
110
+ # @return [Array(String, String)] The file content and file path
111
+ # @raise [Google::Auth::CredentialsError] If the source file doesn't exist
98
112
  def file_data
99
- raise "File #{@credential_source_file} was not found." unless File.exist? @credential_source_file
113
+ unless File.exist? @credential_source_file
114
+ raise CredentialsError,
115
+ "File #{@credential_source_file} was not found."
116
+ end
100
117
  content = File.read @credential_source_file, encoding: "utf-8"
101
118
  [content, @credential_source_file]
102
119
  end
103
120
 
121
+ # Fetches data from a URL source
122
+ #
123
+ # @return [Array(String, String)] The response body and URL
124
+ # @raise [Google::Auth::CredentialsError] If there's an error retrieving data from the URL
125
+ # or if the response is not successful
104
126
  def url_data
105
127
  begin
106
128
  response = connection.get @credential_source_url do |req|
107
129
  req.headers.merge! @credential_source_headers
108
130
  end
109
131
  rescue Faraday::Error => e
110
- raise "Error retrieving from credential url: #{e}"
132
+ raise CredentialsError, "Error retrieving from credential url: #{e}"
133
+ end
134
+ unless response.success?
135
+ raise CredentialsError,
136
+ "Unable to retrieve Identity Pool subject token #{response.body}"
111
137
  end
112
- raise "Unable to retrieve Identity Pool subject token #{response.body}" unless response.success?
113
138
  [response.body, @credential_source_url]
114
139
  end
115
140
  end
@@ -14,6 +14,7 @@
14
14
 
15
15
  require "open3"
16
16
  require "time"
17
+ require "googleauth/errors"
17
18
  require "googleauth/external_account/base_credentials"
18
19
  require "googleauth/external_account/external_account_utils"
19
20
 
@@ -41,34 +42,43 @@ module Google
41
42
 
42
43
  # Initialize from options map.
43
44
  #
44
- # @param [string] audience
45
- # @param [hash{symbol => value}] credential_source
46
- # credential_source is a hash that contains either source file or url.
47
- # credential_source_format is either text or json. To define how we parse the credential response.
48
- #
45
+ # @param [Hash] options Configuration options
46
+ # @option options [String] :audience Audience for the token
47
+ # @option options [Hash] :credential_source Credential source configuration that contains executable
48
+ # configuration
49
+ # @raise [Google::Auth::InitializationError] If executable source, command is missing, or timeout is invalid
49
50
  def initialize options = {}
50
51
  base_setup options
51
52
 
52
53
  @audience = options[:audience]
53
54
  @credential_source = options[:credential_source] || {}
54
55
  @credential_source_executable = @credential_source[:executable]
55
- raise "Missing excutable source. An 'executable' must be provided" if @credential_source_executable.nil?
56
+ if @credential_source_executable.nil?
57
+ raise InitializationError,
58
+ "Missing excutable source. An 'executable' must be provided"
59
+ end
56
60
  @credential_source_executable_command = @credential_source_executable[:command]
57
61
  if @credential_source_executable_command.nil?
58
- raise "Missing command field. Executable command must be provided."
62
+ raise InitializationError, "Missing command field. Executable command must be provided."
59
63
  end
60
64
  @credential_source_executable_timeout_millis = @credential_source_executable[:timeout_millis] ||
61
65
  EXECUTABLE_TIMEOUT_MILLIS_DEFAULT
62
66
  if @credential_source_executable_timeout_millis < EXECUTABLE_TIMEOUT_MILLIS_LOWER_BOUND ||
63
67
  @credential_source_executable_timeout_millis > EXECUTABLE_TIMEOUT_MILLIS_UPPER_BOUND
64
- raise "Timeout must be between 5 and 120 seconds."
68
+ raise InitializationError, "Timeout must be between 5 and 120 seconds."
65
69
  end
66
70
  @credential_source_executable_output_file = @credential_source_executable[:output_file]
67
71
  end
68
72
 
73
+ # Retrieves the subject token using the credential_source object.
74
+ #
75
+ # @return [String] The retrieved subject token
76
+ # @raise [Google::Auth::CredentialsError] If executables are not allowed, if token retrieval fails,
77
+ # or if the token is invalid
69
78
  def retrieve_subject_token!
70
79
  unless ENV[ENABLE_PLUGGABLE_ENV] == "1"
71
- raise "Executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') " \
80
+ raise CredentialsError,
81
+ "Executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') " \
72
82
  "to run."
73
83
  end
74
84
  # check output file first
@@ -97,7 +107,7 @@ module Google
97
107
  subject_token = parse_subject_token response
98
108
  rescue StandardError => e
99
109
  return nil if e.message.match(/The token returned by the executable is expired/)
100
- raise e
110
+ raise CredentialsError, e.message
101
111
  end
102
112
  subject_token
103
113
  end
@@ -106,25 +116,29 @@ module Google
106
116
  validate_response_schema response
107
117
  unless response[:success]
108
118
  if response[:code].nil? || response[:message].nil?
109
- raise "Error code and message fields are required in the response."
119
+ raise CredentialsError, "Error code and message fields are required in the response."
110
120
  end
111
- raise "Executable returned unsuccessful response: code: #{response[:code]}, message: #{response[:message]}."
121
+ raise CredentialsError,
122
+ "Executable returned unsuccessful response: code: #{response[:code]}, message: #{response[:message]}."
112
123
  end
113
124
  if response[:expiration_time] && response[:expiration_time] < Time.now.to_i
114
- raise "The token returned by the executable is expired."
125
+ raise CredentialsError, "The token returned by the executable is expired."
126
+ end
127
+ if response[:token_type].nil?
128
+ raise CredentialsError,
129
+ "The executable response is missing the token_type field."
115
130
  end
116
- raise "The executable response is missing the token_type field." if response[:token_type].nil?
117
131
  return response[:id_token] if ID_TOKEN_TYPE.include? response[:token_type]
118
132
  return response[:saml_response] if response[:token_type] == "urn:ietf:params:oauth:token-type:saml2"
119
- raise "Executable returned unsupported token type."
133
+ raise CredentialsError, "Executable returned unsupported token type."
120
134
  end
121
135
 
122
136
  def validate_response_schema response
123
- raise "The executable response is missing the version field." if response[:version].nil?
137
+ raise CredentialsError, "The executable response is missing the version field." if response[:version].nil?
124
138
  if response[:version] > EXECUTABLE_SUPPORTED_MAX_VERSION
125
- raise "Executable returned unsupported version #{response[:version]}."
139
+ raise CredentialsError, "Executable returned unsupported version #{response[:version]}."
126
140
  end
127
- raise "The executable response is missing the success field." if response[:success].nil?
141
+ raise CredentialsError, "The executable response is missing the success field." if response[:success].nil?
128
142
  end
129
143
 
130
144
  def inject_environment_variables
@@ -145,7 +159,8 @@ module Google
145
159
  Timeout.timeout timeout_seconds do
146
160
  output, error, status = Open3.capture3 environment_vars, command
147
161
  unless status.success?
148
- raise "Executable exited with non-zero return code #{status.exitstatus}. Error: #{output}, #{error}"
162
+ raise CredentialsError,
163
+ "Executable exited with non-zero return code #{status.exitstatus}. Error: #{output}, #{error}"
149
164
  end
150
165
  output
151
166
  end
@@ -15,6 +15,7 @@
15
15
  require "time"
16
16
  require "uri"
17
17
  require "googleauth/credentials_loader"
18
+ require "googleauth/errors"
18
19
  require "googleauth/external_account/aws_credentials"
19
20
  require "googleauth/external_account/identity_pool_credentials"
20
21
  require "googleauth/external_account/pluggable_credentials"
@@ -35,30 +36,41 @@ module Google
35
36
 
36
37
  # Create a ExternalAccount::Credentials
37
38
  #
38
- # @param json_key_io [IO] an IO from which the JSON key can be read
39
- # @param scope [String,Array,nil] the scope(s) to access
39
+ # @param options [Hash] Options for creating credentials
40
+ # @option options [IO] :json_key_io (required) An IO object containing the JSON key
41
+ # @option options [String,Array,nil] :scope The scope(s) to access
42
+ # @return [Google::Auth::ExternalAccount::AwsCredentials,
43
+ # Google::Auth::ExternalAccount::IdentityPoolCredentials,
44
+ # Google::Auth::ExternalAccount::PluggableAuthCredentials]
45
+ # The appropriate external account credentials based on the credential source
46
+ # @raise [Google::Auth::InitializationError] If the json file is missing, lacks required fields,
47
+ # or does not contain a supported credential source
40
48
  def self.make_creds options = {}
41
49
  json_key_io, scope = options.values_at :json_key_io, :scope
42
50
 
43
- raise "A json file is required for external account credentials." unless json_key_io
51
+ raise InitializationError, "A json file is required for external account credentials." unless json_key_io
44
52
  user_creds = read_json_key json_key_io
45
53
 
46
54
  # AWS credentials is determined by aws subject token type
47
55
  return make_aws_credentials user_creds, scope if user_creds[:subject_token_type] == AWS_SUBJECT_TOKEN_TYPE
48
56
 
49
- raise MISSING_CREDENTIAL_SOURCE if user_creds[:credential_source].nil?
57
+ raise InitializationError, MISSING_CREDENTIAL_SOURCE if user_creds[:credential_source].nil?
50
58
  user_creds[:scope] = scope
51
59
  make_external_account_credentials user_creds
52
60
  end
53
61
 
54
62
  # Reads the required fields from the JSON.
63
+ #
64
+ # @param json_key_io [IO] An IO object containing the JSON key
65
+ # @return [Hash] The parsed JSON key
66
+ # @raise [Google::Auth::InitializationError] If the JSON is missing required fields
55
67
  def self.read_json_key json_key_io
56
68
  json_key = MultiJson.load json_key_io.read, symbolize_keys: true
57
69
  wanted = [
58
70
  :audience, :subject_token_type, :token_url, :credential_source
59
71
  ]
60
72
  wanted.each do |key|
61
- raise "the json is missing the #{key} field" unless json_key.key? key
73
+ raise InitializationError, "the json is missing the #{key} field" unless json_key.key? key
62
74
  end
63
75
  json_key
64
76
  end
@@ -66,6 +78,11 @@ module Google
66
78
  class << self
67
79
  private
68
80
 
81
+ # Creates AWS credentials from the provided user credentials
82
+ #
83
+ # @param user_creds [Hash] The user credentials containing AWS credential source information
84
+ # @param scope [String,Array,nil] The scope(s) to access
85
+ # @return [Google::Auth::ExternalAccount::AwsCredentials] The AWS credentials
69
86
  def make_aws_credentials user_creds, scope
70
87
  Google::Auth::ExternalAccount::AwsCredentials.new(
71
88
  audience: user_creds[:audience],
@@ -78,6 +95,13 @@ module Google
78
95
  )
79
96
  end
80
97
 
98
+ # Creates the appropriate external account credentials based on the credential source type
99
+ #
100
+ # @param user_creds [Hash] The user credentials containing credential source information
101
+ # @return [Google::Auth::ExternalAccount::IdentityPoolCredentials,
102
+ # Google::Auth::ExternalAccount::PluggableAuthCredentials]
103
+ # The appropriate external account credentials
104
+ # @raise [Google::Auth::InitializationError] If the credential source is not a supported type
81
105
  def make_external_account_credentials user_creds
82
106
  unless user_creds[:credential_source][:file].nil? && user_creds[:credential_source][:url].nil?
83
107
  return Google::Auth::ExternalAccount::IdentityPoolCredentials.new user_creds
@@ -85,7 +109,7 @@ module Google
85
109
  unless user_creds[:credential_source][:executable].nil?
86
110
  return Google::Auth::ExternalAccount::PluggableAuthCredentials.new user_creds
87
111
  end
88
- raise INVALID_EXTERNAL_ACCOUNT_TYPE
112
+ raise InitializationError, INVALID_EXTERNAL_ACCOUNT_TYPE
89
113
  end
90
114
  end
91
115
  end
@@ -27,8 +27,9 @@ module Google
27
27
 
28
28
  # Initializes an IAMCredentials.
29
29
  #
30
- # @param selector the IAM selector.
31
- # @param token the IAM token.
30
+ # @param selector [String] The IAM selector.
31
+ # @param token [String] The IAM token.
32
+ # @raise [TypeError] If selector or token is not a String
32
33
  def initialize selector, token
33
34
  raise TypeError unless selector.is_a? String
34
35
  raise TypeError unless token.is_a? String
@@ -37,13 +38,19 @@ module Google
37
38
  end
38
39
 
39
40
  # Adds the credential fields to the hash.
41
+ #
42
+ # @param a_hash [Hash] The hash to update with credentials
43
+ # @return [Hash] The updated hash with credentials
40
44
  def apply! a_hash
41
45
  a_hash[SELECTOR_KEY] = @selector
42
46
  a_hash[TOKEN_KEY] = @token
43
47
  a_hash
44
48
  end
45
49
 
46
- # Returns a clone of a_hash updated with the authoriation header
50
+ # Returns a clone of a_hash updated with the authorization header
51
+ #
52
+ # @param a_hash [Hash] The hash to clone and update with credentials
53
+ # @return [Hash] A new hash with credentials
47
54
  def apply a_hash
48
55
  a_copy = a_hash.clone
49
56
  apply! a_copy
@@ -52,9 +59,18 @@ module Google
52
59
 
53
60
  # Returns a reference to the #apply method, suitable for passing as
54
61
  # a closure
62
+ #
63
+ # @return [Proc] A procedure that updates a hash with credentials
55
64
  def updater_proc
56
65
  proc { |a_hash, _opts = {}| apply a_hash }
57
66
  end
67
+
68
+ # Returns the IAM authority selector as the principal
69
+ # @private
70
+ # @return [String] the IAM authoirty selector
71
+ def principal
72
+ @selector
73
+ end
58
74
  end
59
75
  end
60
76
  end
@@ -14,6 +14,8 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
+ require "googleauth/errors"
18
+
17
19
 
18
20
  module Google
19
21
  module Auth
@@ -21,35 +23,39 @@ module Google
21
23
  ##
22
24
  # Failed to obtain keys from the key source.
23
25
  #
24
- class KeySourceError < StandardError; end
26
+ class KeySourceError < StandardError
27
+ include Google::Auth::Error
28
+ end
25
29
 
26
30
  ##
27
31
  # Failed to verify a token.
28
32
  #
29
- class VerificationError < StandardError; end
33
+ class VerificationError < StandardError
34
+ include Google::Auth::Error
35
+ end
30
36
 
31
37
  ##
32
- # Failed to verify a token because it is expired.
38
+ # Failed to verify token because it is expired.
33
39
  #
34
40
  class ExpiredTokenError < VerificationError; end
35
41
 
36
42
  ##
37
- # Failed to verify a token because its signature did not match.
43
+ # Failed to verify token because its signature did not match.
38
44
  #
39
45
  class SignatureError < VerificationError; end
40
46
 
41
47
  ##
42
- # Failed to verify a token because its issuer did not match.
48
+ # Failed to verify token because its issuer did not match.
43
49
  #
44
50
  class IssuerMismatchError < VerificationError; end
45
51
 
46
52
  ##
47
- # Failed to verify a token because its audience did not match.
53
+ # Failed to verify token because its audience did not match.
48
54
  #
49
55
  class AudienceMismatchError < VerificationError; end
50
56
 
51
57
  ##
52
- # Failed to verify a token because its authorized party did not match.
58
+ # Failed to verify token because its authorized party did not match.
53
59
  #
54
60
  class AuthorizedPartyMismatchError < VerificationError; end
55
61
  end
@@ -72,8 +72,8 @@ module Google
72
72
  #
73
73
  # @param jwk [Hash,String] The JWK specification.
74
74
  # @return [KeyInfo]
75
- # @raise [KeySourceError] If the key could not be extracted from the
76
- # JWK.
75
+ # @raise [Google::Auth::IDTokens::KeySourceError] If the key could not be extracted from the
76
+ # JWK due to invalid type, malformed JSON, or invalid key data.
77
77
  #
78
78
  def from_jwk jwk
79
79
  jwk = symbolize_keys ensure_json_parsed jwk
@@ -94,10 +94,10 @@ module Google
94
94
  # Create an array of KeyInfo from a JWK Set, which may be given as
95
95
  # either a hash or an unparsed JSON string.
96
96
  #
97
- # @param jwk [Hash,String] The JWK Set specification.
97
+ # @param jwk_set [Hash,String] The JWK Set specification.
98
98
  # @return [Array<KeyInfo>]
99
- # @raise [KeySourceError] If a key could not be extracted from the
100
- # JWK Set.
99
+ # @raise [Google::Auth::IDTokens::KeySourceError] If a key could not be extracted from the
100
+ # JWK Set, or if the set contains no keys.
101
101
  #
102
102
  def from_jwk_set jwk_set
103
103
  jwk_set = symbolize_keys ensure_json_parsed jwk_set
@@ -261,7 +261,8 @@ module Google
261
261
  # return the new keys.
262
262
  #
263
263
  # @return [Array<KeyInfo>]
264
- # @raise [KeySourceError] if key retrieval failed.
264
+ # @raise [Google::Auth::IDTokens::KeySourceError] If key retrieval fails, JSON parsing
265
+ # fails, or the data cannot be interpreted as keys
265
266
  #
266
267
  def refresh_keys
267
268
  @monitor.synchronize do
@@ -310,6 +311,11 @@ module Google
310
311
 
311
312
  protected
312
313
 
314
+ # Interpret JSON data as X509 certificates
315
+ #
316
+ # @param data [Hash] The JSON data containing certificate strings
317
+ # @return [Array<KeyInfo>] Array of key info objects
318
+ # @raise [Google::Auth::IDTokens::KeySourceError] If X509 certificates cannot be parsed
313
319
  def interpret_json data
314
320
  data.map do |id, cert_str|
315
321
  key = OpenSSL::X509::Certificate.new(cert_str).public_key
@@ -371,7 +377,7 @@ module Google
371
377
  # Attempt to refresh keys and return the new keys.
372
378
  #
373
379
  # @return [Array<KeyInfo>]
374
- # @raise [KeySourceError] if key retrieval failed.
380
+ # @raise [Google::Auth::IDTokens::KeySourceError] If key retrieval failed for any source.
375
381
  #
376
382
  def refresh_keys
377
383
  @sources.flat_map(&:refresh_keys)
@@ -61,10 +61,9 @@ module Google
61
61
  # @param iss [String,nil] If given, override the `iss` check.
62
62
  #
63
63
  # @return [Hash] the decoded payload, if verification succeeded.
64
- # @raise [KeySourceError] if the key source failed to obtain public keys
65
- # @raise [VerificationError] if the token verification failed.
64
+ # @raise [Google::Auth::IDTokens::KeySourceError] if the key source failed to obtain public keys
65
+ # @raise [Google::Auth::IDTokens::VerificationError] if the token verification failed.
66
66
  # Additional data may be available in the error subclass and message.
67
- #
68
67
  def verify token,
69
68
  key_source: :default,
70
69
  aud: :default,
@@ -160,8 +160,8 @@ module Google
160
160
  # checking is performed. Default is to check against {OIDC_ISSUERS}.
161
161
  #
162
162
  # @return [Hash] The decoded token payload.
163
- # @raise [KeySourceError] if the key source failed to obtain public keys
164
- # @raise [VerificationError] if the token verification failed.
163
+ # @raise [Google::Auth::IDTokens::KeySourceError] if the key source failed to obtain public keys
164
+ # @raise [Google::Auth::IDTokens::VerificationError] if the token verification failed.
165
165
  # Additional data may be available in the error subclass and message.
166
166
  #
167
167
  def verify_oidc token,
@@ -197,8 +197,8 @@ module Google
197
197
  # checking is performed. Default is to check against {IAP_ISSUERS}.
198
198
  #
199
199
  # @return [Hash] The decoded token payload.
200
- # @raise [KeySourceError] if the key source failed to obtain public keys
201
- # @raise [VerificationError] if the token verification failed.
200
+ # @raise [Google::Auth::IDTokens::KeySourceError] if the key source failed to obtain public keys
201
+ # @raise [Google::Auth::IDTokens::VerificationError] if the token verification failed.
202
202
  # Additional data may be available in the error subclass and message.
203
203
  #
204
204
  def verify_iap token,
@@ -12,8 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- require "googleauth/signet"
16
15
  require "googleauth/base_client"
16
+ require "googleauth/errors"
17
17
  require "googleauth/helpers/connection"
18
18
 
19
19
  module Google
@@ -191,6 +191,20 @@ module Google
191
191
  self.class.new options
192
192
  end
193
193
 
194
+ # The principal behind the credentials. This class allows custom source credentials type
195
+ # that might not implement `principal`, in which case `:unknown` is returned.
196
+ #
197
+ # @private
198
+ # @return [String, Symbol] The string representation of the principal,
199
+ # the token type in lieu of the principal, or :unknown if source principal is unknown.
200
+ def principal
201
+ if @source_credentials.respond_to? :principal
202
+ @source_credentials.principal
203
+ else
204
+ :unknown
205
+ end
206
+ end
207
+
194
208
  private
195
209
 
196
210
  # Generates a new impersonation access token by exchanging the source credentials' token
@@ -200,21 +214,16 @@ module Google
200
214
  # for an impersonation token using the specified impersonation URL. The generated token and
201
215
  # its expiration time are cached for subsequent use.
202
216
  #
217
+ # @private
203
218
  # @param _options [Hash] (optional) Additional options for token retrieval (currently unused).
204
219
  #
205
- # @raise [Signet::UnexpectedStatusError] If the response status is 403 or 500.
206
- # @raise [Signet::AuthorizationError] For other unexpected response statuses.
220
+ # @raise [Google::Auth::UnexpectedStatusError] If the response status is 403 or 500.
221
+ # @raise [Google::Auth::AuthorizationError] For other unexpected response statuses.
207
222
  #
208
223
  # @return [String] The newly generated impersonation access token.
209
224
  def fetch_access_token! _options = {}
210
- auth_header = {}
211
- auth_header = @source_credentials.updater_proc.call auth_header
212
-
213
- resp = connection.post @impersonation_url do |req|
214
- req.headers.merge! auth_header
215
- req.headers["Content-Type"] = "application/json"
216
- req.body = MultiJson.dump({ scope: @scope })
217
- end
225
+ auth_header = prepare_auth_header
226
+ resp = make_impersonation_request auth_header
218
227
 
219
228
  case resp.status
220
229
  when 200
@@ -223,14 +232,51 @@ module Google
223
232
  @access_token = response["accessToken"]
224
233
  access_token
225
234
  when 403, 500
226
- msg = "Unexpected error code #{resp.status}.\n #{resp.env.response_body} #{ERROR_SUFFIX}"
227
- raise Signet::UnexpectedStatusError, msg
235
+ handle_error_response resp, UnexpectedStatusError
228
236
  else
229
- msg = "Unexpected error code #{resp.status}.\n #{resp.env.response_body} #{ERROR_SUFFIX}"
230
- raise Signet::AuthorizationError, msg
237
+ handle_error_response resp, AuthorizationError
238
+ end
239
+ end
240
+
241
+ # Prepares the authorization header for the impersonation request
242
+ # by fetching a token from source credentials.
243
+ #
244
+ # @private
245
+ # @return [Hash] The authorization header with the source credentials' token
246
+ def prepare_auth_header
247
+ auth_header = {}
248
+ @source_credentials.updater_proc.call auth_header
249
+ auth_header
250
+ end
251
+
252
+ # Makes the HTTP request to the impersonation endpoint.
253
+ #
254
+ # @private
255
+ # @param [Hash] auth_header The authorization header containing the source token
256
+ # @return [Faraday::Response] The HTTP response from the impersonation endpoint
257
+ def make_impersonation_request auth_header
258
+ connection.post @impersonation_url do |req|
259
+ req.headers.merge! auth_header
260
+ req.headers["Content-Type"] = "application/json"
261
+ req.body = MultiJson.dump({ scope: @scope })
231
262
  end
232
263
  end
233
264
 
265
+ # Creates and raises an appropriate error based on the response.
266
+ #
267
+ # @private
268
+ # @param [Faraday::Response] resp The HTTP response
269
+ # @param [Class] error_class The error class to instantiate
270
+ # @raise [StandardError] The appropriate error with details
271
+ def handle_error_response resp, error_class
272
+ msg = "Unexpected error code #{resp.status}.\n #{resp.env.response_body} #{ERROR_SUFFIX}"
273
+ raise error_class.with_details(
274
+ msg,
275
+ credential_type_name: self.class.name,
276
+ principal: principal
277
+ )
278
+ end
279
+
234
280
  # Setter for the expires_at value that makes sure it is converted
235
281
  # to Time object.
236
282
  def expires_at= new_expires_at
@@ -249,7 +295,7 @@ module Google
249
295
  #
250
296
  # @return [Time, nil] The normalized Time object, or nil if the input is nil.
251
297
  #
252
- # @raise [RuntimeError] If the input is not a Time, String, or nil.
298
+ # @raise [Google::Auth::CredentialsError] If the input is not a Time, String, or nil.
253
299
  def normalize_timestamp time
254
300
  case time
255
301
  when NilClass
@@ -259,7 +305,8 @@ module Google
259
305
  when String
260
306
  Time.parse time
261
307
  else
262
- raise "Invalid time value #{time}"
308
+ message = "Invalid time value #{time}"
309
+ raise CredentialsError.with_details(message, credential_type_name: self.class.name, principal: principal)
263
310
  end
264
311
  end
265
312