rodauth-oauth 0.2.0 → 0.4.3
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 +76 -22
- data/README.md +1 -1
- data/lib/rodauth/features/oauth.rb +341 -241
- data/lib/rodauth/features/oauth_http_mac.rb +6 -10
- data/lib/rodauth/features/oauth_jwt.rb +15 -15
- data/lib/rodauth/features/oidc.rb +217 -85
- data/lib/rodauth/oauth/ttl_store.rb +1 -1
- data/lib/rodauth/oauth/version.rb +1 -1
- data/templates/authorize.str +34 -0
- data/templates/client_secret_field.str +4 -0
- data/templates/description_field.str +4 -0
- data/templates/homepage_url_field.str +4 -0
- data/templates/name_field.str +4 -0
- data/templates/new_oauth_application.str +10 -0
- data/templates/oauth_application.str +11 -0
- data/templates/oauth_applications.str +14 -0
- data/templates/oauth_tokens.str +49 -0
- data/templates/redirect_uri_field.str +4 -0
- data/templates/scope_field.str +10 -0
- metadata +18 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 96756ac8a30c904c5b832b64c47a00af9524810561d58c909b6f322da7348e8c
|
|
4
|
+
data.tar.gz: 965f6ff260bd86c2fcb7bbd2ba2bd131b453f04a39b73f99ef0860d2bc95b0e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e7e257a12204599a27d0917f2b31c32906f0d4c566d51ee6d4fde146e2340e36afb9a932cff8bf37872d59259f4d43d423d1c1266f3066063c70aa334f83e119
|
|
7
|
+
data.tar.gz: 07c0e564e7636893f736f6e05f634684cd7bc28e9d0acfb53ba518357fab198bc878792a68bde6b988b8c8ddf2d3e2bb4d4ecebcd9c4bf68d85f75178cdd0fdf
|
data/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,75 @@
|
|
|
2
2
|
|
|
3
3
|
## master
|
|
4
4
|
|
|
5
|
-
### 0.
|
|
5
|
+
### 0.4.3 (09/12/2020)
|
|
6
|
+
|
|
7
|
+
* Introspection requests made to an Authorization Server in "resource server" mode are not correctly encoding the body using the "application/x-www-form-urlencoded" format.
|
|
8
|
+
|
|
9
|
+
### 0.4.2 (24/11/2020)
|
|
10
|
+
|
|
11
|
+
### Bugfixes
|
|
12
|
+
|
|
13
|
+
* database extensions were being run in resource server mode, when it's not expected that the oauth db tables are around.
|
|
14
|
+
|
|
15
|
+
### 0.4.1 (24/11/2020)
|
|
16
|
+
|
|
17
|
+
### Improvements
|
|
18
|
+
|
|
19
|
+
When in "Resource Server" mode, calling `rodauth.authorization_token` will now return an hash of the JSON payload that the Authorization Server responds, and which was already previously used to authorize access to protected resources.
|
|
20
|
+
|
|
21
|
+
### Bugfixes
|
|
22
|
+
|
|
23
|
+
* An error occurred if the client passed an empty authorization header (`Authorization: ` or `Authorization: Bearer `), causing an unexpected error; It now responds with the proper `401 Unauthorized` status code.
|
|
24
|
+
|
|
25
|
+
### 0.4.0 (13/11/2020)
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
* A new method, `get_additional_param(account, claim)`, is now exposed; this method will be called whenever non-OIDC scopes are requested in the emission of the ID token.
|
|
30
|
+
|
|
31
|
+
* The `form_post` response is now supported, either by passing the `response_mode=form_post` request param in the authorization URL, or by setting `oauth_response_mode "form_post"` option. This improves the overall security of an Authorization server even more, as authorization codes are sent to client applications via a POST request to the redirect URI.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Improvements
|
|
35
|
+
|
|
36
|
+
* For the OIDC `address` scope, proper claims are now emitted as per the standard, i.e. the "formatted", "street_address", "locality", "region", "postal_code", "country". These will be the ones referenced in the `get_oidc_param` method.
|
|
37
|
+
|
|
38
|
+
### Bugfixes
|
|
39
|
+
|
|
40
|
+
* The rails templates were missing declarations from a few params, which made some of the flows (the PKCE for example) not work out-of-the box;
|
|
41
|
+
* rails tests were silently not running in CI;
|
|
42
|
+
* The CI suite was revamped, so that all Oauth tests would be run under rails as well. All versions from rails equal or above 5.0 are now targeted;
|
|
43
|
+
|
|
44
|
+
### 0.3.0 (8/10/2020)
|
|
45
|
+
|
|
46
|
+
#### Features
|
|
47
|
+
|
|
48
|
+
* `oauth_refresh_token_protection_policy` is a new option, which can be used to set a protection policy around usage of refresh tokens. By default it's `none`, for backwards-compatibility. However, when set to `rotation`, refresh tokens will be "use-once", i.e. a token refresh request will generate a new refresh token. Also, refresh token requests performed with already-used refresh tokens will be interpreted as a security breach, i.e. all tokens linked to the compromised refresh token will be revoked.
|
|
49
|
+
|
|
50
|
+
#### Improvements
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
* Support for the OIDC authorize [`prompt` parameter](https://openid.net/specs/openid-connect-core-1_0.html) (sectionn 3.1.2.1). It supports the `none`, `login` and `consent` out-of-the-box, while providing support for `select-account` when paired with [rodauth-select-account, a rodauth feature to handle multiple accounts in the same session](https://gitlab.com/honeyryderchuck/rodauth-select-account).
|
|
54
|
+
|
|
55
|
+
* Refresh Tokens are now expirable. The refresh token expiration period is governed by the `oauth_refresh_token_expires_in` option (default: 1 year), and is the period for which a refresh token can be used after its respective access token expired.
|
|
56
|
+
|
|
57
|
+
#### Bugfixes
|
|
58
|
+
|
|
59
|
+
* Default Templates now being packaged, as a way to provide a default experience to the OAuth journeys.
|
|
60
|
+
|
|
61
|
+
* fixing metadata urls when plugin loaded with a prefix path (@ianks)
|
|
62
|
+
|
|
63
|
+
* All date/time-based calculations, such as determining an expiration date, or checking if a token has expired, are now performed using database arithmetic operations, using sequel's `date_arithmetic` plugin. This will eliminate subtle bugs, such as when the database timezone is different than the application OS timezone.
|
|
64
|
+
|
|
65
|
+
* OIDC configuration endpoint is now stricter, eliminating JSON metadata inherited from the Oauth metadata endpoint. (@ianks)
|
|
66
|
+
|
|
67
|
+
#### Chore
|
|
68
|
+
|
|
69
|
+
Use `rodauth.convert_timestamp` in the templates, whenever dates are displayed.
|
|
70
|
+
|
|
71
|
+
Set HTTP Cache headers for metadata responses, such as `/.well-known/oauth-authorization-server` and `/.well-known/openid-configuration`, so they can be stored at the edge. The cache will be valid for 1 day (this value isn't set by an option yet).
|
|
72
|
+
|
|
73
|
+
### 0.2.0 (9/9/2020)
|
|
6
74
|
|
|
7
75
|
#### Features
|
|
8
76
|
|
|
@@ -46,9 +114,7 @@ Fixed some mishandling of HTTP headers when in in resource-server mode.
|
|
|
46
114
|
* 97.7% test coverage;
|
|
47
115
|
* `rodauth-oauth` CI tests run against sqlite, postgresql and mysql.
|
|
48
116
|
|
|
49
|
-
### 0.1.0
|
|
50
|
-
|
|
51
|
-
(31/7/2020)
|
|
117
|
+
### 0.1.0 (31/7/2020)
|
|
52
118
|
|
|
53
119
|
#### Features
|
|
54
120
|
|
|
@@ -94,9 +160,7 @@ URI schemes for client applications redirect URIs have to be `https`. In order t
|
|
|
94
160
|
* fixed trailing "/" in the "issuer" value in server metadata (`https://server.com/` -> `https://server.com`).
|
|
95
161
|
|
|
96
162
|
|
|
97
|
-
### 0.0.6
|
|
98
|
-
|
|
99
|
-
(6/7/2020)
|
|
163
|
+
### 0.0.6 (6/7/2020)
|
|
100
164
|
|
|
101
165
|
#### Features
|
|
102
166
|
|
|
@@ -119,9 +183,7 @@ The `oauth_jwt` feature now supports JWT Secured Authorization Request (JAR) (se
|
|
|
119
183
|
Removed React Javascript from example applications.
|
|
120
184
|
|
|
121
185
|
|
|
122
|
-
### 0.0.5
|
|
123
|
-
|
|
124
|
-
(26/6/2020)
|
|
186
|
+
### 0.0.5 (26/6/2020)
|
|
125
187
|
|
|
126
188
|
#### Features
|
|
127
189
|
|
|
@@ -158,9 +220,7 @@ It **requires** the authorization to implement the server metadata endpoint (`/.
|
|
|
158
220
|
* option `scopes_param` renamed to `scope_param`;
|
|
159
221
|
*
|
|
160
222
|
|
|
161
|
-
## 0.0.4
|
|
162
|
-
|
|
163
|
-
(13/6/2020)
|
|
223
|
+
## 0.0.4 (13/6/2020)
|
|
164
224
|
|
|
165
225
|
### Features
|
|
166
226
|
|
|
@@ -197,9 +257,7 @@ The `oauth_jwt` feature now allows the usage of access tokens to authorize the g
|
|
|
197
257
|
|
|
198
258
|
* Fixed scope claim of JWT ("scopes" -> "scope");
|
|
199
259
|
|
|
200
|
-
## 0.0.3
|
|
201
|
-
|
|
202
|
-
(5/6/2020)
|
|
260
|
+
## 0.0.3 (5/6/2020)
|
|
203
261
|
|
|
204
262
|
### Features
|
|
205
263
|
|
|
@@ -231,9 +289,7 @@ end
|
|
|
231
289
|
* renamed the existing `use_oauth_implicit_grant_type` to `use_oauth_implicit_grant_type?`;
|
|
232
290
|
* It's now usable as JSON API (small caveat: POST authorize will still redirect on success...);
|
|
233
291
|
|
|
234
|
-
## 0.0.2
|
|
235
|
-
|
|
236
|
-
(29/5/2020)
|
|
292
|
+
## 0.0.2 (29/5/2020)
|
|
237
293
|
|
|
238
294
|
### Features
|
|
239
295
|
|
|
@@ -249,8 +305,6 @@ end
|
|
|
249
305
|
|
|
250
306
|
* usage of client secret for authorizing the generation of tokens, as the spec mandates (and refraining from them when doing PKCE).
|
|
251
307
|
|
|
252
|
-
## 0.0.1
|
|
253
|
-
|
|
254
|
-
(14/5/2020)
|
|
308
|
+
## 0.0.1 (14/5/2020)
|
|
255
309
|
|
|
256
310
|
Initial implementation of the Oauth 2.0 framework, with an example app done using roda.
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Rodauth::Oauth
|
|
2
2
|
|
|
3
3
|
[](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/pipelines?page=1&ref=master)
|
|
4
|
-
[](https://honeyryderchuck.gitlab.io/rodauth-oauth/coverage/#_AllFiles)
|
|
4
|
+
[](https://honeyryderchuck.gitlab.io/rodauth-oauth/coverage/#_AllFiles)
|
|
5
5
|
|
|
6
6
|
This is an extension to the `rodauth` gem which implements the [OAuth 2.0 framework](https://tools.ietf.org/html/rfc6749) for an authorization server.
|
|
7
7
|
|
|
@@ -46,6 +46,8 @@ module Rodauth
|
|
|
46
46
|
|
|
47
47
|
SCOPES = %w[profile.read].freeze
|
|
48
48
|
|
|
49
|
+
SERVER_METADATA = OAuth::TtlStore.new
|
|
50
|
+
|
|
49
51
|
before "authorize"
|
|
50
52
|
after "authorize"
|
|
51
53
|
|
|
@@ -75,12 +77,14 @@ module Rodauth
|
|
|
75
77
|
|
|
76
78
|
auth_value_method :oauth_grant_expires_in, 60 * 5 # 5 minutes
|
|
77
79
|
auth_value_method :oauth_token_expires_in, 60 * 60 # 60 minutes
|
|
80
|
+
auth_value_method :oauth_refresh_token_expires_in, 60 * 60 * 24 * 360 # 1 year
|
|
78
81
|
auth_value_method :use_oauth_implicit_grant_type?, false
|
|
79
82
|
auth_value_method :use_oauth_pkce?, true
|
|
80
83
|
auth_value_method :use_oauth_access_type?, true
|
|
81
84
|
|
|
82
85
|
auth_value_method :oauth_require_pkce, false
|
|
83
86
|
auth_value_method :oauth_pkce_challenge_method, "S256"
|
|
87
|
+
auth_value_method :oauth_response_mode, "query"
|
|
84
88
|
|
|
85
89
|
auth_value_method :oauth_valid_uri_schemes, %w[https]
|
|
86
90
|
|
|
@@ -97,6 +101,7 @@ module Rodauth
|
|
|
97
101
|
button "Register", "oauth_application"
|
|
98
102
|
button "Authorize", "oauth_authorize"
|
|
99
103
|
button "Revoke", "oauth_token_revoke"
|
|
104
|
+
button "Back to Client Application", "oauth_authorize_post"
|
|
100
105
|
|
|
101
106
|
# OAuth Token
|
|
102
107
|
auth_value_method :oauth_tokens_path, "oauth-tokens"
|
|
@@ -149,9 +154,11 @@ module Rodauth
|
|
|
149
154
|
auth_value_method :"oauth_applications_#{column}_column", column
|
|
150
155
|
end
|
|
151
156
|
|
|
157
|
+
# Feature options
|
|
152
158
|
auth_value_method :oauth_application_default_scope, SCOPES.first
|
|
153
159
|
auth_value_method :oauth_application_scopes, SCOPES
|
|
154
160
|
auth_value_method :oauth_token_type, "bearer"
|
|
161
|
+
auth_value_method :oauth_refresh_token_protection_policy, "none" # can be: none, sender_constrained, rotation
|
|
155
162
|
|
|
156
163
|
auth_value_method :invalid_client_message, "Invalid client"
|
|
157
164
|
auth_value_method :invalid_grant_type_message, "Invalid grant type"
|
|
@@ -200,7 +207,214 @@ module Rodauth
|
|
|
200
207
|
|
|
201
208
|
auth_value_method :json_request_regexp, %r{\bapplication/(?:vnd\.api\+)?json\b}i
|
|
202
209
|
|
|
203
|
-
|
|
210
|
+
# /token
|
|
211
|
+
route(:token) do |r|
|
|
212
|
+
next unless is_authorization_server?
|
|
213
|
+
|
|
214
|
+
before_token_route
|
|
215
|
+
require_oauth_application
|
|
216
|
+
|
|
217
|
+
r.post do
|
|
218
|
+
catch_error do
|
|
219
|
+
validate_oauth_token_params
|
|
220
|
+
|
|
221
|
+
oauth_token = nil
|
|
222
|
+
transaction do
|
|
223
|
+
before_token
|
|
224
|
+
oauth_token = create_oauth_token
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
json_response_success(json_access_token_payload(oauth_token))
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
throw_json_response_error(invalid_oauth_response_status, "invalid_request")
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# /introspect
|
|
235
|
+
route(:introspect) do |r|
|
|
236
|
+
next unless is_authorization_server?
|
|
237
|
+
|
|
238
|
+
before_introspect_route
|
|
239
|
+
|
|
240
|
+
r.post do
|
|
241
|
+
catch_error do
|
|
242
|
+
validate_oauth_introspect_params
|
|
243
|
+
|
|
244
|
+
before_introspect
|
|
245
|
+
oauth_token = case param("token_type_hint")
|
|
246
|
+
when "access_token"
|
|
247
|
+
oauth_token_by_token(param("token"))
|
|
248
|
+
when "refresh_token"
|
|
249
|
+
oauth_token_by_refresh_token(param("token"))
|
|
250
|
+
else
|
|
251
|
+
oauth_token_by_token(param("token")) || oauth_token_by_refresh_token(param("token"))
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
if oauth_application
|
|
255
|
+
redirect_response_error("invalid_request") if oauth_token && !token_from_application?(oauth_token, oauth_application)
|
|
256
|
+
elsif oauth_token
|
|
257
|
+
@oauth_application = db[oauth_applications_table].where(oauth_applications_id_column =>
|
|
258
|
+
oauth_token[oauth_tokens_oauth_application_id_column]).first
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
json_response_success(json_token_introspect_payload(oauth_token))
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
throw_json_response_error(invalid_oauth_response_status, "invalid_request")
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# /revoke
|
|
269
|
+
route(:revoke) do |r|
|
|
270
|
+
next unless is_authorization_server?
|
|
271
|
+
|
|
272
|
+
before_revoke_route
|
|
273
|
+
require_oauth_application
|
|
274
|
+
|
|
275
|
+
r.post do
|
|
276
|
+
catch_error do
|
|
277
|
+
validate_oauth_revoke_params
|
|
278
|
+
|
|
279
|
+
oauth_token = nil
|
|
280
|
+
transaction do
|
|
281
|
+
before_revoke
|
|
282
|
+
oauth_token = revoke_oauth_token
|
|
283
|
+
after_revoke
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
if accepts_json?
|
|
287
|
+
json_response_success \
|
|
288
|
+
"token" => oauth_token[oauth_tokens_token_column],
|
|
289
|
+
"refresh_token" => oauth_token[oauth_tokens_refresh_token_column],
|
|
290
|
+
"revoked_at" => convert_timestamp(oauth_token[oauth_tokens_revoked_at_column])
|
|
291
|
+
else
|
|
292
|
+
set_notice_flash revoke_oauth_token_notice_flash
|
|
293
|
+
redirect request.referer || "/"
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
redirect_response_error("invalid_request", request.referer || "/")
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# /authorize
|
|
302
|
+
route(:authorize) do |r|
|
|
303
|
+
next unless is_authorization_server?
|
|
304
|
+
|
|
305
|
+
before_authorize_route
|
|
306
|
+
require_authorizable_account
|
|
307
|
+
|
|
308
|
+
validate_oauth_grant_params
|
|
309
|
+
try_approval_prompt if use_oauth_access_type? && request.get?
|
|
310
|
+
|
|
311
|
+
r.get do
|
|
312
|
+
authorize_view
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
r.post do
|
|
316
|
+
redirect_url = URI.parse(redirect_uri)
|
|
317
|
+
|
|
318
|
+
params, mode = transaction do
|
|
319
|
+
before_authorize
|
|
320
|
+
do_authorize
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
case mode
|
|
324
|
+
when "query"
|
|
325
|
+
params = params.map { |k, v| "#{k}=#{v}" }
|
|
326
|
+
params << redirect_url.query if redirect_url.query
|
|
327
|
+
redirect_url.query = params.join("&")
|
|
328
|
+
redirect(redirect_url.to_s)
|
|
329
|
+
when "fragment"
|
|
330
|
+
params = params.map { |k, v| "#{k}=#{v}" }
|
|
331
|
+
params << redirect_url.query if redirect_url.query
|
|
332
|
+
redirect_url.fragment = params.join("&")
|
|
333
|
+
redirect(redirect_url.to_s)
|
|
334
|
+
when "form_post"
|
|
335
|
+
scope.view layout: false, inline: <<-FORM
|
|
336
|
+
<html>
|
|
337
|
+
<head><title>Authorized</title></head>
|
|
338
|
+
<body onload="javascript:document.forms[0].submit()">
|
|
339
|
+
<form method="post" action="#{redirect_uri}">
|
|
340
|
+
#{
|
|
341
|
+
params.map do |name, value|
|
|
342
|
+
"<input type=\"hidden\" name=\"#{name}\" value=\"#{scope.h(value)}\" />"
|
|
343
|
+
end.join
|
|
344
|
+
}
|
|
345
|
+
<input type="submit" class="btn btn-outline-primary" value="#{scope.h(oauth_authorize_post_button)}"/>
|
|
346
|
+
</form>
|
|
347
|
+
</body>
|
|
348
|
+
</html>
|
|
349
|
+
FORM
|
|
350
|
+
when "none"
|
|
351
|
+
redirect(redirect_url.to_s)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def oauth_server_metadata(issuer = nil)
|
|
357
|
+
request.on(".well-known") do
|
|
358
|
+
request.on("oauth-authorization-server") do
|
|
359
|
+
request.get do
|
|
360
|
+
json_response_success(oauth_server_metadata_body(issuer), true)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# /oauth-applications routes
|
|
367
|
+
def oauth_applications
|
|
368
|
+
request.on(oauth_applications_path) do
|
|
369
|
+
require_account
|
|
370
|
+
|
|
371
|
+
request.get "new" do
|
|
372
|
+
new_oauth_application_view
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
request.on(oauth_applications_id_pattern) do |id|
|
|
376
|
+
oauth_application = db[oauth_applications_table].where(oauth_applications_id_column => id).first
|
|
377
|
+
next unless oauth_application
|
|
378
|
+
|
|
379
|
+
scope.instance_variable_set(:@oauth_application, oauth_application)
|
|
380
|
+
|
|
381
|
+
request.is do
|
|
382
|
+
request.get do
|
|
383
|
+
oauth_application_view
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
request.on(oauth_tokens_path) do
|
|
388
|
+
oauth_tokens = db[oauth_tokens_table].where(oauth_tokens_oauth_application_id_column => id)
|
|
389
|
+
scope.instance_variable_set(:@oauth_tokens, oauth_tokens)
|
|
390
|
+
request.get do
|
|
391
|
+
oauth_tokens_view
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
request.get do
|
|
397
|
+
scope.instance_variable_set(:@oauth_applications, db[oauth_applications_table])
|
|
398
|
+
oauth_applications_view
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
request.post do
|
|
402
|
+
catch_error do
|
|
403
|
+
validate_oauth_application_params
|
|
404
|
+
|
|
405
|
+
transaction do
|
|
406
|
+
before_create_oauth_application
|
|
407
|
+
id = create_oauth_application
|
|
408
|
+
after_create_oauth_application
|
|
409
|
+
set_notice_flash create_oauth_application_notice_flash
|
|
410
|
+
redirect "#{request.path}/#{id}"
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
set_error_flash create_oauth_application_error_flash
|
|
414
|
+
new_oauth_application_view
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
204
418
|
|
|
205
419
|
def check_csrf?
|
|
206
420
|
case request.path
|
|
@@ -275,13 +489,13 @@ module Rodauth
|
|
|
275
489
|
def fetch_access_token
|
|
276
490
|
value = request.env["HTTP_AUTHORIZATION"]
|
|
277
491
|
|
|
278
|
-
return unless value
|
|
492
|
+
return unless value && !value.empty?
|
|
279
493
|
|
|
280
494
|
scheme, token = value.split(" ", 2)
|
|
281
495
|
|
|
282
496
|
return unless scheme.downcase == oauth_token_type
|
|
283
497
|
|
|
284
|
-
return if token.empty?
|
|
498
|
+
return if token.nil? || token.empty?
|
|
285
499
|
|
|
286
500
|
token
|
|
287
501
|
end
|
|
@@ -294,101 +508,46 @@ module Rodauth
|
|
|
294
508
|
|
|
295
509
|
return unless bearer_token
|
|
296
510
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
511
|
+
@authorization_token = if is_authorization_server?
|
|
512
|
+
# check if token has not expired
|
|
513
|
+
# check if token has been revoked
|
|
514
|
+
oauth_token_by_token(bearer_token)
|
|
515
|
+
else
|
|
516
|
+
# where in resource server, NOT the authorization server.
|
|
517
|
+
payload = introspection_request("access_token", bearer_token)
|
|
518
|
+
|
|
519
|
+
return unless payload["active"]
|
|
520
|
+
|
|
521
|
+
payload
|
|
522
|
+
end
|
|
300
523
|
end
|
|
301
524
|
|
|
302
525
|
def require_oauth_authorization(*scopes)
|
|
303
|
-
|
|
304
|
-
authorization_required unless authorization_token
|
|
526
|
+
authorization_required unless authorization_token
|
|
305
527
|
|
|
306
|
-
|
|
528
|
+
scopes << oauth_application_default_scope if scopes.empty?
|
|
307
529
|
|
|
530
|
+
token_scopes = if is_authorization_server?
|
|
308
531
|
authorization_token[oauth_tokens_scopes_column].split(oauth_scope_separator)
|
|
309
532
|
else
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
# where in resource server, NOT the authorization server.
|
|
317
|
-
payload = introspection_request("access_token", bearer_token)
|
|
318
|
-
|
|
319
|
-
authorization_required unless payload["active"]
|
|
320
|
-
|
|
321
|
-
payload["scope"].split(oauth_scope_separator)
|
|
533
|
+
aux_scopes = authorization_token["scope"]
|
|
534
|
+
if aux_scopes
|
|
535
|
+
aux_scopes.split(oauth_scope_separator)
|
|
536
|
+
else
|
|
537
|
+
[]
|
|
538
|
+
end
|
|
322
539
|
end
|
|
323
540
|
|
|
324
541
|
authorization_required unless scopes.any? { |scope| token_scopes.include?(scope) }
|
|
325
542
|
end
|
|
326
543
|
|
|
327
|
-
# /oauth-applications routes
|
|
328
|
-
def oauth_applications
|
|
329
|
-
request.on(oauth_applications_path) do
|
|
330
|
-
require_account
|
|
331
|
-
|
|
332
|
-
request.get "new" do
|
|
333
|
-
new_oauth_application_view
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
request.on(oauth_applications_id_pattern) do |id|
|
|
337
|
-
oauth_application = db[oauth_applications_table].where(oauth_applications_id_column => id).first
|
|
338
|
-
next unless oauth_application
|
|
339
|
-
|
|
340
|
-
scope.instance_variable_set(:@oauth_application, oauth_application)
|
|
341
|
-
|
|
342
|
-
request.is do
|
|
343
|
-
request.get do
|
|
344
|
-
oauth_application_view
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
request.on(oauth_tokens_path) do
|
|
349
|
-
oauth_tokens = db[oauth_tokens_table].where(oauth_tokens_oauth_application_id_column => id)
|
|
350
|
-
scope.instance_variable_set(:@oauth_tokens, oauth_tokens)
|
|
351
|
-
request.get do
|
|
352
|
-
oauth_tokens_view
|
|
353
|
-
end
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
request.get do
|
|
358
|
-
scope.instance_variable_set(:@oauth_applications, db[oauth_applications_table])
|
|
359
|
-
oauth_applications_view
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
request.post do
|
|
363
|
-
catch_error do
|
|
364
|
-
validate_oauth_application_params
|
|
365
|
-
|
|
366
|
-
transaction do
|
|
367
|
-
before_create_oauth_application
|
|
368
|
-
id = create_oauth_application
|
|
369
|
-
after_create_oauth_application
|
|
370
|
-
set_notice_flash create_oauth_application_notice_flash
|
|
371
|
-
redirect "#{request.path}/#{id}"
|
|
372
|
-
end
|
|
373
|
-
end
|
|
374
|
-
set_error_flash create_oauth_application_error_flash
|
|
375
|
-
new_oauth_application_view
|
|
376
|
-
end
|
|
377
|
-
end
|
|
378
|
-
end
|
|
379
|
-
|
|
380
|
-
def oauth_server_metadata(issuer = nil)
|
|
381
|
-
request.on(".well-known") do
|
|
382
|
-
request.on("oauth-authorization-server") do
|
|
383
|
-
request.get do
|
|
384
|
-
json_response_success(oauth_server_metadata_body(issuer))
|
|
385
|
-
end
|
|
386
|
-
end
|
|
387
|
-
end
|
|
388
|
-
end
|
|
389
|
-
|
|
390
544
|
def post_configure
|
|
391
545
|
super
|
|
546
|
+
|
|
547
|
+
# all of the extensions below involve DB changes. Resource server mode doesn't use
|
|
548
|
+
# database functions for OAuth though.
|
|
549
|
+
return unless is_authorization_server?
|
|
550
|
+
|
|
392
551
|
self.class.__send__(:include, Rodauth::OAuth::ExtendDatabase(db))
|
|
393
552
|
|
|
394
553
|
# Check whether we can reutilize db entries for the same account / application pair
|
|
@@ -401,6 +560,10 @@ module Rodauth
|
|
|
401
560
|
self.class.send(:define_method, :__one_oauth_token_per_account) { one_oauth_token_per_account }
|
|
402
561
|
end
|
|
403
562
|
|
|
563
|
+
def use_date_arithmetic?
|
|
564
|
+
true
|
|
565
|
+
end
|
|
566
|
+
|
|
404
567
|
private
|
|
405
568
|
|
|
406
569
|
def rescue_from_uniqueness_error(&block)
|
|
@@ -446,9 +609,9 @@ module Rodauth
|
|
|
446
609
|
# time-to-live
|
|
447
610
|
ttl = if response.key?("cache-control")
|
|
448
611
|
cache_control = response["cache-control"]
|
|
449
|
-
cache_control[/max-age=(\d+)/, 1]
|
|
612
|
+
cache_control[/max-age=(\d+)/, 1].to_i
|
|
450
613
|
elsif response.key?("expires")
|
|
451
|
-
|
|
614
|
+
Time.parse(response["expires"]).to_i - Time.now.to_i
|
|
452
615
|
end
|
|
453
616
|
|
|
454
617
|
[JSON.parse(response.body, symbolize_names: true), ttl]
|
|
@@ -461,9 +624,9 @@ module Rodauth
|
|
|
461
624
|
http.use_ssl = auth_url.scheme == "https"
|
|
462
625
|
|
|
463
626
|
request = Net::HTTP::Post.new(introspect_path)
|
|
464
|
-
request["content-type"] =
|
|
627
|
+
request["content-type"] = "application/x-www-form-urlencoded"
|
|
465
628
|
request["accept"] = json_response_content_type
|
|
466
|
-
request.
|
|
629
|
+
request.set_form_data({ "token_type_hint" => token_type_hint, "token" => token })
|
|
467
630
|
|
|
468
631
|
before_introspection_request(request)
|
|
469
632
|
response = http.request(request)
|
|
@@ -516,7 +679,7 @@ module Rodauth
|
|
|
516
679
|
end
|
|
517
680
|
|
|
518
681
|
def oauth_unique_id_generator
|
|
519
|
-
SecureRandom.
|
|
682
|
+
SecureRandom.urlsafe_base64(32)
|
|
520
683
|
end
|
|
521
684
|
|
|
522
685
|
def generate_token_hash(token)
|
|
@@ -537,7 +700,7 @@ module Rodauth
|
|
|
537
700
|
|
|
538
701
|
def generate_oauth_token(params = {}, should_generate_refresh_token = true)
|
|
539
702
|
create_params = {
|
|
540
|
-
oauth_grants_expires_in_column =>
|
|
703
|
+
oauth_grants_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_token_expires_in)
|
|
541
704
|
}.merge(params)
|
|
542
705
|
|
|
543
706
|
rescue_from_uniqueness_error do
|
|
@@ -597,26 +760,37 @@ module Rodauth
|
|
|
597
760
|
end
|
|
598
761
|
end
|
|
599
762
|
|
|
600
|
-
def oauth_token_by_token(token
|
|
763
|
+
def oauth_token_by_token(token)
|
|
764
|
+
ds = db[oauth_tokens_table]
|
|
765
|
+
|
|
601
766
|
ds = if oauth_tokens_token_hash_column
|
|
602
|
-
|
|
767
|
+
ds.where(oauth_tokens_token_hash_column => generate_token_hash(token))
|
|
603
768
|
else
|
|
604
|
-
|
|
769
|
+
ds.where(oauth_tokens_token_column => token)
|
|
605
770
|
end
|
|
606
771
|
|
|
607
772
|
ds.where(Sequel[oauth_tokens_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
|
|
608
773
|
.where(oauth_tokens_revoked_at_column => nil).first
|
|
609
774
|
end
|
|
610
775
|
|
|
611
|
-
def oauth_token_by_refresh_token(token,
|
|
776
|
+
def oauth_token_by_refresh_token(token, revoked: false)
|
|
777
|
+
ds = db[oauth_tokens_table]
|
|
778
|
+
#
|
|
779
|
+
# filter expired refresh tokens out.
|
|
780
|
+
# an expired refresh token is a token whose access token expired for a period longer than the
|
|
781
|
+
# refresh token expiration period.
|
|
782
|
+
#
|
|
783
|
+
ds = ds.where(Sequel.date_add(oauth_tokens_expires_in_column, seconds: oauth_refresh_token_expires_in) >= Sequel::CURRENT_TIMESTAMP)
|
|
784
|
+
|
|
612
785
|
ds = if oauth_tokens_refresh_token_hash_column
|
|
613
|
-
|
|
786
|
+
ds.where(oauth_tokens_refresh_token_hash_column => generate_token_hash(token))
|
|
614
787
|
else
|
|
615
|
-
|
|
788
|
+
ds.where(oauth_tokens_refresh_token_column => token)
|
|
616
789
|
end
|
|
617
790
|
|
|
618
|
-
ds.where(
|
|
619
|
-
|
|
791
|
+
ds = ds.where(oauth_tokens_revoked_at_column => nil) unless revoked
|
|
792
|
+
|
|
793
|
+
ds.first
|
|
620
794
|
end
|
|
621
795
|
|
|
622
796
|
def json_access_token_payload(oauth_token)
|
|
@@ -714,6 +888,9 @@ module Rodauth
|
|
|
714
888
|
end
|
|
715
889
|
redirect_response_error("invalid_scope") unless check_valid_scopes?
|
|
716
890
|
|
|
891
|
+
if (response_mode = param_or_nil("response_mode")) && response_mode != "form_post"
|
|
892
|
+
redirect_response_error("invalid_request")
|
|
893
|
+
end
|
|
717
894
|
validate_pkce_challenge_params if use_oauth_pkce?
|
|
718
895
|
end
|
|
719
896
|
|
|
@@ -731,7 +908,6 @@ module Rodauth
|
|
|
731
908
|
).count.zero?
|
|
732
909
|
|
|
733
910
|
# if there's a previous oauth grant for the params combo, it means that this user has approved before.
|
|
734
|
-
|
|
735
911
|
request.env["REQUEST_METHOD"] = "POST"
|
|
736
912
|
end
|
|
737
913
|
|
|
@@ -740,7 +916,7 @@ module Rodauth
|
|
|
740
916
|
oauth_grants_account_id_column => account_id,
|
|
741
917
|
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
|
742
918
|
oauth_grants_redirect_uri_column => redirect_uri,
|
|
743
|
-
oauth_grants_expires_in_column =>
|
|
919
|
+
oauth_grants_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_grant_expires_in),
|
|
744
920
|
oauth_grants_scopes_column => scopes.join(oauth_scope_separator)
|
|
745
921
|
)
|
|
746
922
|
|
|
@@ -766,28 +942,26 @@ module Rodauth
|
|
|
766
942
|
create_params[oauth_grants_code_column]
|
|
767
943
|
end
|
|
768
944
|
|
|
769
|
-
def do_authorize(
|
|
945
|
+
def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
|
|
770
946
|
case param("response_type")
|
|
771
947
|
when "token"
|
|
772
948
|
redirect_response_error("invalid_request") unless use_oauth_implicit_grant_type?
|
|
773
949
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
end
|
|
950
|
+
response_mode ||= "fragment"
|
|
951
|
+
response_params.replace(_do_authorize_token)
|
|
952
|
+
when "code"
|
|
953
|
+
response_mode ||= "query"
|
|
954
|
+
response_params.replace(_do_authorize_code)
|
|
955
|
+
when "none"
|
|
956
|
+
response_mode ||= "none"
|
|
957
|
+
when "", nil
|
|
958
|
+
response_mode ||= oauth_response_mode
|
|
959
|
+
response_params.replace(_do_authorize_code)
|
|
785
960
|
end
|
|
786
961
|
|
|
787
|
-
|
|
962
|
+
response_params["state"] = param("state") if param_or_nil("state")
|
|
788
963
|
|
|
789
|
-
|
|
790
|
-
redirect_url.fragment = fragment_params.join("&") unless fragment_params.empty?
|
|
964
|
+
[response_params, response_mode]
|
|
791
965
|
end
|
|
792
966
|
|
|
793
967
|
def _do_authorize_code
|
|
@@ -846,14 +1020,30 @@ module Rodauth
|
|
|
846
1020
|
}
|
|
847
1021
|
create_oauth_token_from_authorization_code(oauth_grant, create_params)
|
|
848
1022
|
when "refresh_token"
|
|
849
|
-
# fetch oauth token
|
|
850
|
-
oauth_token = oauth_token_by_refresh_token(param("refresh_token"))
|
|
851
|
-
|
|
852
|
-
|
|
1023
|
+
# fetch potentially revoked oauth token
|
|
1024
|
+
oauth_token = oauth_token_by_refresh_token(param("refresh_token"), revoked: true)
|
|
1025
|
+
|
|
1026
|
+
if !oauth_token
|
|
1027
|
+
redirect_response_error("invalid_grant")
|
|
1028
|
+
elsif oauth_token[oauth_tokens_revoked_at_column]
|
|
1029
|
+
if oauth_refresh_token_protection_policy == "rotation"
|
|
1030
|
+
# https://tools.ietf.org/html/draft-ietf-oauth-v2-1-00#section-6.1
|
|
1031
|
+
#
|
|
1032
|
+
# If a refresh token is compromised and subsequently used by both the attacker and the legitimate
|
|
1033
|
+
# client, one of them will present an invalidated refresh token, which will inform the authorization
|
|
1034
|
+
# server of the breach. The authorization server cannot determine which party submitted the invalid
|
|
1035
|
+
# refresh token, but it will revoke the active refresh token. This stops the attack at the cost of
|
|
1036
|
+
# forcing the legitimate client to obtain a fresh authorization grant.
|
|
1037
|
+
|
|
1038
|
+
db[oauth_tokens_table].where(oauth_tokens_oauth_token_id_column => oauth_token[oauth_tokens_id_column])
|
|
1039
|
+
.update(oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
|
|
1040
|
+
end
|
|
1041
|
+
redirect_response_error("invalid_grant")
|
|
1042
|
+
end
|
|
853
1043
|
|
|
854
1044
|
update_params = {
|
|
855
1045
|
oauth_tokens_oauth_application_id_column => oauth_token[oauth_grants_oauth_application_id_column],
|
|
856
|
-
oauth_tokens_expires_in_column =>
|
|
1046
|
+
oauth_tokens_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_token_expires_in)
|
|
857
1047
|
}
|
|
858
1048
|
create_oauth_token_from_token(oauth_token, update_params)
|
|
859
1049
|
end
|
|
@@ -885,6 +1075,7 @@ module Rodauth
|
|
|
885
1075
|
redirect_response_error("invalid_grant") unless token_from_application?(oauth_token, oauth_application)
|
|
886
1076
|
|
|
887
1077
|
rescue_from_uniqueness_error do
|
|
1078
|
+
oauth_tokens_ds = db[oauth_tokens_table]
|
|
888
1079
|
token = oauth_unique_id_generator
|
|
889
1080
|
|
|
890
1081
|
if oauth_tokens_token_hash_column
|
|
@@ -893,9 +1084,25 @@ module Rodauth
|
|
|
893
1084
|
update_params[oauth_tokens_token_column] = token
|
|
894
1085
|
end
|
|
895
1086
|
|
|
896
|
-
|
|
1087
|
+
oauth_token = if oauth_refresh_token_protection_policy == "rotation"
|
|
1088
|
+
insert_params = {
|
|
1089
|
+
**update_params,
|
|
1090
|
+
oauth_tokens_oauth_token_id_column => oauth_token[oauth_tokens_id_column],
|
|
1091
|
+
oauth_tokens_scopes_column => oauth_token[oauth_tokens_scopes_column]
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
# revoke the refresh token
|
|
1095
|
+
oauth_tokens_ds.where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
|
|
1096
|
+
.update(oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP)
|
|
1097
|
+
|
|
1098
|
+
insert_params[oauth_tokens_oauth_token_id_column] = oauth_token[oauth_tokens_id_column]
|
|
1099
|
+
__insert_and_return__(oauth_tokens_ds, oauth_tokens_id_column, insert_params)
|
|
1100
|
+
else
|
|
1101
|
+
# includes none
|
|
1102
|
+
ds = oauth_tokens_ds.where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
|
|
1103
|
+
__update_and_return__(ds, update_params)
|
|
1104
|
+
end
|
|
897
1105
|
|
|
898
|
-
oauth_token = __update_and_return__(ds, update_params)
|
|
899
1106
|
oauth_token[oauth_tokens_token_column] = token
|
|
900
1107
|
oauth_token
|
|
901
1108
|
end
|
|
@@ -1000,9 +1207,17 @@ module Rodauth
|
|
|
1000
1207
|
end
|
|
1001
1208
|
end
|
|
1002
1209
|
|
|
1003
|
-
def json_response_success(body)
|
|
1210
|
+
def json_response_success(body, cache = false)
|
|
1004
1211
|
response.status = 200
|
|
1005
1212
|
response["Content-Type"] ||= json_response_content_type
|
|
1213
|
+
if cache
|
|
1214
|
+
# defaulting to 1-day for everyone, for now at least
|
|
1215
|
+
max_age = 60 * 60 * 24
|
|
1216
|
+
response["Cache-Control"] = "private, max-age=#{max_age}"
|
|
1217
|
+
else
|
|
1218
|
+
response["Cache-Control"] = "no-store"
|
|
1219
|
+
response["Pragma"] = "no-cache"
|
|
1220
|
+
end
|
|
1006
1221
|
json_payload = _json_response_body(body)
|
|
1007
1222
|
response.write(json_payload)
|
|
1008
1223
|
request.halt
|
|
@@ -1122,7 +1337,7 @@ module Rodauth
|
|
|
1122
1337
|
issuer += "/#{path}" if path
|
|
1123
1338
|
|
|
1124
1339
|
responses_supported = %w[code]
|
|
1125
|
-
response_modes_supported = %w[query]
|
|
1340
|
+
response_modes_supported = %w[query form_post]
|
|
1126
1341
|
grant_types_supported = %w[authorization_code]
|
|
1127
1342
|
|
|
1128
1343
|
if use_oauth_implicit_grant_type?
|
|
@@ -1130,11 +1345,12 @@ module Rodauth
|
|
|
1130
1345
|
response_modes_supported << "fragment"
|
|
1131
1346
|
grant_types_supported << "implicit"
|
|
1132
1347
|
end
|
|
1348
|
+
|
|
1133
1349
|
{
|
|
1134
1350
|
issuer: issuer,
|
|
1135
1351
|
authorization_endpoint: authorize_url,
|
|
1136
1352
|
token_endpoint: token_url,
|
|
1137
|
-
registration_endpoint:
|
|
1353
|
+
registration_endpoint: route_url(oauth_applications_path),
|
|
1138
1354
|
scopes_supported: oauth_application_scopes,
|
|
1139
1355
|
response_types_supported: responses_supported,
|
|
1140
1356
|
response_modes_supported: response_modes_supported,
|
|
@@ -1151,121 +1367,5 @@ module Rodauth
|
|
|
1151
1367
|
code_challenge_methods_supported: (use_oauth_pkce? ? oauth_pkce_challenge_method : nil)
|
|
1152
1368
|
}
|
|
1153
1369
|
end
|
|
1154
|
-
|
|
1155
|
-
# /token
|
|
1156
|
-
route(:token) do |r|
|
|
1157
|
-
next unless is_authorization_server?
|
|
1158
|
-
|
|
1159
|
-
before_token_route
|
|
1160
|
-
require_oauth_application
|
|
1161
|
-
|
|
1162
|
-
r.post do
|
|
1163
|
-
catch_error do
|
|
1164
|
-
validate_oauth_token_params
|
|
1165
|
-
|
|
1166
|
-
oauth_token = nil
|
|
1167
|
-
transaction do
|
|
1168
|
-
before_token
|
|
1169
|
-
oauth_token = create_oauth_token
|
|
1170
|
-
end
|
|
1171
|
-
|
|
1172
|
-
json_response_success(json_access_token_payload(oauth_token))
|
|
1173
|
-
end
|
|
1174
|
-
|
|
1175
|
-
throw_json_response_error(invalid_oauth_response_status, "invalid_request")
|
|
1176
|
-
end
|
|
1177
|
-
end
|
|
1178
|
-
|
|
1179
|
-
# /introspect
|
|
1180
|
-
route(:introspect) do |r|
|
|
1181
|
-
next unless is_authorization_server?
|
|
1182
|
-
|
|
1183
|
-
before_introspect_route
|
|
1184
|
-
|
|
1185
|
-
r.post do
|
|
1186
|
-
catch_error do
|
|
1187
|
-
validate_oauth_introspect_params
|
|
1188
|
-
|
|
1189
|
-
before_introspect
|
|
1190
|
-
oauth_token = case param("token_type_hint")
|
|
1191
|
-
when "access_token"
|
|
1192
|
-
oauth_token_by_token(param("token"))
|
|
1193
|
-
when "refresh_token"
|
|
1194
|
-
oauth_token_by_refresh_token(param("token"))
|
|
1195
|
-
else
|
|
1196
|
-
oauth_token_by_token(param("token")) || oauth_token_by_refresh_token(param("token"))
|
|
1197
|
-
end
|
|
1198
|
-
|
|
1199
|
-
if oauth_application
|
|
1200
|
-
redirect_response_error("invalid_request") if oauth_token && !token_from_application?(oauth_token, oauth_application)
|
|
1201
|
-
elsif oauth_token
|
|
1202
|
-
@oauth_application = db[oauth_applications_table].where(oauth_applications_id_column =>
|
|
1203
|
-
oauth_token[oauth_tokens_oauth_application_id_column]).first
|
|
1204
|
-
end
|
|
1205
|
-
|
|
1206
|
-
json_response_success(json_token_introspect_payload(oauth_token))
|
|
1207
|
-
end
|
|
1208
|
-
|
|
1209
|
-
throw_json_response_error(invalid_oauth_response_status, "invalid_request")
|
|
1210
|
-
end
|
|
1211
|
-
end
|
|
1212
|
-
|
|
1213
|
-
# /revoke
|
|
1214
|
-
route(:revoke) do |r|
|
|
1215
|
-
next unless is_authorization_server?
|
|
1216
|
-
|
|
1217
|
-
before_revoke_route
|
|
1218
|
-
require_oauth_application
|
|
1219
|
-
|
|
1220
|
-
r.post do
|
|
1221
|
-
catch_error do
|
|
1222
|
-
validate_oauth_revoke_params
|
|
1223
|
-
|
|
1224
|
-
oauth_token = nil
|
|
1225
|
-
transaction do
|
|
1226
|
-
before_revoke
|
|
1227
|
-
oauth_token = revoke_oauth_token
|
|
1228
|
-
after_revoke
|
|
1229
|
-
end
|
|
1230
|
-
|
|
1231
|
-
if accepts_json?
|
|
1232
|
-
json_response_success \
|
|
1233
|
-
"token" => oauth_token[oauth_tokens_token_column],
|
|
1234
|
-
"refresh_token" => oauth_token[oauth_tokens_refresh_token_column],
|
|
1235
|
-
"revoked_at" => oauth_token[oauth_tokens_revoked_at_column]
|
|
1236
|
-
else
|
|
1237
|
-
set_notice_flash revoke_oauth_token_notice_flash
|
|
1238
|
-
redirect request.referer || "/"
|
|
1239
|
-
end
|
|
1240
|
-
end
|
|
1241
|
-
|
|
1242
|
-
redirect_response_error("invalid_request", request.referer || "/")
|
|
1243
|
-
end
|
|
1244
|
-
end
|
|
1245
|
-
|
|
1246
|
-
# /authorize
|
|
1247
|
-
route(:authorize) do |r|
|
|
1248
|
-
next unless is_authorization_server?
|
|
1249
|
-
|
|
1250
|
-
before_authorize_route
|
|
1251
|
-
require_authorizable_account
|
|
1252
|
-
|
|
1253
|
-
validate_oauth_grant_params
|
|
1254
|
-
try_approval_prompt if use_oauth_access_type? && request.get?
|
|
1255
|
-
|
|
1256
|
-
r.get do
|
|
1257
|
-
authorize_view
|
|
1258
|
-
end
|
|
1259
|
-
|
|
1260
|
-
r.post do
|
|
1261
|
-
redirect_url = URI.parse(redirect_uri)
|
|
1262
|
-
|
|
1263
|
-
transaction do
|
|
1264
|
-
before_authorize
|
|
1265
|
-
do_authorize(redirect_url)
|
|
1266
|
-
end
|
|
1267
|
-
redirect(redirect_url.to_s)
|
|
1268
|
-
end
|
|
1269
|
-
end
|
|
1270
1370
|
end
|
|
1271
1371
|
end
|