googleauth 0.5.1 → 0.11.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 +5 -5
- data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +5 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +36 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
- data/.github/ISSUE_TEMPLATE/support_request.md +7 -0
- data/.kokoro/build.bat +16 -0
- data/.kokoro/build.sh +4 -0
- data/.kokoro/continuous/common.cfg +24 -0
- data/.kokoro/continuous/linux.cfg +25 -0
- data/.kokoro/continuous/osx.cfg +8 -0
- data/.kokoro/continuous/post.cfg +30 -0
- data/.kokoro/continuous/windows.cfg +29 -0
- data/.kokoro/osx.sh +4 -0
- data/.kokoro/presubmit/common.cfg +24 -0
- data/.kokoro/presubmit/linux.cfg +24 -0
- data/.kokoro/presubmit/osx.cfg +8 -0
- data/.kokoro/presubmit/windows.cfg +29 -0
- data/.kokoro/release.cfg +94 -0
- data/.kokoro/trampoline.bat +10 -0
- data/.kokoro/trampoline.sh +4 -0
- data/.repo-metadata.json +5 -0
- data/.rubocop.yml +17 -1
- data/CHANGELOG.md +90 -19
- data/CODE_OF_CONDUCT.md +43 -0
- data/Gemfile +16 -13
- data/README.md +58 -18
- data/Rakefile +106 -10
- data/googleauth.gemspec +27 -25
- data/lib/googleauth/application_default.rb +81 -0
- data/lib/googleauth/client_id.rb +21 -19
- data/lib/googleauth/compute_engine.rb +40 -43
- data/lib/googleauth/credentials.rb +375 -0
- data/lib/googleauth/credentials_loader.rb +117 -43
- data/lib/googleauth/default_credentials.rb +93 -0
- data/lib/googleauth/iam.rb +11 -11
- data/lib/googleauth/json_key_reader.rb +46 -0
- data/lib/googleauth/scope_util.rb +12 -12
- data/lib/googleauth/service_account.rb +64 -62
- data/lib/googleauth/signet.rb +53 -12
- data/lib/googleauth/stores/file_token_store.rb +8 -8
- data/lib/googleauth/stores/redis_token_store.rb +22 -22
- data/lib/googleauth/token_store.rb +6 -6
- data/lib/googleauth/user_authorizer.rb +80 -68
- data/lib/googleauth/user_refresh.rb +44 -35
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +77 -68
- data/lib/googleauth.rb +6 -96
- 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 +47 -46
- data/spec/googleauth/client_id_spec.rb +75 -55
- data/spec/googleauth/compute_engine_spec.rb +60 -43
- data/spec/googleauth/credentials_spec.rb +467 -0
- data/spec/googleauth/get_application_default_spec.rb +149 -111
- data/spec/googleauth/iam_spec.rb +25 -25
- data/spec/googleauth/scope_util_spec.rb +26 -24
- data/spec/googleauth/service_account_spec.rb +261 -143
- data/spec/googleauth/signet_spec.rb +93 -30
- data/spec/googleauth/stores/file_token_store_spec.rb +12 -13
- data/spec/googleauth/stores/redis_token_store_spec.rb +11 -11
- data/spec/googleauth/stores/store_examples.rb +16 -16
- data/spec/googleauth/user_authorizer_spec.rb +153 -124
- data/spec/googleauth/user_refresh_spec.rb +186 -121
- data/spec/googleauth/web_user_authorizer_spec.rb +82 -69
- data/spec/spec_helper.rb +21 -19
- metadata +75 -32
- data/.rubocop_todo.yml +0 -32
- data/.travis.yml +0 -37
@@ -27,10 +27,10 @@
|
|
27
27
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
28
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
29
|
|
30
|
-
require
|
31
|
-
require
|
32
|
-
require
|
33
|
-
require
|
30
|
+
require "uri"
|
31
|
+
require "multi_json"
|
32
|
+
require "googleauth/signet"
|
33
|
+
require "googleauth/user_refresh"
|
34
34
|
|
35
35
|
module Google
|
36
36
|
module Auth
|
@@ -53,13 +53,13 @@ module Google
|
|
53
53
|
# ...
|
54
54
|
class UserAuthorizer
|
55
55
|
MISMATCHED_CLIENT_ID_ERROR =
|
56
|
-
|
57
|
-
NIL_CLIENT_ID_ERROR =
|
58
|
-
NIL_SCOPE_ERROR =
|
59
|
-
NIL_USER_ID_ERROR =
|
60
|
-
NIL_TOKEN_STORE_ERROR =
|
56
|
+
"Token client ID of %s does not match configured client id %s".freeze
|
57
|
+
NIL_CLIENT_ID_ERROR = "Client id can not be nil.".freeze
|
58
|
+
NIL_SCOPE_ERROR = "Scope can not be nil.".freeze
|
59
|
+
NIL_USER_ID_ERROR = "User ID can not be nil.".freeze
|
60
|
+
NIL_TOKEN_STORE_ERROR = "Can not call method if token store is nil".freeze
|
61
61
|
MISSING_ABSOLUTE_URL_ERROR =
|
62
|
-
'Absolute base url required for relative callback url "%s"'
|
62
|
+
'Absolute base url required for relative callback url "%s"'.freeze
|
63
63
|
|
64
64
|
# Initialize the authorizer
|
65
65
|
#
|
@@ -72,14 +72,14 @@ module Google
|
|
72
72
|
# @param [String] callback_uri
|
73
73
|
# URL (either absolute or relative) of the auth callback.
|
74
74
|
# Defaults to '/oauth2callback'
|
75
|
-
def initialize
|
76
|
-
|
77
|
-
|
75
|
+
def initialize client_id, scope, token_store, callback_uri = nil
|
76
|
+
raise NIL_CLIENT_ID_ERROR if client_id.nil?
|
77
|
+
raise NIL_SCOPE_ERROR if scope.nil?
|
78
78
|
|
79
79
|
@client_id = client_id
|
80
80
|
@scope = Array(scope)
|
81
81
|
@token_store = token_store
|
82
|
-
@callback_uri = callback_uri ||
|
82
|
+
@callback_uri = callback_uri || "/oauth2callback"
|
83
83
|
end
|
84
84
|
|
85
85
|
# Build the URL for requesting authorization.
|
@@ -97,19 +97,20 @@ module Google
|
|
97
97
|
# nil.
|
98
98
|
# @return [String]
|
99
99
|
# Authorization url
|
100
|
-
def get_authorization_url
|
100
|
+
def get_authorization_url options = {}
|
101
101
|
scope = options[:scope] || @scope
|
102
102
|
credentials = UserRefreshCredentials.new(
|
103
|
-
client_id:
|
103
|
+
client_id: @client_id.id,
|
104
104
|
client_secret: @client_id.secret,
|
105
|
-
scope:
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
105
|
+
scope: scope
|
106
|
+
)
|
107
|
+
redirect_uri = redirect_uri_for options[:base_url]
|
108
|
+
url = credentials.authorization_uri(access_type: "offline",
|
109
|
+
redirect_uri: redirect_uri,
|
110
|
+
approval_prompt: "force",
|
111
|
+
state: options[:state],
|
111
112
|
include_granted_scopes: true,
|
112
|
-
login_hint:
|
113
|
+
login_hint: options[:login_hint])
|
113
114
|
url.to_s
|
114
115
|
end
|
115
116
|
|
@@ -122,31 +123,26 @@ module Google
|
|
122
123
|
# the requested scopes
|
123
124
|
# @return [Google::Auth::UserRefreshCredentials]
|
124
125
|
# Stored credentials, nil if none present
|
125
|
-
def get_credentials
|
126
|
-
|
127
|
-
fail NIL_TOKEN_STORE_ERROR if @token_store.nil?
|
128
|
-
|
129
|
-
scope ||= @scope
|
130
|
-
saved_token = @token_store.load(user_id)
|
126
|
+
def get_credentials user_id, scope = nil
|
127
|
+
saved_token = stored_token user_id
|
131
128
|
return nil if saved_token.nil?
|
132
|
-
data = MultiJson.load
|
129
|
+
data = MultiJson.load saved_token
|
133
130
|
|
134
|
-
if data.fetch(
|
135
|
-
|
136
|
-
data[
|
131
|
+
if data.fetch("client_id", @client_id.id) != @client_id.id
|
132
|
+
raise format(MISMATCHED_CLIENT_ID_ERROR,
|
133
|
+
data["client_id"], @client_id.id)
|
137
134
|
end
|
138
135
|
|
139
136
|
credentials = UserRefreshCredentials.new(
|
140
|
-
client_id:
|
137
|
+
client_id: @client_id.id,
|
141
138
|
client_secret: @client_id.secret,
|
142
|
-
scope:
|
143
|
-
access_token:
|
144
|
-
refresh_token: data[
|
145
|
-
expires_at:
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
end
|
139
|
+
scope: data["scope"] || @scope,
|
140
|
+
access_token: data["access_token"],
|
141
|
+
refresh_token: data["refresh_token"],
|
142
|
+
expires_at: data.fetch("expiration_time_millis", 0) / 1000
|
143
|
+
)
|
144
|
+
scope ||= @scope
|
145
|
+
return monitor_credentials user_id, credentials if credentials.includes_scope? scope
|
150
146
|
nil
|
151
147
|
end
|
152
148
|
|
@@ -165,19 +161,20 @@ module Google
|
|
165
161
|
# callback uri is a relative.
|
166
162
|
# @return [Google::Auth::UserRefreshCredentials]
|
167
163
|
# Credentials if exchange is successful
|
168
|
-
def get_credentials_from_code
|
164
|
+
def get_credentials_from_code options = {}
|
169
165
|
user_id = options[:user_id]
|
170
166
|
code = options[:code]
|
171
167
|
scope = options[:scope] || @scope
|
172
168
|
base_url = options[:base_url]
|
173
169
|
credentials = UserRefreshCredentials.new(
|
174
|
-
client_id:
|
170
|
+
client_id: @client_id.id,
|
175
171
|
client_secret: @client_id.secret,
|
176
|
-
redirect_uri:
|
177
|
-
scope:
|
172
|
+
redirect_uri: redirect_uri_for(base_url),
|
173
|
+
scope: scope
|
174
|
+
)
|
178
175
|
credentials.code = code
|
179
176
|
credentials.fetch_access_token!({})
|
180
|
-
monitor_credentials
|
177
|
+
monitor_credentials user_id, credentials
|
181
178
|
end
|
182
179
|
|
183
180
|
# Exchanges an authorization code returned in the oauth callback.
|
@@ -197,10 +194,9 @@ module Google
|
|
197
194
|
# callback uri is a relative.
|
198
195
|
# @return [Google::Auth::UserRefreshCredentials]
|
199
196
|
# Credentials if exchange is successful
|
200
|
-
def get_and_store_credentials_from_code
|
201
|
-
credentials = get_credentials_from_code
|
202
|
-
|
203
|
-
store_credentials(options[:user_id], credentials)
|
197
|
+
def get_and_store_credentials_from_code options = {}
|
198
|
+
credentials = get_credentials_from_code options
|
199
|
+
store_credentials options[:user_id], credentials
|
204
200
|
end
|
205
201
|
|
206
202
|
# Revokes a user's credentials. This both revokes the actual
|
@@ -208,11 +204,11 @@ module Google
|
|
208
204
|
#
|
209
205
|
# @param [String] user_id
|
210
206
|
# Unique ID of the user for loading/storing credentials.
|
211
|
-
def revoke_authorization
|
212
|
-
credentials = get_credentials
|
207
|
+
def revoke_authorization user_id
|
208
|
+
credentials = get_credentials user_id
|
213
209
|
if credentials
|
214
210
|
begin
|
215
|
-
@token_store.delete
|
211
|
+
@token_store.delete user_id
|
216
212
|
ensure
|
217
213
|
credentials.revoke!
|
218
214
|
end
|
@@ -228,19 +224,32 @@ module Google
|
|
228
224
|
# Unique ID of the user for loading/storing credentials.
|
229
225
|
# @param [Google::Auth::UserRefreshCredentials] credentials
|
230
226
|
# Credentials to store.
|
231
|
-
def store_credentials
|
227
|
+
def store_credentials user_id, credentials
|
232
228
|
json = MultiJson.dump(
|
233
|
-
client_id:
|
234
|
-
access_token:
|
235
|
-
refresh_token:
|
236
|
-
scope:
|
237
|
-
expiration_time_millis:
|
238
|
-
|
229
|
+
client_id: credentials.client_id,
|
230
|
+
access_token: credentials.access_token,
|
231
|
+
refresh_token: credentials.refresh_token,
|
232
|
+
scope: credentials.scope,
|
233
|
+
expiration_time_millis: credentials.expires_at.to_i * 1000
|
234
|
+
)
|
235
|
+
@token_store.store user_id, json
|
239
236
|
credentials
|
240
237
|
end
|
241
238
|
|
242
239
|
private
|
243
240
|
|
241
|
+
# @private Fetch stored token with given user_id
|
242
|
+
#
|
243
|
+
# @param [String] user_id
|
244
|
+
# Unique ID of the user for loading/storing credentials.
|
245
|
+
# @return [String] The saved token from @token_store
|
246
|
+
def stored_token user_id
|
247
|
+
raise NIL_USER_ID_ERROR if user_id.nil?
|
248
|
+
raise NIL_TOKEN_STORE_ERROR if @token_store.nil?
|
249
|
+
|
250
|
+
@token_store.load user_id
|
251
|
+
end
|
252
|
+
|
244
253
|
# Begin watching a credential for refreshes so the access token can be
|
245
254
|
# saved.
|
246
255
|
#
|
@@ -248,9 +257,9 @@ module Google
|
|
248
257
|
# Unique ID of the user for loading/storing credentials.
|
249
258
|
# @param [Google::Auth::UserRefreshCredentials] credentials
|
250
259
|
# Credentials to store.
|
251
|
-
def monitor_credentials
|
260
|
+
def monitor_credentials user_id, credentials
|
252
261
|
credentials.on_refresh do |cred|
|
253
|
-
store_credentials
|
262
|
+
store_credentials user_id, cred
|
254
263
|
end
|
255
264
|
credentials
|
256
265
|
end
|
@@ -261,13 +270,16 @@ module Google
|
|
261
270
|
# Absolute URL to resolve the callback against if necessary.
|
262
271
|
# @return [String]
|
263
272
|
# Redirect URI
|
264
|
-
def redirect_uri_for
|
265
|
-
return @callback_uri
|
266
|
-
|
267
|
-
MISSING_ABSOLUTE_URL_ERROR,
|
268
|
-
@callback_uri) if base_url.nil? || URI(base_url).scheme.nil?
|
273
|
+
def redirect_uri_for base_url
|
274
|
+
return @callback_uri if uri_is_postmessage?(@callback_uri) || !URI(@callback_uri).scheme.nil?
|
275
|
+
raise format(MISSING_ABSOLUTE_URL_ERROR, @callback_uri) if base_url.nil? || URI(base_url).scheme.nil?
|
269
276
|
URI.join(base_url, @callback_uri).to_s
|
270
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
|
271
283
|
end
|
272
284
|
end
|
273
285
|
end
|
@@ -27,10 +27,10 @@
|
|
27
27
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
28
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
29
|
|
30
|
-
require
|
31
|
-
require
|
32
|
-
require
|
33
|
-
require
|
30
|
+
require "googleauth/signet"
|
31
|
+
require "googleauth/credentials_loader"
|
32
|
+
require "googleauth/scope_util"
|
33
|
+
require "multi_json"
|
34
34
|
|
35
35
|
module Google
|
36
36
|
# Module Auth provides classes that provide Google-specific authorization
|
@@ -44,63 +44,72 @@ 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
|
-
TOKEN_CRED_URI =
|
50
|
-
AUTHORIZATION_URI =
|
51
|
-
REVOKE_TOKEN_URI =
|
49
|
+
TOKEN_CRED_URI = "https://oauth2.googleapis.com/token".freeze
|
50
|
+
AUTHORIZATION_URI = "https://accounts.google.com/o/oauth2/auth".freeze
|
51
|
+
REVOKE_TOKEN_URI = "https://oauth2.googleapis.com/revoke".freeze
|
52
52
|
extend CredentialsLoader
|
53
|
+
attr_reader :project_id
|
53
54
|
|
54
55
|
# Create a UserRefreshCredentials.
|
55
56
|
#
|
56
57
|
# @param json_key_io [IO] an IO from which the JSON key can be read
|
57
58
|
# @param scope [string|array|nil] the scope(s) to access
|
58
|
-
def self.make_creds
|
59
|
-
json_key_io, scope = options.values_at
|
60
|
-
user_creds = read_json_key
|
59
|
+
def self.make_creds options = {}
|
60
|
+
json_key_io, scope = options.values_at :json_key_io, :scope
|
61
|
+
user_creds = read_json_key json_key_io if json_key_io
|
61
62
|
user_creds ||= {
|
62
|
-
|
63
|
-
|
64
|
-
|
63
|
+
"client_id" => ENV[CredentialsLoader::CLIENT_ID_VAR],
|
64
|
+
"client_secret" => ENV[CredentialsLoader::CLIENT_SECRET_VAR],
|
65
|
+
"refresh_token" => ENV[CredentialsLoader::REFRESH_TOKEN_VAR],
|
66
|
+
"project_id" => ENV[CredentialsLoader::PROJECT_ID_VAR]
|
65
67
|
}
|
66
68
|
|
67
69
|
new(token_credential_uri: TOKEN_CRED_URI,
|
68
|
-
client_id:
|
69
|
-
client_secret:
|
70
|
-
refresh_token:
|
71
|
-
|
70
|
+
client_id: user_creds["client_id"],
|
71
|
+
client_secret: user_creds["client_secret"],
|
72
|
+
refresh_token: user_creds["refresh_token"],
|
73
|
+
project_id: user_creds["project_id"],
|
74
|
+
scope: scope)
|
75
|
+
.configure_connection(options)
|
72
76
|
end
|
73
77
|
|
74
78
|
# Reads the client_id, client_secret and refresh_token fields from the
|
75
79
|
# JSON key.
|
76
|
-
def self.read_json_key
|
77
|
-
json_key = MultiJson.load
|
78
|
-
wanted =
|
80
|
+
def self.read_json_key json_key_io
|
81
|
+
json_key = MultiJson.load json_key_io.read
|
82
|
+
wanted = ["client_id", "client_secret", "refresh_token"]
|
79
83
|
wanted.each do |key|
|
80
|
-
|
84
|
+
raise "the json is missing the #{key} field" unless json_key.key? key
|
81
85
|
end
|
82
86
|
json_key
|
83
87
|
end
|
84
88
|
|
85
|
-
def initialize
|
89
|
+
def initialize options = {}
|
86
90
|
options ||= {}
|
87
91
|
options[:token_credential_uri] ||= TOKEN_CRED_URI
|
88
92
|
options[:authorization_uri] ||= AUTHORIZATION_URI
|
89
|
-
|
93
|
+
@project_id = options[:project_id]
|
94
|
+
@project_id ||= CredentialsLoader.load_gcloud_project_id
|
95
|
+
super options
|
90
96
|
end
|
91
97
|
|
92
98
|
# Revokes the credential
|
93
|
-
def revoke!
|
99
|
+
def revoke! options = {}
|
94
100
|
c = options[:connection] || Faraday.default_connection
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
101
|
+
|
102
|
+
retry_with_error do
|
103
|
+
resp = c.post(REVOKE_TOKEN_URI, token: refresh_token || access_token)
|
104
|
+
case resp.status
|
105
|
+
when 200
|
106
|
+
self.access_token = nil
|
107
|
+
self.refresh_token = nil
|
108
|
+
self.expires_at = 0
|
109
|
+
else
|
110
|
+
raise(Signet::AuthorizationError,
|
111
|
+
"Unexpected error code #{resp.status}")
|
112
|
+
end
|
104
113
|
end
|
105
114
|
end
|
106
115
|
|
@@ -110,7 +119,7 @@ module Google
|
|
110
119
|
# Scope to verify
|
111
120
|
# @return [Boolean]
|
112
121
|
# True if scope is granted
|
113
|
-
def includes_scope?
|
122
|
+
def includes_scope? required_scope
|
114
123
|
missing_scope = Google::Auth::ScopeUtil.normalize(required_scope) -
|
115
124
|
Google::Auth::ScopeUtil.normalize(scope)
|
116
125
|
missing_scope.empty?
|
data/lib/googleauth/version.rb
CHANGED
@@ -27,11 +27,11 @@
|
|
27
27
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
28
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
29
|
|
30
|
-
require
|
31
|
-
require
|
32
|
-
require
|
33
|
-
require
|
34
|
-
require
|
30
|
+
require "multi_json"
|
31
|
+
require "googleauth/signet"
|
32
|
+
require "googleauth/user_authorizer"
|
33
|
+
require "googleauth/user_refresh"
|
34
|
+
require "securerandom"
|
35
35
|
|
36
36
|
module Google
|
37
37
|
module Auth
|
@@ -66,20 +66,21 @@ module Google
|
|
66
66
|
# @see {Google::Auth::ControllerHelpers}
|
67
67
|
# @note Requires sessions are enabled
|
68
68
|
class WebUserAuthorizer < Google::Auth::UserAuthorizer
|
69
|
-
STATE_PARAM =
|
70
|
-
AUTH_CODE_KEY =
|
71
|
-
ERROR_CODE_KEY =
|
72
|
-
SESSION_ID_KEY =
|
73
|
-
CALLBACK_STATE_KEY =
|
74
|
-
CURRENT_URI_KEY =
|
75
|
-
XSRF_KEY =
|
76
|
-
SCOPE_KEY =
|
69
|
+
STATE_PARAM = "state".freeze
|
70
|
+
AUTH_CODE_KEY = "code".freeze
|
71
|
+
ERROR_CODE_KEY = "error".freeze
|
72
|
+
SESSION_ID_KEY = "session_id".freeze
|
73
|
+
CALLBACK_STATE_KEY = "g-auth-callback".freeze
|
74
|
+
CURRENT_URI_KEY = "current_uri".freeze
|
75
|
+
XSRF_KEY = "g-xsrf-token".freeze
|
76
|
+
SCOPE_KEY = "scope".freeze
|
77
77
|
|
78
|
-
NIL_REQUEST_ERROR =
|
79
|
-
NIL_SESSION_ERROR =
|
80
|
-
MISSING_AUTH_CODE_ERROR =
|
81
|
-
AUTHORIZATION_ERROR =
|
82
|
-
INVALID_STATE_TOKEN_ERROR =
|
78
|
+
NIL_REQUEST_ERROR = "Request is required.".freeze
|
79
|
+
NIL_SESSION_ERROR = "Sessions must be enabled".freeze
|
80
|
+
MISSING_AUTH_CODE_ERROR = "Missing authorization code in request".freeze
|
81
|
+
AUTHORIZATION_ERROR = "Authorization error: %s".freeze
|
82
|
+
INVALID_STATE_TOKEN_ERROR =
|
83
|
+
"State token does not match expected value".freeze
|
83
84
|
|
84
85
|
class << self
|
85
86
|
attr_accessor :default
|
@@ -96,9 +97,9 @@ module Google
|
|
96
97
|
#
|
97
98
|
# @param [Rack::Request] request
|
98
99
|
# Current request
|
99
|
-
def self.handle_auth_callback_deferred
|
100
|
-
callback_state, redirect_uri = extract_callback_state
|
101
|
-
request.session[CALLBACK_STATE_KEY] = MultiJson.dump
|
100
|
+
def self.handle_auth_callback_deferred request
|
101
|
+
callback_state, redirect_uri = extract_callback_state request
|
102
|
+
request.session[CALLBACK_STATE_KEY] = MultiJson.dump callback_state
|
102
103
|
redirect_uri
|
103
104
|
end
|
104
105
|
|
@@ -113,8 +114,8 @@ module Google
|
|
113
114
|
# @param [String] callback_uri
|
114
115
|
# URL (either absolute or relative) of the auth callback. Defaults
|
115
116
|
# to '/oauth2callback'
|
116
|
-
def initialize
|
117
|
-
super
|
117
|
+
def initialize client_id, scope, token_store, callback_uri = nil
|
118
|
+
super client_id, scope, token_store, callback_uri
|
118
119
|
end
|
119
120
|
|
120
121
|
# Handle the result of the oauth callback. Exchanges the authorization
|
@@ -126,15 +127,17 @@ module Google
|
|
126
127
|
# Current request
|
127
128
|
# @return (Google::Auth::UserRefreshCredentials, String)
|
128
129
|
# credentials & next URL to redirect to
|
129
|
-
def handle_auth_callback
|
130
|
+
def handle_auth_callback user_id, request
|
130
131
|
callback_state, redirect_uri = WebUserAuthorizer.extract_callback_state(
|
131
|
-
request
|
132
|
-
|
132
|
+
request
|
133
|
+
)
|
134
|
+
WebUserAuthorizer.validate_callback_state callback_state, request
|
133
135
|
credentials = get_and_store_credentials_from_code(
|
134
|
-
user_id:
|
135
|
-
code:
|
136
|
-
scope:
|
137
|
-
base_url: request.url
|
136
|
+
user_id: user_id,
|
137
|
+
code: callback_state[AUTH_CODE_KEY],
|
138
|
+
scope: callback_state[SCOPE_KEY],
|
139
|
+
base_url: request.url
|
140
|
+
)
|
138
141
|
[credentials, redirect_uri]
|
139
142
|
end
|
140
143
|
|
@@ -151,29 +154,35 @@ module Google
|
|
151
154
|
# @param [String, Array<String>] scope
|
152
155
|
# Authorization scope to request. Overrides the instance scopes if
|
153
156
|
# not nil.
|
157
|
+
# @param [Hash] state
|
158
|
+
# Optional key-values to be returned to the oauth callback.
|
154
159
|
# @return [String]
|
155
160
|
# Authorization url
|
156
|
-
def get_authorization_url
|
161
|
+
def get_authorization_url options = {}
|
157
162
|
options = options.dup
|
158
163
|
request = options[:request]
|
159
|
-
|
160
|
-
|
164
|
+
raise NIL_REQUEST_ERROR if request.nil?
|
165
|
+
raise NIL_SESSION_ERROR if request.session.nil?
|
166
|
+
|
167
|
+
state = options[:state] || {}
|
161
168
|
|
162
169
|
redirect_to = options[:redirect_to] || request.url
|
163
170
|
request.session[XSRF_KEY] = SecureRandom.base64
|
164
|
-
options[:state] = MultiJson.dump(
|
165
|
-
|
166
|
-
|
171
|
+
options[:state] = MultiJson.dump(state.merge(
|
172
|
+
SESSION_ID_KEY => request.session[XSRF_KEY],
|
173
|
+
CURRENT_URI_KEY => redirect_to
|
174
|
+
))
|
167
175
|
options[:base_url] = request.url
|
168
|
-
super
|
176
|
+
super options
|
169
177
|
end
|
170
178
|
|
171
|
-
# Fetch stored credentials for the user.
|
179
|
+
# Fetch stored credentials for the user from the given request session.
|
172
180
|
#
|
173
181
|
# @param [String] user_id
|
174
182
|
# Unique ID of the user for loading/storing credentials.
|
175
183
|
# @param [Rack::Request] request
|
176
|
-
# Current request
|
184
|
+
# Current request. Optional. If omitted, this will attempt to fall back
|
185
|
+
# on the base class behavior of reading from the token store.
|
177
186
|
# @param [Array<String>, String] scope
|
178
187
|
# If specified, only returns credentials that have all the \
|
179
188
|
# requested scopes
|
@@ -182,31 +191,32 @@ module Google
|
|
182
191
|
# @raise [Signet::AuthorizationError]
|
183
192
|
# May raise an error if an authorization code is present in the session
|
184
193
|
# and exchange of the code fails
|
185
|
-
def get_credentials
|
186
|
-
if request.session.key?(CALLBACK_STATE_KEY)
|
194
|
+
def get_credentials user_id, request = nil, scope = nil
|
195
|
+
if request && request.session.key?(CALLBACK_STATE_KEY)
|
187
196
|
# Note - in theory, no need to check required scope as this is
|
188
197
|
# expected to be called immediately after a return from authorization
|
189
|
-
state_json = request.session.delete
|
190
|
-
callback_state = MultiJson.load
|
191
|
-
WebUserAuthorizer.validate_callback_state
|
198
|
+
state_json = request.session.delete CALLBACK_STATE_KEY
|
199
|
+
callback_state = MultiJson.load state_json
|
200
|
+
WebUserAuthorizer.validate_callback_state callback_state, request
|
192
201
|
get_and_store_credentials_from_code(
|
193
|
-
user_id:
|
194
|
-
code:
|
195
|
-
scope:
|
196
|
-
base_url: request.url
|
202
|
+
user_id: user_id,
|
203
|
+
code: callback_state[AUTH_CODE_KEY],
|
204
|
+
scope: callback_state[SCOPE_KEY],
|
205
|
+
base_url: request.url
|
206
|
+
)
|
197
207
|
else
|
198
|
-
super
|
208
|
+
super user_id, scope
|
199
209
|
end
|
200
210
|
end
|
201
211
|
|
202
|
-
def self.extract_callback_state
|
203
|
-
state = MultiJson.load(request[STATE_PARAM] ||
|
212
|
+
def self.extract_callback_state request
|
213
|
+
state = MultiJson.load(request[STATE_PARAM] || "{}")
|
204
214
|
redirect_uri = state[CURRENT_URI_KEY]
|
205
215
|
callback_state = {
|
206
|
-
AUTH_CODE_KEY
|
207
|
-
ERROR_CODE_KEY =>
|
216
|
+
AUTH_CODE_KEY => request[AUTH_CODE_KEY],
|
217
|
+
ERROR_CODE_KEY => request[ERROR_CODE_KEY],
|
208
218
|
SESSION_ID_KEY => state[SESSION_ID_KEY],
|
209
|
-
SCOPE_KEY
|
219
|
+
SCOPE_KEY => request[SCOPE_KEY]
|
210
220
|
}
|
211
221
|
[callback_state, redirect_uri]
|
212
222
|
end
|
@@ -221,14 +231,13 @@ module Google
|
|
221
231
|
# Error message if failed
|
222
232
|
# @param [Rack::Request] request
|
223
233
|
# Current request
|
224
|
-
def self.validate_callback_state
|
225
|
-
if state[AUTH_CODE_KEY].nil?
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
sprintf(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY])
|
234
|
+
def self.validate_callback_state state, request
|
235
|
+
raise Signet::AuthorizationError, MISSING_AUTH_CODE_ERROR if state[AUTH_CODE_KEY].nil?
|
236
|
+
if state[ERROR_CODE_KEY]
|
237
|
+
raise Signet::AuthorizationError,
|
238
|
+
format(AUTHORIZATION_ERROR, state[ERROR_CODE_KEY])
|
230
239
|
elsif request.session[XSRF_KEY] != state[SESSION_ID_KEY]
|
231
|
-
|
240
|
+
raise Signet::AuthorizationError, INVALID_STATE_TOKEN_ERROR
|
232
241
|
end
|
233
242
|
end
|
234
243
|
|
@@ -254,7 +263,7 @@ module Google
|
|
254
263
|
#
|
255
264
|
# @see {Google::Auth::WebUserAuthorizer}
|
256
265
|
class CallbackApp
|
257
|
-
LOCATION_HEADER =
|
266
|
+
LOCATION_HEADER = "Location".freeze
|
258
267
|
REDIR_STATUS = 302
|
259
268
|
ERROR_STATUS = 500
|
260
269
|
|
@@ -270,18 +279,18 @@ module Google
|
|
270
279
|
# Rack environment
|
271
280
|
# @return [Array]
|
272
281
|
# HTTP response
|
273
|
-
def self.call
|
274
|
-
request = Rack::Request.new
|
275
|
-
return_url = WebUserAuthorizer.handle_auth_callback_deferred
|
282
|
+
def self.call env
|
283
|
+
request = Rack::Request.new env
|
284
|
+
return_url = WebUserAuthorizer.handle_auth_callback_deferred request
|
276
285
|
if return_url
|
277
286
|
[REDIR_STATUS, { LOCATION_HEADER => return_url }, []]
|
278
287
|
else
|
279
|
-
[ERROR_STATUS, {}, [
|
288
|
+
[ERROR_STATUS, {}, ["No return URL is present in the request."]]
|
280
289
|
end
|
281
290
|
end
|
282
291
|
|
283
|
-
def call
|
284
|
-
self.class.call
|
292
|
+
def call env
|
293
|
+
self.class.call env
|
285
294
|
end
|
286
295
|
end
|
287
296
|
end
|