googleauth 1.14.0 → 1.15.1
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 +21 -0
- data/Credentials.md +110 -0
- data/Errors.md +152 -0
- data/lib/googleauth/api_key.rb +9 -0
- data/lib/googleauth/application_default.rb +3 -1
- data/lib/googleauth/base_client.rb +5 -0
- data/lib/googleauth/bearer_token.rb +16 -2
- data/lib/googleauth/client_id.rb +9 -5
- data/lib/googleauth/compute_engine.rb +64 -18
- data/lib/googleauth/credentials.rb +67 -35
- data/lib/googleauth/credentials_loader.rb +24 -4
- data/lib/googleauth/default_credentials.rb +64 -32
- data/lib/googleauth/errors.rb +117 -0
- data/lib/googleauth/external_account/aws_credentials.rb +85 -18
- data/lib/googleauth/external_account/base_credentials.rb +31 -2
- data/lib/googleauth/external_account/external_account_utils.rb +15 -4
- data/lib/googleauth/external_account/identity_pool_credentials.rb +40 -15
- data/lib/googleauth/external_account/pluggable_credentials.rb +34 -19
- data/lib/googleauth/external_account.rb +35 -6
- data/lib/googleauth/iam.rb +19 -3
- data/lib/googleauth/id_tokens/errors.rb +13 -7
- data/lib/googleauth/id_tokens/key_sources.rb +13 -7
- data/lib/googleauth/id_tokens/verifier.rb +2 -3
- data/lib/googleauth/id_tokens.rb +4 -4
- data/lib/googleauth/impersonated_service_account.rb +64 -17
- data/lib/googleauth/json_key_reader.rb +11 -2
- data/lib/googleauth/oauth2/sts_client.rb +9 -4
- data/lib/googleauth/scope_util.rb +1 -1
- data/lib/googleauth/service_account.rb +37 -10
- data/lib/googleauth/service_account_jwt_header.rb +9 -2
- data/lib/googleauth/signet.rb +24 -6
- data/lib/googleauth/user_authorizer.rb +35 -7
- data/lib/googleauth/user_refresh.rb +42 -16
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +46 -9
- data/lib/googleauth.rb +1 -0
- metadata +8 -5
@@ -13,6 +13,7 @@
|
|
13
13
|
# limitations under the License.require "time"
|
14
14
|
|
15
15
|
require "googleauth/base_client"
|
16
|
+
require "googleauth/errors"
|
16
17
|
require "googleauth/helpers/connection"
|
17
18
|
require "googleauth/oauth2/sts_client"
|
18
19
|
|
@@ -89,6 +90,14 @@ module Google
|
|
89
90
|
%r{/iam\.googleapis\.com/locations/[^/]+/workforcePools/}.match?(@audience || "")
|
90
91
|
end
|
91
92
|
|
93
|
+
# For external account credentials, the principal is
|
94
|
+
# represented by the audience, such as a workforce pool
|
95
|
+
# @private
|
96
|
+
# @return [String] the GCP principal, e.g. a workforce pool
|
97
|
+
def principal
|
98
|
+
@audience
|
99
|
+
end
|
100
|
+
|
92
101
|
private
|
93
102
|
|
94
103
|
def token_type
|
@@ -96,6 +105,8 @@ module Google
|
|
96
105
|
:access_token
|
97
106
|
end
|
98
107
|
|
108
|
+
# A common method for Other credentials to call during initialization
|
109
|
+
# @raise [Google::Auth::InitializationError] If workforce_pool_user_project is incorrectly set
|
99
110
|
def base_setup options
|
100
111
|
self.default_connection = options[:connection]
|
101
112
|
|
@@ -121,9 +132,11 @@ module Google
|
|
121
132
|
connection: default_connection
|
122
133
|
)
|
123
134
|
return unless @workforce_pool_user_project && !is_workforce_pool?
|
124
|
-
raise "workforce_pool_user_project should not be set for non-workforce pool credentials."
|
135
|
+
raise InitializationError, "workforce_pool_user_project should not be set for non-workforce pool credentials."
|
125
136
|
end
|
126
137
|
|
138
|
+
# Exchange tokens at STS endpoint
|
139
|
+
# @raise [Google::Auth::AuthorizationError] If the token exchange request fails
|
127
140
|
def exchange_token
|
128
141
|
additional_options = nil
|
129
142
|
if @client_id.nil? && @workforce_pool_user_project
|
@@ -140,6 +153,12 @@ module Google
|
|
140
153
|
}
|
141
154
|
log_token_request token_request
|
142
155
|
@sts_client.exchange_token token_request
|
156
|
+
rescue Google::Auth::AuthorizationError => e
|
157
|
+
raise Google::Auth::AuthorizationError.with_details(
|
158
|
+
e.message,
|
159
|
+
credential_type_name: self.class.name,
|
160
|
+
principal: principal
|
161
|
+
)
|
143
162
|
end
|
144
163
|
|
145
164
|
def log_token_request token_request
|
@@ -160,6 +179,12 @@ module Google
|
|
160
179
|
end
|
161
180
|
end
|
162
181
|
|
182
|
+
# Exchanges a token for an impersonated service account access token
|
183
|
+
#
|
184
|
+
# @param [String] token The token to exchange
|
185
|
+
# @param [Hash] _options Additional options (not used)
|
186
|
+
# @return [Hash] The response containing the impersonated access token
|
187
|
+
# @raise [Google::Auth::CredentialsError] If the impersonation request fails
|
163
188
|
def get_impersonated_access_token token, _options = {}
|
164
189
|
log_impersonated_token_request token
|
165
190
|
response = connection.post @service_account_impersonation_url do |req|
|
@@ -169,7 +194,11 @@ module Google
|
|
169
194
|
end
|
170
195
|
|
171
196
|
if response.status != 200
|
172
|
-
raise
|
197
|
+
raise CredentialsError.with_details(
|
198
|
+
"Service account impersonation failed with status #{response.status}",
|
199
|
+
credential_type_name: self.class.name,
|
200
|
+
principal: principal
|
201
|
+
)
|
173
202
|
end
|
174
203
|
|
175
204
|
MultiJson.load response.body
|
@@ -13,6 +13,7 @@
|
|
13
13
|
# limitations under the License.require "time"
|
14
14
|
|
15
15
|
require "googleauth/base_client"
|
16
|
+
require "googleauth/errors"
|
16
17
|
require "googleauth/helpers/connection"
|
17
18
|
require "googleauth/oauth2/sts_client"
|
18
19
|
|
@@ -36,8 +37,8 @@ module Google
|
|
36
37
|
# call this API or the required scopes may not be selected:
|
37
38
|
# https://cloud.google.com/resource-manager/reference/rest/v1/projects/get#authorization-scopes
|
38
39
|
#
|
39
|
-
# @return [
|
40
|
-
#
|
40
|
+
# @return [String, nil] The project ID corresponding to the workload identity
|
41
|
+
# pool or workforce pool if determinable
|
41
42
|
#
|
42
43
|
def project_id
|
43
44
|
return @project_id unless @project_id.nil?
|
@@ -65,7 +66,8 @@ module Google
|
|
65
66
|
# STS audience pattern:
|
66
67
|
# `//iam.googleapis.com/projects/$PROJECT_NUMBER/locations/...`
|
67
68
|
#
|
68
|
-
# @return [
|
69
|
+
# @return [String, nil] The project number extracted from the audience string,
|
70
|
+
# or nil if it cannot be determined
|
69
71
|
#
|
70
72
|
def project_number
|
71
73
|
segments = @audience.split "/"
|
@@ -74,6 +76,11 @@ module Google
|
|
74
76
|
segments[idx + 1]
|
75
77
|
end
|
76
78
|
|
79
|
+
# Normalizes a timestamp value to a Time object
|
80
|
+
#
|
81
|
+
# @param time [Time, String, nil] The timestamp to normalize
|
82
|
+
# @return [Time, nil] The normalized timestamp or nil if input is nil
|
83
|
+
# @raise [Google::Auth::CredentialsError] If the time value is not nil, Time, or String
|
77
84
|
def normalize_timestamp time
|
78
85
|
case time
|
79
86
|
when NilClass
|
@@ -83,10 +90,14 @@ module Google
|
|
83
90
|
when String
|
84
91
|
Time.parse time
|
85
92
|
else
|
86
|
-
raise "Invalid time value #{time}"
|
93
|
+
raise CredentialsError, "Invalid time value #{time}"
|
87
94
|
end
|
88
95
|
end
|
89
96
|
|
97
|
+
# Extracts the service account email from the impersonation URL
|
98
|
+
#
|
99
|
+
# @return [String, nil] The service account email extracted from the
|
100
|
+
# service_account_impersonation_url, or nil if it cannot be determined
|
90
101
|
def service_account_email
|
91
102
|
return nil if @service_account_impersonation_url.nil?
|
92
103
|
start_idx = @service_account_impersonation_url.rindex "/"
|
@@ -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 [
|
36
|
-
# @
|
37
|
-
#
|
38
|
-
# credential_source_format is either text or json
|
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
|
-
|
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
|
-
|
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 [
|
45
|
-
# @
|
46
|
-
#
|
47
|
-
#
|
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
|
-
|
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
|
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
|
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
|
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"
|
@@ -33,32 +34,48 @@ module Google
|
|
33
34
|
MISSING_CREDENTIAL_SOURCE = "missing credential source for external account".freeze
|
34
35
|
INVALID_EXTERNAL_ACCOUNT_TYPE = "credential source is not supported external account type".freeze
|
35
36
|
|
37
|
+
# @private
|
38
|
+
# @type [::String] The type name for this credential.
|
39
|
+
CREDENTIAL_TYPE_NAME = "external_account".freeze
|
40
|
+
|
36
41
|
# Create a ExternalAccount::Credentials
|
37
42
|
#
|
38
|
-
# @param
|
39
|
-
# @
|
43
|
+
# @param options [Hash] Options for creating credentials
|
44
|
+
# @option options [IO] :json_key_io (required) An IO object containing the JSON key
|
45
|
+
# @option options [String,Array,nil] :scope The scope(s) to access
|
46
|
+
# @return [Google::Auth::ExternalAccount::AwsCredentials,
|
47
|
+
# Google::Auth::ExternalAccount::IdentityPoolCredentials,
|
48
|
+
# Google::Auth::ExternalAccount::PluggableAuthCredentials]
|
49
|
+
# The appropriate external account credentials based on the credential source
|
50
|
+
# @raise [Google::Auth::InitializationError] If the json file is missing, lacks required fields,
|
51
|
+
# or does not contain a supported credential source
|
40
52
|
def self.make_creds options = {}
|
41
53
|
json_key_io, scope = options.values_at :json_key_io, :scope
|
42
54
|
|
43
|
-
raise "A json file is required for external account credentials." unless json_key_io
|
55
|
+
raise InitializationError, "A json file is required for external account credentials." unless json_key_io
|
56
|
+
CredentialsLoader.load_and_verify_json_key_type json_key_io, CREDENTIAL_TYPE_NAME
|
44
57
|
user_creds = read_json_key json_key_io
|
45
58
|
|
46
59
|
# AWS credentials is determined by aws subject token type
|
47
60
|
return make_aws_credentials user_creds, scope if user_creds[:subject_token_type] == AWS_SUBJECT_TOKEN_TYPE
|
48
61
|
|
49
|
-
raise MISSING_CREDENTIAL_SOURCE if user_creds[:credential_source].nil?
|
62
|
+
raise InitializationError, MISSING_CREDENTIAL_SOURCE if user_creds[:credential_source].nil?
|
50
63
|
user_creds[:scope] = scope
|
51
64
|
make_external_account_credentials user_creds
|
52
65
|
end
|
53
66
|
|
54
67
|
# Reads the required fields from the JSON.
|
68
|
+
#
|
69
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
70
|
+
# @return [Hash] The parsed JSON key
|
71
|
+
# @raise [Google::Auth::InitializationError] If the JSON is missing required fields
|
55
72
|
def self.read_json_key json_key_io
|
56
73
|
json_key = MultiJson.load json_key_io.read, symbolize_keys: true
|
57
74
|
wanted = [
|
58
75
|
:audience, :subject_token_type, :token_url, :credential_source
|
59
76
|
]
|
60
77
|
wanted.each do |key|
|
61
|
-
raise "the json is missing the #{key} field" unless json_key.key? key
|
78
|
+
raise InitializationError, "the json is missing the #{key} field" unless json_key.key? key
|
62
79
|
end
|
63
80
|
json_key
|
64
81
|
end
|
@@ -66,6 +83,11 @@ module Google
|
|
66
83
|
class << self
|
67
84
|
private
|
68
85
|
|
86
|
+
# Creates AWS credentials from the provided user credentials
|
87
|
+
#
|
88
|
+
# @param user_creds [Hash] The user credentials containing AWS credential source information
|
89
|
+
# @param scope [String,Array,nil] The scope(s) to access
|
90
|
+
# @return [Google::Auth::ExternalAccount::AwsCredentials] The AWS credentials
|
69
91
|
def make_aws_credentials user_creds, scope
|
70
92
|
Google::Auth::ExternalAccount::AwsCredentials.new(
|
71
93
|
audience: user_creds[:audience],
|
@@ -78,6 +100,13 @@ module Google
|
|
78
100
|
)
|
79
101
|
end
|
80
102
|
|
103
|
+
# Creates the appropriate external account credentials based on the credential source type
|
104
|
+
#
|
105
|
+
# @param user_creds [Hash] The user credentials containing credential source information
|
106
|
+
# @return [Google::Auth::ExternalAccount::IdentityPoolCredentials,
|
107
|
+
# Google::Auth::ExternalAccount::PluggableAuthCredentials]
|
108
|
+
# The appropriate external account credentials
|
109
|
+
# @raise [Google::Auth::InitializationError] If the credential source is not a supported type
|
81
110
|
def make_external_account_credentials user_creds
|
82
111
|
unless user_creds[:credential_source][:file].nil? && user_creds[:credential_source][:url].nil?
|
83
112
|
return Google::Auth::ExternalAccount::IdentityPoolCredentials.new user_creds
|
@@ -85,7 +114,7 @@ module Google
|
|
85
114
|
unless user_creds[:credential_source][:executable].nil?
|
86
115
|
return Google::Auth::ExternalAccount::PluggableAuthCredentials.new user_creds
|
87
116
|
end
|
88
|
-
raise INVALID_EXTERNAL_ACCOUNT_TYPE
|
117
|
+
raise InitializationError, INVALID_EXTERNAL_ACCOUNT_TYPE
|
89
118
|
end
|
90
119
|
end
|
91
120
|
end
|
data/lib/googleauth/iam.rb
CHANGED
@@ -27,8 +27,9 @@ module Google
|
|
27
27
|
|
28
28
|
# Initializes an IAMCredentials.
|
29
29
|
#
|
30
|
-
# @param selector
|
31
|
-
# @param 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
|
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
|
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
|
33
|
+
class VerificationError < StandardError
|
34
|
+
include Google::Auth::Error
|
35
|
+
end
|
30
36
|
|
31
37
|
##
|
32
|
-
# Failed to verify
|
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
|
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
|
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
|
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
|
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
|
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]
|
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]
|
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,
|