googleauth 1.14.0 → 1.17.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 +49 -0
- data/Credentials.md +110 -0
- data/Errors.md +152 -0
- data/README.md +0 -1
- 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 +15 -7
- data/lib/googleauth/compute_engine.rb +64 -18
- data/lib/googleauth/credentials.rb +66 -35
- data/lib/googleauth/credentials_loader.rb +30 -5
- data/lib/googleauth/default_credentials.rb +69 -34
- data/lib/googleauth/errors.rb +117 -0
- data/lib/googleauth/external_account/aws_credentials.rb +87 -19
- data/lib/googleauth/external_account/base_credentials.rb +34 -4
- data/lib/googleauth/external_account/external_account_utils.rb +17 -5
- data/lib/googleauth/external_account/identity_pool_credentials.rb +42 -16
- data/lib/googleauth/external_account/pluggable_credentials.rb +37 -21
- data/lib/googleauth/external_account.rb +53 -7
- data/lib/googleauth/iam.rb +20 -4
- 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 +119 -19
- data/lib/googleauth/json_key_reader.rb +17 -3
- data/lib/googleauth/oauth2/sts_client.rb +12 -6
- data/lib/googleauth/scope_util.rb +2 -2
- data/lib/googleauth/service_account.rb +55 -21
- data/lib/googleauth/service_account_jwt_header.rb +9 -2
- data/lib/googleauth/signet.rb +30 -10
- data/lib/googleauth/user_authorizer.rb +38 -10
- data/lib/googleauth/user_refresh.rb +54 -19
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +54 -17
- data/lib/googleauth.rb +1 -0
- metadata +22 -19
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
require "jwt"
|
|
16
|
-
require "
|
|
16
|
+
require "json"
|
|
17
17
|
require "stringio"
|
|
18
18
|
|
|
19
19
|
require "google/logging/message"
|
|
@@ -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,29 @@ 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
|
|
56
|
-
|
|
60
|
+
# @raise [ArgumentError] If both scope and target_audience are specified
|
|
61
|
+
def self.make_creds options = {} # rubocop:disable Metrics/MethodLength
|
|
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
|
-
|
|
70
|
-
|
|
67
|
+
private_key, client_email, project_id, quota_project_id, universe_domain =
|
|
68
|
+
if json_key_io
|
|
69
|
+
json_key = JSON.parse json_key_io.read
|
|
70
|
+
if json_key.key? "type"
|
|
71
|
+
json_key_io.rewind
|
|
72
|
+
else # Defaults to class credential 'type' if missing.
|
|
73
|
+
json_key["type"] = CREDENTIAL_TYPE_NAME
|
|
74
|
+
json_key_io = StringIO.new JSON.generate(json_key)
|
|
75
|
+
end
|
|
76
|
+
CredentialsLoader.load_and_verify_json_key_type json_key_io, CREDENTIAL_TYPE_NAME
|
|
77
|
+
read_json_key json_key_io
|
|
78
|
+
else
|
|
79
|
+
creds_from_env
|
|
80
|
+
end
|
|
71
81
|
project_id ||= CredentialsLoader.load_gcloud_project_id
|
|
72
82
|
|
|
73
83
|
new(token_credential_uri: token_credential_uri || TOKEN_CRED_URI,
|
|
@@ -110,6 +120,9 @@ module Google
|
|
|
110
120
|
# Handles certain escape sequences that sometimes appear in input.
|
|
111
121
|
# Specifically, interprets the "\n" sequence for newline, and removes
|
|
112
122
|
# enclosing quotes.
|
|
123
|
+
#
|
|
124
|
+
# @param str [String] The string to unescape
|
|
125
|
+
# @return [String] The unescaped string
|
|
113
126
|
def self.unescape str
|
|
114
127
|
str = str.gsub '\n', "\n"
|
|
115
128
|
str = str[1..-2] if str.start_with?('"') && str.end_with?('"')
|
|
@@ -164,21 +177,42 @@ module Google
|
|
|
164
177
|
self
|
|
165
178
|
end
|
|
166
179
|
|
|
180
|
+
# Returns the client email as the principal for service account credentials
|
|
181
|
+
# @private
|
|
182
|
+
# @return [String] the email address of the service account
|
|
183
|
+
def principal
|
|
184
|
+
@issuer
|
|
185
|
+
end
|
|
186
|
+
|
|
167
187
|
private
|
|
168
188
|
|
|
169
189
|
def apply_self_signed_jwt! a_hash
|
|
170
190
|
# Use the ServiceAccountJwtHeaderCredentials using the same cred values
|
|
171
|
-
|
|
172
|
-
private_key:
|
|
173
|
-
|
|
174
|
-
project_id:
|
|
175
|
-
quota_project_id: @quota_project_id
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
191
|
+
alt = ServiceAccountJwtHeaderCredentials.new(
|
|
192
|
+
private_key: @signing_key.to_s,
|
|
193
|
+
issuer: @issuer,
|
|
194
|
+
project_id: @project_id,
|
|
195
|
+
quota_project_id: @quota_project_id,
|
|
196
|
+
universe_domain: universe_domain,
|
|
197
|
+
scope: scope,
|
|
198
|
+
logger: logger
|
|
199
|
+
)
|
|
180
200
|
alt.apply! a_hash
|
|
181
201
|
end
|
|
202
|
+
|
|
203
|
+
# @private
|
|
204
|
+
# Loads service account credential details from environment variables.
|
|
205
|
+
#
|
|
206
|
+
# @return [Array<String, String, String, nil, nil>] An array containing private_key,
|
|
207
|
+
# client_email, project_id, quota_project_id, and universe_domain.
|
|
208
|
+
def self.creds_from_env
|
|
209
|
+
private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
|
210
|
+
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
|
211
|
+
project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
|
212
|
+
[private_key, client_email, project_id, nil, nil]
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
private_class_method :creds_from_env
|
|
182
216
|
end
|
|
183
217
|
end
|
|
184
218
|
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
|
|
@@ -192,10 +210,12 @@ module Signet
|
|
|
192
210
|
digest = Digest::SHA256.hexdigest response_hash["id_token"]
|
|
193
211
|
response_hash["id_token"] = "(sha256:#{digest})"
|
|
194
212
|
end
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
213
|
+
logger&.debug do
|
|
214
|
+
Google::Logging::Message.from(
|
|
215
|
+
message: "Received auth token response: #{response_hash}",
|
|
216
|
+
"credentialsId" => object_id
|
|
217
|
+
)
|
|
218
|
+
end
|
|
199
219
|
end
|
|
200
220
|
|
|
201
221
|
def log_auth_error err
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
require "uri"
|
|
16
|
-
require "
|
|
16
|
+
require "json"
|
|
17
17
|
require "googleauth/signet"
|
|
18
18
|
require "googleauth/user_refresh"
|
|
19
19
|
require "securerandom"
|
|
@@ -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
|
-
data =
|
|
143
|
+
data = JSON.parse 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,8 +247,10 @@ 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
|
-
json =
|
|
253
|
+
json = JSON.generate(
|
|
245
254
|
client_id: credentials.client_id,
|
|
246
255
|
access_token: credentials.access_token,
|
|
247
256
|
refresh_token: credentials.refresh_token,
|
|
@@ -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,10 +12,11 @@
|
|
|
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 "
|
|
18
|
+
require "googleauth/signet"
|
|
19
|
+
require "json"
|
|
19
20
|
|
|
20
21
|
module Google
|
|
21
22
|
# Module Auth provides classes that provide Google-specific authorization
|
|
@@ -38,21 +39,36 @@ 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
|
-
def self.make_creds options = {}
|
|
50
|
+
def self.make_creds options = {} # rubocop:disable Metrics/MethodLength
|
|
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
|
+
json_key = JSON.parse json_key_io.read
|
|
54
|
+
if json_key.key? "type"
|
|
55
|
+
json_key_io.rewind
|
|
56
|
+
else # Defaults to class credential 'type' if missing.
|
|
57
|
+
json_key["type"] = CREDENTIAL_TYPE_NAME
|
|
58
|
+
json_key_io = StringIO.new JSON.generate(json_key)
|
|
59
|
+
end
|
|
60
|
+
CredentialsLoader.load_and_verify_json_key_type json_key_io, CREDENTIAL_TYPE_NAME
|
|
61
|
+
read_json_key json_key_io
|
|
62
|
+
else
|
|
63
|
+
{
|
|
64
|
+
"client_id" => ENV[CredentialsLoader::CLIENT_ID_VAR],
|
|
65
|
+
"client_secret" => ENV[CredentialsLoader::CLIENT_SECRET_VAR],
|
|
66
|
+
"refresh_token" => ENV[CredentialsLoader::REFRESH_TOKEN_VAR],
|
|
67
|
+
"project_id" => ENV[CredentialsLoader::PROJECT_ID_VAR],
|
|
68
|
+
"quota_project_id" => nil,
|
|
69
|
+
"universe_domain" => nil
|
|
70
|
+
}
|
|
71
|
+
end
|
|
56
72
|
new(token_credential_uri: TOKEN_CRED_URI,
|
|
57
73
|
client_id: user_creds["client_id"],
|
|
58
74
|
client_secret: user_creds["client_secret"],
|
|
@@ -64,13 +80,16 @@ module Google
|
|
|
64
80
|
.configure_connection(options)
|
|
65
81
|
end
|
|
66
82
|
|
|
67
|
-
# Reads
|
|
68
|
-
#
|
|
83
|
+
# Reads a JSON key from an IO object and extracts required fields.
|
|
84
|
+
#
|
|
85
|
+
# @param [IO] json_key_io An IO object containing the JSON key
|
|
86
|
+
# @return [Hash] The parsed JSON key
|
|
87
|
+
# @raise [Google::Auth::InitializationError] If the JSON is missing required fields
|
|
69
88
|
def self.read_json_key json_key_io
|
|
70
|
-
json_key =
|
|
89
|
+
json_key = JSON.parse json_key_io.read
|
|
71
90
|
wanted = ["client_id", "client_secret", "refresh_token"]
|
|
72
91
|
wanted.each do |key|
|
|
73
|
-
raise "the json is missing the #{key} field" unless json_key.key? key
|
|
92
|
+
raise InitializationError, "the json is missing the #{key} field" unless json_key.key? key
|
|
74
93
|
end
|
|
75
94
|
json_key
|
|
76
95
|
end
|
|
@@ -106,6 +125,10 @@ module Google
|
|
|
106
125
|
end
|
|
107
126
|
|
|
108
127
|
# Revokes the credential
|
|
128
|
+
#
|
|
129
|
+
# @param [Hash] options Options for revoking the credential
|
|
130
|
+
# @option options [Faraday::Connection] :connection The connection to use
|
|
131
|
+
# @raise [Google::Auth::AuthorizationError] If the revocation request fails
|
|
109
132
|
def revoke! options = {}
|
|
110
133
|
c = options[:connection] || Faraday.default_connection
|
|
111
134
|
|
|
@@ -117,9 +140,14 @@ module Google
|
|
|
117
140
|
self.refresh_token = nil
|
|
118
141
|
self.expires_at = 0
|
|
119
142
|
else
|
|
120
|
-
raise(
|
|
121
|
-
|
|
143
|
+
raise AuthorizationError.with_details(
|
|
144
|
+
"Unexpected error code #{resp.status}",
|
|
145
|
+
credential_type_name: self.class.name,
|
|
146
|
+
principal: principal
|
|
147
|
+
)
|
|
122
148
|
end
|
|
149
|
+
|
|
150
|
+
resp.body
|
|
123
151
|
end
|
|
124
152
|
end
|
|
125
153
|
|
|
@@ -157,6 +185,13 @@ module Google
|
|
|
157
185
|
|
|
158
186
|
self
|
|
159
187
|
end
|
|
188
|
+
|
|
189
|
+
# Returns the client ID as the principal for user refresh credentials
|
|
190
|
+
# @private
|
|
191
|
+
# @return [String, Symbol] the client ID or :user_refresh if not available
|
|
192
|
+
def principal
|
|
193
|
+
@client_id || :user_refresh
|
|
194
|
+
end
|
|
160
195
|
end
|
|
161
196
|
end
|
|
162
197
|
end
|
data/lib/googleauth/version.rb
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
require "
|
|
15
|
+
require "json"
|
|
16
|
+
require "googleauth/errors"
|
|
16
17
|
require "googleauth/signet"
|
|
17
18
|
require "googleauth/user_authorizer"
|
|
18
19
|
require "googleauth/user_refresh"
|
|
@@ -79,9 +80,11 @@ module Google
|
|
|
79
80
|
#
|
|
80
81
|
# @param [Rack::Request] request
|
|
81
82
|
# Current request
|
|
83
|
+
# @return [String, nil]
|
|
84
|
+
# Redirect URI if successfully extracted, nil otherwise
|
|
82
85
|
def self.handle_auth_callback_deferred request
|
|
83
86
|
callback_state, redirect_uri = extract_callback_state request
|
|
84
|
-
request.session[CALLBACK_STATE_KEY] =
|
|
87
|
+
request.session[CALLBACK_STATE_KEY] = JSON.generate callback_state
|
|
85
88
|
redirect_uri
|
|
86
89
|
end
|
|
87
90
|
|
|
@@ -151,20 +154,22 @@ module Google
|
|
|
151
154
|
# Optional key-values to be returned to the oauth callback.
|
|
152
155
|
# @return [String]
|
|
153
156
|
# Authorization url
|
|
157
|
+
# @raise [Google::Auth::InitializationError]
|
|
158
|
+
# If request is nil or request.session is nil
|
|
154
159
|
def get_authorization_url options = {}
|
|
155
160
|
options = options.dup
|
|
156
161
|
request = options[:request]
|
|
157
|
-
raise NIL_REQUEST_ERROR if request.nil?
|
|
158
|
-
raise NIL_SESSION_ERROR if request.session.nil?
|
|
162
|
+
raise InitializationError, NIL_REQUEST_ERROR if request.nil?
|
|
163
|
+
raise InitializationError, NIL_SESSION_ERROR if request.session.nil?
|
|
159
164
|
|
|
160
165
|
state = options[:state] || {}
|
|
161
166
|
|
|
162
167
|
redirect_to = options[:redirect_to] || request.url
|
|
163
168
|
request.session[XSRF_KEY] = SecureRandom.base64
|
|
164
|
-
options[:state] =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
169
|
+
options[:state] = JSON.generate(state.merge(
|
|
170
|
+
SESSION_ID_KEY => request.session[XSRF_KEY],
|
|
171
|
+
CURRENT_URI_KEY => redirect_to
|
|
172
|
+
))
|
|
168
173
|
options[:base_url] = request.url
|
|
169
174
|
super options
|
|
170
175
|
end
|
|
@@ -181,15 +186,15 @@ module Google
|
|
|
181
186
|
# requested scopes
|
|
182
187
|
# @return [Google::Auth::UserRefreshCredentials]
|
|
183
188
|
# Stored credentials, nil if none present
|
|
184
|
-
# @raise [
|
|
185
|
-
#
|
|
186
|
-
#
|
|
189
|
+
# @raise [Google::Auth::AuthorizationError]
|
|
190
|
+
# If the authorization code is missing, there's an error in the request,
|
|
191
|
+
# or the state token doesn't match
|
|
187
192
|
def get_credentials user_id, request = nil, scope = nil
|
|
188
193
|
if request&.session&.key? CALLBACK_STATE_KEY
|
|
189
194
|
# Note - in theory, no need to check required scope as this is
|
|
190
195
|
# expected to be called immediately after a return from authorization
|
|
191
196
|
state_json = request.session.delete CALLBACK_STATE_KEY
|
|
192
|
-
callback_state =
|
|
197
|
+
callback_state = JSON.parse state_json
|
|
193
198
|
WebUserAuthorizer.validate_callback_state callback_state, request
|
|
194
199
|
get_and_store_credentials_from_code(
|
|
195
200
|
user_id: user_id,
|
|
@@ -202,8 +207,14 @@ module Google
|
|
|
202
207
|
end
|
|
203
208
|
end
|
|
204
209
|
|
|
210
|
+
# Extract the callback state from the request
|
|
211
|
+
#
|
|
212
|
+
# @param [Rack::Request] request
|
|
213
|
+
# Current request
|
|
214
|
+
# @return [Array<Hash, String>]
|
|
215
|
+
# Callback state and redirect URI
|
|
205
216
|
def self.extract_callback_state request
|
|
206
|
-
state =
|
|
217
|
+
state = JSON.parse(request.params[STATE_PARAM] || "{}")
|
|
207
218
|
redirect_uri = state[CURRENT_URI_KEY]
|
|
208
219
|
callback_state = {
|
|
209
220
|
AUTH_CODE_KEY => request.params[AUTH_CODE_KEY],
|
|
@@ -214,6 +225,15 @@ module Google
|
|
|
214
225
|
[callback_state, redirect_uri]
|
|
215
226
|
end
|
|
216
227
|
|
|
228
|
+
# Returns the principal identifier for this web authorizer
|
|
229
|
+
# This is a class method that returns a symbol since
|
|
230
|
+
# we might not have a client_id in the static callback context
|
|
231
|
+
#
|
|
232
|
+
# @return [Symbol] The symbol for web user authorization
|
|
233
|
+
def self.principal
|
|
234
|
+
:web_user_authorization
|
|
235
|
+
end
|
|
236
|
+
|
|
217
237
|
# Verifies the results of an authorization callback
|
|
218
238
|
#
|
|
219
239
|
# @param [Hash] state
|
|
@@ -224,13 +244,30 @@ module Google
|
|
|
224
244
|
# Error message if failed
|
|
225
245
|
# @param [Rack::Request] request
|
|
226
246
|
# Current request
|
|
247
|
+
# @raise [Google::Auth::AuthorizationError]
|
|
248
|
+
# If the authorization code is missing, there's an error in the callback state,
|
|
249
|
+
# or the state token doesn't match
|
|
227
250
|
def self.validate_callback_state state, request
|
|
228
|
-
|
|
251
|
+
if state[AUTH_CODE_KEY].nil?
|
|
252
|
+
raise AuthorizationError.with_details(
|
|
253
|
+
MISSING_AUTH_CODE_ERROR,
|
|
254
|
+
credential_type_name: name,
|
|
255
|
+
principal: principal
|
|
256
|
+
)
|
|
257
|
+
end
|
|
258
|
+
|
|
229
259
|
if state[ERROR_CODE_KEY]
|
|
230
|
-
raise
|
|
231
|
-
|
|
260
|
+
raise AuthorizationError.with_details(
|
|
261
|
+
format(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY]),
|
|
262
|
+
credential_type_name: name,
|
|
263
|
+
principal: principal
|
|
264
|
+
)
|
|
232
265
|
elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY]
|
|
233
|
-
raise
|
|
266
|
+
raise AuthorizationError.with_details(
|
|
267
|
+
INVALID_STATE_TOKEN_ERROR,
|
|
268
|
+
credential_type_name: name,
|
|
269
|
+
principal: principal
|
|
270
|
+
)
|
|
234
271
|
end
|
|
235
272
|
end
|
|
236
273
|
|
data/lib/googleauth.rb
CHANGED
|
@@ -18,6 +18,7 @@ require "googleauth/bearer_token"
|
|
|
18
18
|
require "googleauth/client_id"
|
|
19
19
|
require "googleauth/credentials"
|
|
20
20
|
require "googleauth/default_credentials"
|
|
21
|
+
require "googleauth/errors"
|
|
21
22
|
require "googleauth/external_account"
|
|
22
23
|
require "googleauth/id_tokens"
|
|
23
24
|
require "googleauth/impersonated_service_account"
|