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
data/lib/googleauth/id_tokens.rb
CHANGED
@@ -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 [
|
206
|
-
# @raise [
|
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
|
-
|
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
|
-
|
227
|
-
raise Signet::UnexpectedStatusError, msg
|
235
|
+
handle_error_response resp, UnexpectedStatusError
|
228
236
|
else
|
229
|
-
|
230
|
-
|
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 [
|
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
|
-
|
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
|
|
@@ -12,6 +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/errors"
|
16
|
+
|
15
17
|
module Google
|
16
18
|
# Module Auth provides classes that provide Google-specific authorization
|
17
19
|
# used to access Google APIs.
|
@@ -19,10 +21,17 @@ module Google
|
|
19
21
|
# JsonKeyReader contains the behaviour used to read private key and
|
20
22
|
# client email fields from the service account
|
21
23
|
module JsonKeyReader
|
24
|
+
# Reads a JSON key from an IO object and extracts common fields.
|
25
|
+
#
|
26
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
27
|
+
# @return [Array(String, String, String, String, String)] An array containing:
|
28
|
+
# private_key, client_email, project_id, quota_project_id, and universe_domain
|
29
|
+
# @raise [Google::Auth::InitializationError] If client_email or private_key
|
30
|
+
# fields are missing from the JSON
|
22
31
|
def read_json_key json_key_io
|
23
32
|
json_key = MultiJson.load json_key_io.read
|
24
|
-
raise "missing client_email" unless json_key.key? "client_email"
|
25
|
-
raise "missing private_key" unless json_key.key? "private_key"
|
33
|
+
raise InitializationError, "missing client_email" unless json_key.key? "client_email"
|
34
|
+
raise InitializationError, "missing private_key" unless json_key.key? "private_key"
|
26
35
|
[
|
27
36
|
json_key["private_key"],
|
28
37
|
json_key["client_email"],
|
@@ -12,6 +12,7 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
require "googleauth/errors"
|
15
16
|
require "googleauth/helpers/connection"
|
16
17
|
|
17
18
|
module Google
|
@@ -36,10 +37,12 @@ module Google
|
|
36
37
|
|
37
38
|
# Create a new instance of the STSClient.
|
38
39
|
#
|
39
|
-
# @param [
|
40
|
-
#
|
40
|
+
# @param [Hash] options Configuration options
|
41
|
+
# @option options [String] :token_exchange_endpoint The token exchange endpoint
|
42
|
+
# @option options [Faraday::Connection] :connection The Faraday connection to use
|
43
|
+
# @raise [Google::Auth::InitializationError] If token_exchange_endpoint is nil
|
41
44
|
def initialize options = {}
|
42
|
-
raise "Token exchange endpoint can not be nil" if options[:token_exchange_endpoint].nil?
|
45
|
+
raise InitializationError, "Token exchange endpoint can not be nil" if options[:token_exchange_endpoint].nil?
|
43
46
|
self.default_connection = options[:connection]
|
44
47
|
@token_exchange_endpoint = options[:token_exchange_endpoint]
|
45
48
|
end
|
@@ -67,6 +70,8 @@ module Google
|
|
67
70
|
# The optional additional headers to pass to the token exchange endpoint.
|
68
71
|
#
|
69
72
|
# @return [Hash] A hash containing the token exchange response.
|
73
|
+
# @raise [ArgumentError] If required options are missing
|
74
|
+
# @raise [Google::Auth::AuthorizationError] If the token exchange request fails
|
70
75
|
def exchange_token options = {}
|
71
76
|
missing_required_opts = [:grant_type, :subject_token, :subject_token_type] - options.keys
|
72
77
|
unless missing_required_opts.empty?
|
@@ -81,7 +86,7 @@ module Google
|
|
81
86
|
response = connection.post @token_exchange_endpoint, URI.encode_www_form(request_body), headers
|
82
87
|
|
83
88
|
if response.status != 200
|
84
|
-
raise "Token exchange failed with status #{response.status}"
|
89
|
+
raise AuthorizationError, "Token exchange failed with status #{response.status}"
|
85
90
|
end
|
86
91
|
|
87
92
|
MultiJson.load response.body
|
@@ -57,7 +57,7 @@ module Google
|
|
57
57
|
#
|
58
58
|
# @param scope [String,Array<String>] Input scope(s)
|
59
59
|
# @return [Array<String>] Always an array of strings
|
60
|
-
# @raise ArgumentError If the input is not a string or array of strings
|
60
|
+
# @raise [ArgumentError] If the input is not a string or array of strings
|
61
61
|
#
|
62
62
|
def self.as_array scope
|
63
63
|
case scope
|
@@ -41,6 +41,10 @@ module Google
|
|
41
41
|
attr_reader :project_id
|
42
42
|
attr_reader :quota_project_id
|
43
43
|
|
44
|
+
# @private
|
45
|
+
# @type [::String] The type name for this credential.
|
46
|
+
CREDENTIAL_TYPE_NAME = "service_account".freeze
|
47
|
+
|
44
48
|
def enable_self_signed_jwt?
|
45
49
|
# Use a self-singed JWT if there's no information that can be used to
|
46
50
|
# obtain an OAuth token, OR if there are scopes but also an assertion
|
@@ -51,23 +55,22 @@ module Google
|
|
51
55
|
|
52
56
|
# Creates a ServiceAccountCredentials.
|
53
57
|
#
|
54
|
-
# @param json_key_io [IO]
|
58
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
55
59
|
# @param scope [string|array|nil] the scope(s) to access
|
60
|
+
# @raise [ArgumentError] If both scope and target_audience are specified
|
56
61
|
def self.make_creds options = {}
|
57
62
|
json_key_io, scope, enable_self_signed_jwt, target_audience, audience, token_credential_uri =
|
58
63
|
options.values_at :json_key_io, :scope, :enable_self_signed_jwt, :target_audience,
|
59
64
|
:audience, :token_credential_uri
|
60
65
|
raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience
|
61
66
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
universe_domain = nil
|
70
|
-
end
|
67
|
+
private_key, client_email, project_id, quota_project_id, universe_domain =
|
68
|
+
if json_key_io
|
69
|
+
CredentialsLoader.load_and_verify_json_key_type json_key_io, CREDENTIAL_TYPE_NAME
|
70
|
+
read_json_key json_key_io
|
71
|
+
else
|
72
|
+
creds_from_env
|
73
|
+
end
|
71
74
|
project_id ||= CredentialsLoader.load_gcloud_project_id
|
72
75
|
|
73
76
|
new(token_credential_uri: token_credential_uri || TOKEN_CRED_URI,
|
@@ -110,6 +113,9 @@ module Google
|
|
110
113
|
# Handles certain escape sequences that sometimes appear in input.
|
111
114
|
# Specifically, interprets the "\n" sequence for newline, and removes
|
112
115
|
# enclosing quotes.
|
116
|
+
#
|
117
|
+
# @param str [String] The string to unescape
|
118
|
+
# @return [String] The unescaped string
|
113
119
|
def self.unescape str
|
114
120
|
str = str.gsub '\n', "\n"
|
115
121
|
str = str[1..-2] if str.start_with?('"') && str.end_with?('"')
|
@@ -164,6 +170,13 @@ module Google
|
|
164
170
|
self
|
165
171
|
end
|
166
172
|
|
173
|
+
# Returns the client email as the principal for service account credentials
|
174
|
+
# @private
|
175
|
+
# @return [String] the email address of the service account
|
176
|
+
def principal
|
177
|
+
@issuer
|
178
|
+
end
|
179
|
+
|
167
180
|
private
|
168
181
|
|
169
182
|
def apply_self_signed_jwt! a_hash
|
@@ -179,6 +192,20 @@ module Google
|
|
179
192
|
alt.logger = logger
|
180
193
|
alt.apply! a_hash
|
181
194
|
end
|
195
|
+
|
196
|
+
# @private
|
197
|
+
# Loads service account credential details from environment variables.
|
198
|
+
#
|
199
|
+
# @return [Array<String, String, String, nil, nil>] An array containing private_key,
|
200
|
+
# client_email, project_id, quota_project_id, and universe_domain.
|
201
|
+
def self.creds_from_env
|
202
|
+
private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
203
|
+
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
204
|
+
project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
205
|
+
[private_key, client_email, project_id, nil, nil]
|
206
|
+
end
|
207
|
+
|
208
|
+
private_class_method :creds_from_env
|
182
209
|
end
|
183
210
|
end
|
184
211
|
end
|
@@ -47,7 +47,7 @@ module Google
|
|
47
47
|
|
48
48
|
# Create a ServiceAccountJwtHeaderCredentials.
|
49
49
|
#
|
50
|
-
# @param json_key_io [IO]
|
50
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
51
51
|
# @param scope [string|array|nil] the scope(s) to access
|
52
52
|
def self.make_creds options = {}
|
53
53
|
json_key_io, scope = options.values_at :json_key_io, :scope
|
@@ -56,7 +56,7 @@ module Google
|
|
56
56
|
|
57
57
|
# Initializes a ServiceAccountJwtHeaderCredentials.
|
58
58
|
#
|
59
|
-
# @param json_key_io [IO]
|
59
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
60
60
|
def initialize options = {}
|
61
61
|
json_key_io = options[:json_key_io]
|
62
62
|
if json_key_io
|
@@ -159,6 +159,13 @@ module Google
|
|
159
159
|
false
|
160
160
|
end
|
161
161
|
|
162
|
+
# Returns the client email as the principal for service account JWT header credentials
|
163
|
+
# @private
|
164
|
+
# @return [String] the email address of the service account
|
165
|
+
def principal
|
166
|
+
@issuer
|
167
|
+
end
|
168
|
+
|
162
169
|
private
|
163
170
|
|
164
171
|
def deep_hash_normalize old_hash
|
data/lib/googleauth/signet.rb
CHANGED
@@ -16,6 +16,7 @@ require "base64"
|
|
16
16
|
require "json"
|
17
17
|
require "signet/oauth_2/client"
|
18
18
|
require "googleauth/base_client"
|
19
|
+
require "googleauth/errors"
|
19
20
|
|
20
21
|
module Signet
|
21
22
|
# OAuth2 supports OAuth2 authentication.
|
@@ -109,17 +110,29 @@ module Signet
|
|
109
110
|
end
|
110
111
|
end
|
111
112
|
|
113
|
+
# rubocop:disable Metrics/MethodLength
|
114
|
+
|
115
|
+
# Retries the provided block with exponential backoff, handling and wrapping errors.
|
116
|
+
#
|
117
|
+
# @param [Integer] max_retry_count The maximum number of retries before giving up
|
118
|
+
# @yield The block to execute and potentially retry
|
119
|
+
# @return [Object] The result of the block if successful
|
120
|
+
# @raise [Google::Auth::AuthorizationError] If a Signet::AuthorizationError occurs or if retries are exhausted
|
121
|
+
# @raise [Google::Auth::ParseError] If a Signet::ParseError occurs during token parsing
|
112
122
|
def retry_with_error max_retry_count = 5
|
113
123
|
retry_count = 0
|
114
124
|
|
115
125
|
begin
|
116
126
|
yield.tap { |resp| log_response resp }
|
127
|
+
rescue Signet::AuthorizationError, Signet::ParseError => e
|
128
|
+
log_auth_error e
|
129
|
+
error_class = e.is_a?(Signet::ParseError) ? Google::Auth::ParseError : Google::Auth::AuthorizationError
|
130
|
+
raise error_class.with_details(
|
131
|
+
e.message,
|
132
|
+
credential_type_name: self.class.name,
|
133
|
+
principal: respond_to?(:principal) ? principal : :signet_client
|
134
|
+
)
|
117
135
|
rescue StandardError => e
|
118
|
-
if e.is_a?(Signet::AuthorizationError) || e.is_a?(Signet::ParseError)
|
119
|
-
log_auth_error e
|
120
|
-
raise e
|
121
|
-
end
|
122
|
-
|
123
136
|
if retry_count < max_retry_count
|
124
137
|
log_transient_error e
|
125
138
|
retry_count += 1
|
@@ -128,10 +141,15 @@ module Signet
|
|
128
141
|
else
|
129
142
|
log_retries_exhausted e
|
130
143
|
msg = "Unexpected error: #{e.inspect}"
|
131
|
-
raise
|
144
|
+
raise Google::Auth::AuthorizationError.with_details(
|
145
|
+
msg,
|
146
|
+
credential_type_name: self.class.name,
|
147
|
+
principal: respond_to?(:principal) ? principal : :signet_client
|
148
|
+
)
|
132
149
|
end
|
133
150
|
end
|
134
151
|
end
|
152
|
+
# rubocop:enable Metrics/MethodLength
|
135
153
|
|
136
154
|
# Creates a duplicate of these credentials
|
137
155
|
# without the Signet::OAuth2::Client-specific
|
@@ -63,12 +63,14 @@ module Google
|
|
63
63
|
# @param [String] code_verifier
|
64
64
|
# Random string of 43-128 chars used to verify the key exchange using
|
65
65
|
# PKCE.
|
66
|
+
# @raise [Google::Auth::InitializationError]
|
67
|
+
# If client_id is nil or scope is nil
|
66
68
|
def initialize client_id, scope, token_store,
|
67
69
|
legacy_callback_uri = nil,
|
68
70
|
callback_uri: nil,
|
69
71
|
code_verifier: nil
|
70
|
-
raise NIL_CLIENT_ID_ERROR if client_id.nil?
|
71
|
-
raise NIL_SCOPE_ERROR if scope.nil?
|
72
|
+
raise InitializationError, NIL_CLIENT_ID_ERROR if client_id.nil?
|
73
|
+
raise InitializationError, NIL_SCOPE_ERROR if scope.nil?
|
72
74
|
|
73
75
|
@client_id = client_id
|
74
76
|
@scope = Array(scope)
|
@@ -133,14 +135,19 @@ module Google
|
|
133
135
|
# the requested scopes
|
134
136
|
# @return [Google::Auth::UserRefreshCredentials]
|
135
137
|
# Stored credentials, nil if none present
|
138
|
+
# @raise [Google::Auth::CredentialsError]
|
139
|
+
# If the client ID in the stored token doesn't match the configured client ID
|
136
140
|
def get_credentials user_id, scope = nil
|
137
141
|
saved_token = stored_token user_id
|
138
142
|
return nil if saved_token.nil?
|
139
143
|
data = MultiJson.load saved_token
|
140
144
|
|
141
145
|
if data.fetch("client_id", @client_id.id) != @client_id.id
|
142
|
-
raise
|
143
|
-
|
146
|
+
raise CredentialsError.with_details(
|
147
|
+
format(MISMATCHED_CLIENT_ID_ERROR, data["client_id"], @client_id.id),
|
148
|
+
credential_type_name: self.class.name,
|
149
|
+
principal: principal
|
150
|
+
)
|
144
151
|
end
|
145
152
|
|
146
153
|
credentials = UserRefreshCredentials.new(
|
@@ -240,6 +247,8 @@ module Google
|
|
240
247
|
# Unique ID of the user for loading/storing credentials.
|
241
248
|
# @param [Google::Auth::UserRefreshCredentials] credentials
|
242
249
|
# Credentials to store.
|
250
|
+
# @return [Google::Auth::UserRefreshCredentials]
|
251
|
+
# The stored credentials
|
243
252
|
def store_credentials user_id, credentials
|
244
253
|
json = MultiJson.dump(
|
245
254
|
client_id: credentials.client_id,
|
@@ -269,6 +278,15 @@ module Google
|
|
269
278
|
SecureRandom.alphanumeric random_number
|
270
279
|
end
|
271
280
|
|
281
|
+
# Returns the principal identifier for this authorizer
|
282
|
+
# The client ID is used as the principal for user authorizers
|
283
|
+
#
|
284
|
+
# @private
|
285
|
+
# @return [String] The client ID associated with this authorizer
|
286
|
+
def principal
|
287
|
+
@client_id.id
|
288
|
+
end
|
289
|
+
|
272
290
|
private
|
273
291
|
|
274
292
|
# @private Fetch stored token with given user_id
|
@@ -276,9 +294,11 @@ module Google
|
|
276
294
|
# @param [String] user_id
|
277
295
|
# Unique ID of the user for loading/storing credentials.
|
278
296
|
# @return [String] The saved token from @token_store
|
297
|
+
# @raise [Google::Auth::InitializationError]
|
298
|
+
# If user_id is nil or token_store is nil
|
279
299
|
def stored_token user_id
|
280
|
-
raise NIL_USER_ID_ERROR if user_id.nil?
|
281
|
-
raise NIL_TOKEN_STORE_ERROR if @token_store.nil?
|
300
|
+
raise InitializationError, NIL_USER_ID_ERROR if user_id.nil?
|
301
|
+
raise InitializationError, NIL_TOKEN_STORE_ERROR if @token_store.nil?
|
282
302
|
|
283
303
|
@token_store.load user_id
|
284
304
|
end
|
@@ -303,9 +323,17 @@ module Google
|
|
303
323
|
# Absolute URL to resolve the callback against if necessary.
|
304
324
|
# @return [String]
|
305
325
|
# Redirect URI
|
326
|
+
# @raise [Google::Auth::CredentialsError]
|
327
|
+
# If the callback URI is relative and base_url is nil or not absolute
|
306
328
|
def redirect_uri_for base_url
|
307
329
|
return @callback_uri if uri_is_postmessage?(@callback_uri) || !URI(@callback_uri).scheme.nil?
|
308
|
-
|
330
|
+
if base_url.nil? || URI(base_url).scheme.nil?
|
331
|
+
raise CredentialsError.with_details(
|
332
|
+
format(MISSING_ABSOLUTE_URL_ERROR, @callback_uri),
|
333
|
+
credential_type_name: self.class.name,
|
334
|
+
principal: principal
|
335
|
+
)
|
336
|
+
end
|
309
337
|
URI.join(base_url, @callback_uri).to_s
|
310
338
|
end
|
311
339
|
|
@@ -12,9 +12,10 @@
|
|
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/credentials_loader"
|
16
|
+
require "googleauth/errors"
|
17
17
|
require "googleauth/scope_util"
|
18
|
+
require "googleauth/signet"
|
18
19
|
require "multi_json"
|
19
20
|
|
20
21
|
module Google
|
@@ -38,21 +39,29 @@ module Google
|
|
38
39
|
attr_reader :project_id
|
39
40
|
attr_reader :quota_project_id
|
40
41
|
|
42
|
+
# @private
|
43
|
+
# @type [::String] The type name for this credential.
|
44
|
+
CREDENTIAL_TYPE_NAME = "authorized_user".freeze
|
45
|
+
|
41
46
|
# Create a UserRefreshCredentials.
|
42
47
|
#
|
43
|
-
# @param json_key_io [IO]
|
48
|
+
# @param json_key_io [IO] An IO object containing the JSON key
|
44
49
|
# @param scope [string|array|nil] the scope(s) to access
|
45
50
|
def self.make_creds options = {}
|
46
51
|
json_key_io, scope = options.values_at :json_key_io, :scope
|
47
|
-
user_creds =
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
user_creds = if json_key_io
|
53
|
+
CredentialsLoader.load_and_verify_json_key_type json_key_io, CREDENTIAL_TYPE_NAME
|
54
|
+
read_json_key json_key_io
|
55
|
+
else
|
56
|
+
{
|
57
|
+
"client_id" => ENV[CredentialsLoader::CLIENT_ID_VAR],
|
58
|
+
"client_secret" => ENV[CredentialsLoader::CLIENT_SECRET_VAR],
|
59
|
+
"refresh_token" => ENV[CredentialsLoader::REFRESH_TOKEN_VAR],
|
60
|
+
"project_id" => ENV[CredentialsLoader::PROJECT_ID_VAR],
|
61
|
+
"quota_project_id" => nil,
|
62
|
+
"universe_domain" => nil
|
63
|
+
}
|
64
|
+
end
|
56
65
|
new(token_credential_uri: TOKEN_CRED_URI,
|
57
66
|
client_id: user_creds["client_id"],
|
58
67
|
client_secret: user_creds["client_secret"],
|
@@ -64,13 +73,16 @@ module Google
|
|
64
73
|
.configure_connection(options)
|
65
74
|
end
|
66
75
|
|
67
|
-
# Reads
|
68
|
-
#
|
76
|
+
# Reads a JSON key from an IO object and extracts required fields.
|
77
|
+
#
|
78
|
+
# @param [IO] json_key_io An IO object containing the JSON key
|
79
|
+
# @return [Hash] The parsed JSON key
|
80
|
+
# @raise [Google::Auth::InitializationError] If the JSON is missing required fields
|
69
81
|
def self.read_json_key json_key_io
|
70
82
|
json_key = MultiJson.load json_key_io.read
|
71
83
|
wanted = ["client_id", "client_secret", "refresh_token"]
|
72
84
|
wanted.each do |key|
|
73
|
-
raise "the json is missing the #{key} field" unless json_key.key? key
|
85
|
+
raise InitializationError, "the json is missing the #{key} field" unless json_key.key? key
|
74
86
|
end
|
75
87
|
json_key
|
76
88
|
end
|
@@ -106,6 +118,10 @@ module Google
|
|
106
118
|
end
|
107
119
|
|
108
120
|
# Revokes the credential
|
121
|
+
#
|
122
|
+
# @param [Hash] options Options for revoking the credential
|
123
|
+
# @option options [Faraday::Connection] :connection The connection to use
|
124
|
+
# @raise [Google::Auth::AuthorizationError] If the revocation request fails
|
109
125
|
def revoke! options = {}
|
110
126
|
c = options[:connection] || Faraday.default_connection
|
111
127
|
|
@@ -117,8 +133,11 @@ module Google
|
|
117
133
|
self.refresh_token = nil
|
118
134
|
self.expires_at = 0
|
119
135
|
else
|
120
|
-
raise(
|
121
|
-
|
136
|
+
raise AuthorizationError.with_details(
|
137
|
+
"Unexpected error code #{resp.status}",
|
138
|
+
credential_type_name: self.class.name,
|
139
|
+
principal: principal
|
140
|
+
)
|
122
141
|
end
|
123
142
|
end
|
124
143
|
end
|
@@ -157,6 +176,13 @@ module Google
|
|
157
176
|
|
158
177
|
self
|
159
178
|
end
|
179
|
+
|
180
|
+
# Returns the client ID as the principal for user refresh credentials
|
181
|
+
# @private
|
182
|
+
# @return [String, Symbol] the client ID or :user_refresh if not available
|
183
|
+
def principal
|
184
|
+
@client_id || :user_refresh
|
185
|
+
end
|
160
186
|
end
|
161
187
|
end
|
162
188
|
end
|