googleauth 0.8.1 → 0.16.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/.github/CODEOWNERS +7 -0
- data/.github/workflows/release.yml +39 -0
- data/.kokoro/build.bat +9 -1
- data/.kokoro/continuous/linux.cfg +12 -2
- data/.kokoro/continuous/osx.cfg +5 -0
- data/.kokoro/continuous/post.cfg +30 -0
- data/.kokoro/continuous/windows.cfg +27 -1
- data/.kokoro/presubmit/linux.cfg +11 -1
- data/.kokoro/presubmit/osx.cfg +5 -0
- data/.kokoro/presubmit/windows.cfg +27 -1
- data/.kokoro/release.cfg +42 -1
- data/.kokoro/trampoline.bat +10 -0
- data/.repo-metadata.json +5 -0
- data/.rubocop.yml +8 -2
- data/CHANGELOG.md +94 -20
- data/Gemfile +7 -7
- data/{COPYING → LICENSE} +0 -0
- data/README.md +12 -15
- data/Rakefile +48 -5
- data/googleauth.gemspec +7 -3
- data/integration/helper.rb +31 -0
- data/integration/id_tokens/key_source_test.rb +74 -0
- data/lib/googleauth.rb +1 -0
- data/lib/googleauth/application_default.rb +2 -2
- data/lib/googleauth/compute_engine.rb +45 -20
- data/lib/googleauth/credentials.rb +445 -71
- data/lib/googleauth/credentials_loader.rb +11 -9
- data/lib/googleauth/iam.rb +1 -1
- data/lib/googleauth/id_tokens.rb +233 -0
- data/lib/googleauth/id_tokens/errors.rb +71 -0
- data/lib/googleauth/id_tokens/key_sources.rb +396 -0
- data/lib/googleauth/id_tokens/verifier.rb +142 -0
- data/lib/googleauth/json_key_reader.rb +6 -2
- data/lib/googleauth/scope_util.rb +1 -1
- data/lib/googleauth/service_account.rb +42 -23
- data/lib/googleauth/signet.rb +9 -6
- data/lib/googleauth/stores/file_token_store.rb +1 -0
- data/lib/googleauth/stores/redis_token_store.rb +1 -0
- data/lib/googleauth/user_authorizer.rb +6 -1
- data/lib/googleauth/user_refresh.rb +2 -2
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +16 -14
- data/rakelib/devsite_builder.rb +45 -0
- data/rakelib/link_checker.rb +64 -0
- data/rakelib/repo_metadata.rb +59 -0
- data/spec/googleauth/apply_auth_examples.rb +28 -5
- data/spec/googleauth/compute_engine_spec.rb +69 -13
- data/spec/googleauth/credentials_spec.rb +492 -165
- data/spec/googleauth/service_account_spec.rb +31 -16
- data/spec/googleauth/signet_spec.rb +46 -7
- data/spec/googleauth/user_authorizer_spec.rb +21 -1
- data/spec/googleauth/user_refresh_spec.rb +1 -1
- data/spec/googleauth/web_user_authorizer_spec.rb +6 -0
- data/test/helper.rb +33 -0
- data/test/id_tokens/key_sources_test.rb +240 -0
- data/test/id_tokens/verifier_test.rb +269 -0
- metadata +49 -13
- data/.kokoro/windows.sh +0 -4
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2020 Google LLC
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are
|
7
|
+
# met:
|
8
|
+
#
|
9
|
+
# * Redistributions of source code must retain the above copyright
|
10
|
+
# notice, this list of conditions and the following disclaimer.
|
11
|
+
# * Redistributions in binary form must reproduce the above
|
12
|
+
# copyright notice, this list of conditions and the following disclaimer
|
13
|
+
# in the documentation and/or other materials provided with the
|
14
|
+
# distribution.
|
15
|
+
# * Neither the name of Google Inc. nor the names of its
|
16
|
+
# contributors may be used to endorse or promote products derived from
|
17
|
+
# this software without specific prior written permission.
|
18
|
+
#
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
20
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
21
|
+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
22
|
+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
23
|
+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
24
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
25
|
+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
26
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
27
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
28
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
29
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
|
31
|
+
require "jwt"
|
32
|
+
|
33
|
+
module Google
|
34
|
+
module Auth
|
35
|
+
module IDTokens
|
36
|
+
##
|
37
|
+
# An object that can verify ID tokens.
|
38
|
+
#
|
39
|
+
# A verifier maintains a set of default settings, including the key
|
40
|
+
# source and fields to verify. However, individual verification calls can
|
41
|
+
# override any of these settings.
|
42
|
+
#
|
43
|
+
class Verifier
|
44
|
+
##
|
45
|
+
# Create a verifier.
|
46
|
+
#
|
47
|
+
# @param key_source [key source] The default key source to use. All
|
48
|
+
# verification calls must have a key source, so if no default key
|
49
|
+
# source is provided here, then calls to {#verify} _must_ provide
|
50
|
+
# a key source.
|
51
|
+
# @param aud [String,nil] The default audience (`aud`) check, or `nil`
|
52
|
+
# for no check.
|
53
|
+
# @param azp [String,nil] The default authorized party (`azp`) check,
|
54
|
+
# or `nil` for no check.
|
55
|
+
# @param iss [String,nil] The default issuer (`iss`) check, or `nil`
|
56
|
+
# for no check.
|
57
|
+
#
|
58
|
+
def initialize key_source: nil,
|
59
|
+
aud: nil,
|
60
|
+
azp: nil,
|
61
|
+
iss: nil
|
62
|
+
@key_source = key_source
|
63
|
+
@aud = aud
|
64
|
+
@azp = azp
|
65
|
+
@iss = iss
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Verify the given token.
|
70
|
+
#
|
71
|
+
# @param token [String] the ID token to verify.
|
72
|
+
# @param key_source [key source] If given, override the key source.
|
73
|
+
# @param aud [String,nil] If given, override the `aud` check.
|
74
|
+
# @param azp [String,nil] If given, override the `azp` check.
|
75
|
+
# @param iss [String,nil] If given, override the `iss` check.
|
76
|
+
#
|
77
|
+
# @return [Hash] the decoded payload, if verification succeeded.
|
78
|
+
# @raise [KeySourceError] if the key source failed to obtain public keys
|
79
|
+
# @raise [VerificationError] if the token verification failed.
|
80
|
+
# Additional data may be available in the error subclass and message.
|
81
|
+
#
|
82
|
+
def verify token,
|
83
|
+
key_source: :default,
|
84
|
+
aud: :default,
|
85
|
+
azp: :default,
|
86
|
+
iss: :default
|
87
|
+
key_source = @key_source if key_source == :default
|
88
|
+
aud = @aud if aud == :default
|
89
|
+
azp = @azp if azp == :default
|
90
|
+
iss = @iss if iss == :default
|
91
|
+
|
92
|
+
raise KeySourceError, "No key sources" unless key_source
|
93
|
+
keys = key_source.current_keys
|
94
|
+
payload = decode_token token, keys, aud, azp, iss
|
95
|
+
unless payload
|
96
|
+
keys = key_source.refresh_keys
|
97
|
+
payload = decode_token token, keys, aud, azp, iss
|
98
|
+
end
|
99
|
+
raise SignatureError, "Token not verified as issued by Google" unless payload
|
100
|
+
payload
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def decode_token token, keys, aud, azp, iss
|
106
|
+
payload = nil
|
107
|
+
keys.find do |key|
|
108
|
+
options = { algorithms: key.algorithm }
|
109
|
+
decoded_token = JWT.decode token, key.key, true, options
|
110
|
+
payload = decoded_token.first
|
111
|
+
rescue JWT::ExpiredSignature
|
112
|
+
raise ExpiredTokenError, "Token signature is expired"
|
113
|
+
rescue JWT::DecodeError
|
114
|
+
nil # Try the next key
|
115
|
+
end
|
116
|
+
|
117
|
+
normalize_and_verify_payload payload, aud, azp, iss
|
118
|
+
end
|
119
|
+
|
120
|
+
def normalize_and_verify_payload payload, aud, azp, iss
|
121
|
+
return nil unless payload
|
122
|
+
|
123
|
+
# Map the legacy "cid" claim to the canonical "azp"
|
124
|
+
payload["azp"] ||= payload["cid"] if payload.key? "cid"
|
125
|
+
|
126
|
+
# Payload content validation
|
127
|
+
if aud && (Array(aud) & Array(payload["aud"])).empty?
|
128
|
+
raise AudienceMismatchError, "Token aud mismatch: #{payload['aud']}"
|
129
|
+
end
|
130
|
+
if azp && (Array(azp) & Array(payload["azp"])).empty?
|
131
|
+
raise AuthorizedPartyMismatchError, "Token azp mismatch: #{payload['azp']}"
|
132
|
+
end
|
133
|
+
if iss && (Array(iss) & Array(payload["iss"])).empty?
|
134
|
+
raise IssuerMismatchError, "Token iss mismatch: #{payload['iss']}"
|
135
|
+
end
|
136
|
+
|
137
|
+
payload
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -38,8 +38,12 @@ module Google
|
|
38
38
|
json_key = MultiJson.load json_key_io.read
|
39
39
|
raise "missing client_email" unless json_key.key? "client_email"
|
40
40
|
raise "missing private_key" unless json_key.key? "private_key"
|
41
|
-
|
42
|
-
|
41
|
+
[
|
42
|
+
json_key["private_key"],
|
43
|
+
json_key["client_email"],
|
44
|
+
json_key["project_id"],
|
45
|
+
json_key["quota_project_id"]
|
46
|
+
]
|
43
47
|
end
|
44
48
|
end
|
45
49
|
end
|
@@ -45,34 +45,47 @@ module Google
|
|
45
45
|
# from credentials from a json key file downloaded from the developer
|
46
46
|
# console (via 'Generate new Json Key').
|
47
47
|
#
|
48
|
-
# cf [Application Default Credentials](
|
48
|
+
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
|
49
49
|
class ServiceAccountCredentials < Signet::OAuth2::Client
|
50
50
|
TOKEN_CRED_URI = "https://www.googleapis.com/oauth2/v4/token".freeze
|
51
51
|
extend CredentialsLoader
|
52
52
|
extend JsonKeyReader
|
53
53
|
attr_reader :project_id
|
54
|
+
attr_reader :quota_project_id
|
55
|
+
|
56
|
+
def enable_self_signed_jwt?
|
57
|
+
@enable_self_signed_jwt
|
58
|
+
end
|
54
59
|
|
55
60
|
# Creates a ServiceAccountCredentials.
|
56
61
|
#
|
57
62
|
# @param json_key_io [IO] an IO from which the JSON key can be read
|
58
63
|
# @param scope [string|array|nil] the scope(s) to access
|
59
64
|
def self.make_creds options = {}
|
60
|
-
json_key_io, scope
|
65
|
+
json_key_io, scope, enable_self_signed_jwt, target_audience, audience, token_credential_uri =
|
66
|
+
options.values_at :json_key_io, :scope, :enable_self_signed_jwt, :target_audience,
|
67
|
+
:audience, :token_credential_uri
|
68
|
+
raise ArgumentError, "Cannot specify both scope and target_audience" if scope && target_audience
|
69
|
+
|
61
70
|
if json_key_io
|
62
|
-
private_key, client_email, project_id = read_json_key json_key_io
|
71
|
+
private_key, client_email, project_id, quota_project_id = read_json_key json_key_io
|
63
72
|
else
|
64
73
|
private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
65
74
|
client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
66
75
|
project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
76
|
+
quota_project_id = nil
|
67
77
|
end
|
68
78
|
project_id ||= CredentialsLoader.load_gcloud_project_id
|
69
79
|
|
70
|
-
new(token_credential_uri: TOKEN_CRED_URI,
|
71
|
-
audience:
|
72
|
-
scope:
|
73
|
-
|
74
|
-
|
75
|
-
|
80
|
+
new(token_credential_uri: token_credential_uri || TOKEN_CRED_URI,
|
81
|
+
audience: audience || TOKEN_CRED_URI,
|
82
|
+
scope: scope,
|
83
|
+
enable_self_signed_jwt: enable_self_signed_jwt,
|
84
|
+
target_audience: target_audience,
|
85
|
+
issuer: client_email,
|
86
|
+
signing_key: OpenSSL::PKey::RSA.new(private_key),
|
87
|
+
project_id: project_id,
|
88
|
+
quota_project_id: quota_project_id)
|
76
89
|
.configure_connection(options)
|
77
90
|
end
|
78
91
|
|
@@ -87,30 +100,34 @@ module Google
|
|
87
100
|
|
88
101
|
def initialize options = {}
|
89
102
|
@project_id = options[:project_id]
|
103
|
+
@quota_project_id = options[:quota_project_id]
|
104
|
+
@enable_self_signed_jwt = options[:enable_self_signed_jwt] ? true : false
|
90
105
|
super options
|
91
106
|
end
|
92
107
|
|
93
|
-
# Extends the base class
|
94
|
-
#
|
95
|
-
# If scope(s) is not set, it creates a transient
|
96
|
-
# ServiceAccountJwtHeaderCredentials instance and uses that to
|
97
|
-
# authenticate instead.
|
108
|
+
# Extends the base class to use a transient
|
109
|
+
# ServiceAccountJwtHeaderCredentials for certain cases.
|
98
110
|
def apply! a_hash, opts = {}
|
99
|
-
# Use
|
100
|
-
|
111
|
+
# Use a self-singed JWT if there's no information that can be used to
|
112
|
+
# obtain an OAuth token, OR if there are scopes but also an assertion
|
113
|
+
# that they are default scopes that shouldn't be used to fetch a token.
|
114
|
+
if target_audience.nil? && (scope.nil? || enable_self_signed_jwt?)
|
115
|
+
apply_self_signed_jwt! a_hash
|
116
|
+
else
|
101
117
|
super
|
102
|
-
return
|
103
118
|
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
104
122
|
|
123
|
+
def apply_self_signed_jwt! a_hash
|
105
124
|
# Use the ServiceAccountJwtHeaderCredentials using the same cred values
|
106
|
-
# if no scopes are set.
|
107
125
|
cred_json = {
|
108
126
|
private_key: @signing_key.to_s,
|
109
127
|
client_email: @issuer
|
110
128
|
}
|
111
|
-
alt_clz = ServiceAccountJwtHeaderCredentials
|
112
129
|
key_io = StringIO.new MultiJson.dump(cred_json)
|
113
|
-
alt =
|
130
|
+
alt = ServiceAccountJwtHeaderCredentials.make_creds json_key_io: key_io
|
114
131
|
alt.apply! a_hash
|
115
132
|
end
|
116
133
|
end
|
@@ -123,7 +140,7 @@ module Google
|
|
123
140
|
# console (via 'Generate new Json Key'). It is not part of any OAuth2
|
124
141
|
# flow, rather it creates a JWT and sends that as a credential.
|
125
142
|
#
|
126
|
-
# cf [Application Default Credentials](
|
143
|
+
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
|
127
144
|
class ServiceAccountJwtHeaderCredentials
|
128
145
|
JWT_AUD_URI_KEY = :jwt_aud_uri
|
129
146
|
AUTH_METADATA_KEY = Signet::OAuth2::AUTH_METADATA_KEY
|
@@ -133,6 +150,7 @@ module Google
|
|
133
150
|
extend CredentialsLoader
|
134
151
|
extend JsonKeyReader
|
135
152
|
attr_reader :project_id
|
153
|
+
attr_reader :quota_project_id
|
136
154
|
|
137
155
|
# make_creds proxies the construction of a credentials instance
|
138
156
|
#
|
@@ -151,12 +169,13 @@ module Google
|
|
151
169
|
def initialize options = {}
|
152
170
|
json_key_io = options[:json_key_io]
|
153
171
|
if json_key_io
|
154
|
-
@private_key, @issuer, @project_id =
|
172
|
+
@private_key, @issuer, @project_id, @quota_project_id =
|
155
173
|
self.class.read_json_key json_key_io
|
156
174
|
else
|
157
175
|
@private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR]
|
158
176
|
@issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR]
|
159
177
|
@project_id = ENV[CredentialsLoader::PROJECT_ID_VAR]
|
178
|
+
@quota_project_id = nil
|
160
179
|
end
|
161
180
|
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
162
181
|
@signing_key = OpenSSL::PKey::RSA.new @private_key
|
@@ -184,7 +203,7 @@ module Google
|
|
184
203
|
# Returns a reference to the #apply method, suitable for passing as
|
185
204
|
# a closure
|
186
205
|
def updater_proc
|
187
|
-
|
206
|
+
proc { |a_hash, opts = {}| apply a_hash, opts }
|
188
207
|
end
|
189
208
|
|
190
209
|
protected
|
data/lib/googleauth/signet.rb
CHANGED
@@ -48,8 +48,9 @@ module Signet
|
|
48
48
|
def apply! a_hash, opts = {}
|
49
49
|
# fetch the access token there is currently not one, or if the client
|
50
50
|
# has expired
|
51
|
-
|
52
|
-
|
51
|
+
token_type = target_audience ? :id_token : :access_token
|
52
|
+
fetch_access_token! opts if send(token_type).nil? || expires_within?(60)
|
53
|
+
a_hash[AUTH_METADATA_KEY] = "Bearer #{send token_type}"
|
53
54
|
end
|
54
55
|
|
55
56
|
# Returns a clone of a_hash updated with the authentication token
|
@@ -62,11 +63,11 @@ module Signet
|
|
62
63
|
# Returns a reference to the #apply method, suitable for passing as
|
63
64
|
# a closure
|
64
65
|
def updater_proc
|
65
|
-
|
66
|
+
proc { |a_hash, opts = {}| apply a_hash, opts }
|
66
67
|
end
|
67
68
|
|
68
69
|
def on_refresh &block
|
69
|
-
@refresh_listeners
|
70
|
+
@refresh_listeners = [] unless defined? @refresh_listeners
|
70
71
|
@refresh_listeners << block
|
71
72
|
end
|
72
73
|
|
@@ -76,13 +77,15 @@ module Signet
|
|
76
77
|
connection = build_default_connection
|
77
78
|
options = options.merge connection: connection if connection
|
78
79
|
end
|
79
|
-
info =
|
80
|
+
info = retry_with_error do
|
81
|
+
orig_fetch_access_token! options
|
82
|
+
end
|
80
83
|
notify_refresh_listeners
|
81
84
|
info
|
82
85
|
end
|
83
86
|
|
84
87
|
def notify_refresh_listeners
|
85
|
-
listeners = @refresh_listeners
|
88
|
+
listeners = defined?(@refresh_listeners) ? @refresh_listeners : []
|
86
89
|
listeners.each do |block|
|
87
90
|
block.call self
|
88
91
|
end
|
@@ -271,10 +271,15 @@ module Google
|
|
271
271
|
# @return [String]
|
272
272
|
# Redirect URI
|
273
273
|
def redirect_uri_for base_url
|
274
|
-
return @callback_uri
|
274
|
+
return @callback_uri if uri_is_postmessage?(@callback_uri) || !URI(@callback_uri).scheme.nil?
|
275
275
|
raise format(MISSING_ABSOLUTE_URL_ERROR, @callback_uri) if base_url.nil? || URI(base_url).scheme.nil?
|
276
276
|
URI.join(base_url, @callback_uri).to_s
|
277
277
|
end
|
278
|
+
|
279
|
+
# Check if URI is Google's postmessage flow (not a valid redirect_uri by spec, but allowed)
|
280
|
+
def uri_is_postmessage? uri
|
281
|
+
uri.to_s.casecmp("postmessage").zero?
|
282
|
+
end
|
278
283
|
end
|
279
284
|
end
|
280
285
|
end
|
@@ -44,7 +44,7 @@ module Google
|
|
44
44
|
# 'gcloud auth login' saves a file with these contents in well known
|
45
45
|
# location
|
46
46
|
#
|
47
|
-
# cf [Application Default Credentials](
|
47
|
+
# cf [Application Default Credentials](https://cloud.google.com/docs/authentication/production)
|
48
48
|
class UserRefreshCredentials < Signet::OAuth2::Client
|
49
49
|
TOKEN_CRED_URI = "https://oauth2.googleapis.com/token".freeze
|
50
50
|
AUTHORIZATION_URI = "https://accounts.google.com/o/oauth2/auth".freeze
|
@@ -79,7 +79,7 @@ module Google
|
|
79
79
|
# JSON key.
|
80
80
|
def self.read_json_key json_key_io
|
81
81
|
json_key = MultiJson.load json_key_io.read
|
82
|
-
wanted =
|
82
|
+
wanted = ["client_id", "client_secret", "refresh_token"]
|
83
83
|
wanted.each do |key|
|
84
84
|
raise "the json is missing the #{key} field" unless json_key.key? key
|
85
85
|
end
|
data/lib/googleauth/version.rb
CHANGED
@@ -58,12 +58,9 @@ module Google
|
|
58
58
|
# end
|
59
59
|
#
|
60
60
|
# Instead of implementing the callback directly, applications are
|
61
|
-
# encouraged to use {Google::Auth::
|
61
|
+
# encouraged to use {Google::Auth::WebUserAuthorizer::CallbackApp} instead.
|
62
62
|
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
# @see {Google::Auth::AuthCallbackApp}
|
66
|
-
# @see {Google::Auth::ControllerHelpers}
|
63
|
+
# @see CallbackApp
|
67
64
|
# @note Requires sessions are enabled
|
68
65
|
class WebUserAuthorizer < Google::Auth::UserAuthorizer
|
69
66
|
STATE_PARAM = "state".freeze
|
@@ -154,6 +151,8 @@ module Google
|
|
154
151
|
# @param [String, Array<String>] scope
|
155
152
|
# Authorization scope to request. Overrides the instance scopes if
|
156
153
|
# not nil.
|
154
|
+
# @param [Hash] state
|
155
|
+
# Optional key-values to be returned to the oauth callback.
|
157
156
|
# @return [String]
|
158
157
|
# Authorization url
|
159
158
|
def get_authorization_url options = {}
|
@@ -162,22 +161,25 @@ module Google
|
|
162
161
|
raise NIL_REQUEST_ERROR if request.nil?
|
163
162
|
raise NIL_SESSION_ERROR if request.session.nil?
|
164
163
|
|
164
|
+
state = options[:state] || {}
|
165
|
+
|
165
166
|
redirect_to = options[:redirect_to] || request.url
|
166
167
|
request.session[XSRF_KEY] = SecureRandom.base64
|
167
|
-
options[:state] = MultiJson.dump(
|
168
|
-
|
169
|
-
|
170
|
-
|
168
|
+
options[:state] = MultiJson.dump(state.merge(
|
169
|
+
SESSION_ID_KEY => request.session[XSRF_KEY],
|
170
|
+
CURRENT_URI_KEY => redirect_to
|
171
|
+
))
|
171
172
|
options[:base_url] = request.url
|
172
173
|
super options
|
173
174
|
end
|
174
175
|
|
175
|
-
# Fetch stored credentials for the user.
|
176
|
+
# Fetch stored credentials for the user from the given request session.
|
176
177
|
#
|
177
178
|
# @param [String] user_id
|
178
179
|
# Unique ID of the user for loading/storing credentials.
|
179
180
|
# @param [Rack::Request] request
|
180
|
-
# Current request
|
181
|
+
# Current request. Optional. If omitted, this will attempt to fall back
|
182
|
+
# on the base class behavior of reading from the token store.
|
181
183
|
# @param [Array<String>, String] scope
|
182
184
|
# If specified, only returns credentials that have all the \
|
183
185
|
# requested scopes
|
@@ -186,8 +188,8 @@ module Google
|
|
186
188
|
# @raise [Signet::AuthorizationError]
|
187
189
|
# May raise an error if an authorization code is present in the session
|
188
190
|
# and exchange of the code fails
|
189
|
-
def get_credentials user_id, request, scope = nil
|
190
|
-
if request
|
191
|
+
def get_credentials user_id, request = nil, scope = nil
|
192
|
+
if request&.session&.key? CALLBACK_STATE_KEY
|
191
193
|
# Note - in theory, no need to check required scope as this is
|
192
194
|
# expected to be called immediately after a return from authorization
|
193
195
|
state_json = request.session.delete CALLBACK_STATE_KEY
|
@@ -256,7 +258,7 @@ module Google
|
|
256
258
|
# Google::Auth::WebUserAuthorizer::CallbackApp.call(env)
|
257
259
|
# end
|
258
260
|
#
|
259
|
-
# @see
|
261
|
+
# @see Google::Auth::WebUserAuthorizer
|
260
262
|
class CallbackApp
|
261
263
|
LOCATION_HEADER = "Location".freeze
|
262
264
|
REDIR_STATUS = 302
|