rodauth-oauth 0.3.0 → 0.4.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 +21 -2
- data/lib/rodauth/features/oauth.rb +52 -19
- data/lib/rodauth/features/oauth_http_mac.rb +6 -10
- data/lib/rodauth/features/oidc.rb +43 -32
- data/lib/rodauth/oauth/ttl_store.rb +1 -1
- data/lib/rodauth/oauth/version.rb +1 -1
- data/templates/authorize.str +1 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b09ecfc1d3a8ee0f5b890620baa14ca6d847362bf38dd158e02bd2c8ebfc204e
|
4
|
+
data.tar.gz: 89f0e82d7721f7ee175b1c53b7b3e0cc534e6983fe37dfc02e433df77b58225d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d00a178f561ddecacff0587e1120b68bb22cd10b76b106b00f41167ba9c8bd8b2b8958fd629588924e502be8c947a81d3722102038cd329c006f4b4daf6efada
|
7
|
+
data.tar.gz: 328542ba8ce7ef8e8f605056a9a8cbf6599136232d93f7246b13fe037ebc07225e2051b3ee454eb90ec4ae480e2b493d662d1cbbcd0ae5cc7e57a0ff29b10696
|
data/CHANGELOG.md
CHANGED
@@ -2,18 +2,37 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
### 0.4.0
|
6
|
+
|
7
|
+
### Features
|
8
|
+
|
9
|
+
* 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.
|
10
|
+
|
11
|
+
* 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.
|
12
|
+
|
13
|
+
|
14
|
+
### Improvements
|
15
|
+
|
16
|
+
* 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.
|
17
|
+
|
18
|
+
### Bugfixes
|
19
|
+
|
20
|
+
* 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;
|
21
|
+
* rails tests were silently not running in CI;
|
22
|
+
* 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;
|
23
|
+
|
5
24
|
### 0.3.0
|
6
25
|
|
7
26
|
#### Features
|
8
27
|
|
9
|
-
* `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
|
28
|
+
* `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.
|
10
29
|
|
11
30
|
#### Improvements
|
12
31
|
|
13
32
|
|
14
33
|
* 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).
|
15
34
|
|
16
|
-
* Refresh Tokens are now
|
35
|
+
* 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.
|
17
36
|
|
18
37
|
#### Bugfixes
|
19
38
|
|
@@ -84,6 +84,7 @@ module Rodauth
|
|
84
84
|
|
85
85
|
auth_value_method :oauth_require_pkce, false
|
86
86
|
auth_value_method :oauth_pkce_challenge_method, "S256"
|
87
|
+
auth_value_method :oauth_response_mode, "query"
|
87
88
|
|
88
89
|
auth_value_method :oauth_valid_uri_schemes, %w[https]
|
89
90
|
|
@@ -100,6 +101,7 @@ module Rodauth
|
|
100
101
|
button "Register", "oauth_application"
|
101
102
|
button "Authorize", "oauth_authorize"
|
102
103
|
button "Revoke", "oauth_token_revoke"
|
104
|
+
button "Back to Client Application", "oauth_authorize_post"
|
103
105
|
|
104
106
|
# OAuth Token
|
105
107
|
auth_value_method :oauth_tokens_path, "oauth-tokens"
|
@@ -313,11 +315,41 @@ module Rodauth
|
|
313
315
|
r.post do
|
314
316
|
redirect_url = URI.parse(redirect_uri)
|
315
317
|
|
316
|
-
transaction do
|
318
|
+
params, mode = transaction do
|
317
319
|
before_authorize
|
318
|
-
do_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)
|
319
352
|
end
|
320
|
-
redirect(redirect_url.to_s)
|
321
353
|
end
|
322
354
|
end
|
323
355
|
|
@@ -848,6 +880,9 @@ module Rodauth
|
|
848
880
|
end
|
849
881
|
redirect_response_error("invalid_scope") unless check_valid_scopes?
|
850
882
|
|
883
|
+
if (response_mode = param_or_nil("response_mode")) && response_mode != "form_post"
|
884
|
+
redirect_response_error("invalid_request")
|
885
|
+
end
|
851
886
|
validate_pkce_challenge_params if use_oauth_pkce?
|
852
887
|
end
|
853
888
|
|
@@ -899,28 +934,26 @@ module Rodauth
|
|
899
934
|
create_params[oauth_grants_code_column]
|
900
935
|
end
|
901
936
|
|
902
|
-
def do_authorize(
|
937
|
+
def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
|
903
938
|
case param("response_type")
|
904
939
|
when "token"
|
905
940
|
redirect_response_error("invalid_request") unless use_oauth_implicit_grant_type?
|
906
941
|
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
end
|
942
|
+
response_mode ||= "fragment"
|
943
|
+
response_params.replace(_do_authorize_token)
|
944
|
+
when "code"
|
945
|
+
response_mode ||= "query"
|
946
|
+
response_params.replace(_do_authorize_code)
|
947
|
+
when "none"
|
948
|
+
response_mode ||= "none"
|
949
|
+
when "", nil
|
950
|
+
response_mode ||= oauth_response_mode
|
951
|
+
response_params.replace(_do_authorize_code)
|
918
952
|
end
|
919
953
|
|
920
|
-
|
954
|
+
response_params["state"] = param("state") if param_or_nil("state")
|
921
955
|
|
922
|
-
|
923
|
-
redirect_url.fragment = fragment_params.join("&") unless fragment_params.empty?
|
956
|
+
[response_params, response_mode]
|
924
957
|
end
|
925
958
|
|
926
959
|
def _do_authorize_code
|
@@ -1296,7 +1329,7 @@ module Rodauth
|
|
1296
1329
|
issuer += "/#{path}" if path
|
1297
1330
|
|
1298
1331
|
responses_supported = %w[code]
|
1299
|
-
response_modes_supported = %w[query]
|
1332
|
+
response_modes_supported = %w[query form_post]
|
1300
1333
|
grant_types_supported = %w[authorization_code]
|
1301
1334
|
|
1302
1335
|
if use_oauth_implicit_grant_type?
|
@@ -8,20 +8,16 @@ module Rodauth
|
|
8
8
|
def delete_suffix(suffix)
|
9
9
|
suffix = suffix.to_s
|
10
10
|
len = suffix.length
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
dup
|
15
|
-
end
|
11
|
+
return dup unless len.positive? && index(suffix, -len)
|
12
|
+
|
13
|
+
self[0...-len]
|
16
14
|
end
|
17
15
|
|
18
16
|
def delete_prefix(prefix)
|
19
17
|
prefix = prefix.to_s
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
dup
|
24
|
-
end
|
18
|
+
return dup unless rindex(prefix, 0)
|
19
|
+
|
20
|
+
self[prefix.length..-1]
|
25
21
|
end
|
26
22
|
end
|
27
23
|
end
|
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
module Rodauth
|
4
4
|
Feature.define(:oidc) do
|
5
|
+
# https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
|
5
6
|
OIDC_SCOPES_MAP = {
|
6
7
|
"profile" => %i[name family_name given_name middle_name nickname preferred_username
|
7
8
|
profile picture website gender birthdate zoneinfo locale updated_at].freeze,
|
8
9
|
"email" => %i[email email_verified].freeze,
|
9
|
-
"address" => %i[
|
10
|
+
"address" => %i[formatted street_address locality region postal_code country].freeze,
|
10
11
|
"phone" => %i[phone_number phone_number_verified].freeze
|
11
12
|
}.freeze
|
12
13
|
|
@@ -74,7 +75,7 @@ module Rodauth
|
|
74
75
|
auth_value_method :oauth_prompt_login_cookie_options, {}.freeze
|
75
76
|
auth_value_method :oauth_prompt_login_interval, 5 * 60 * 60 # 5 minutes
|
76
77
|
|
77
|
-
auth_value_methods(:get_oidc_param)
|
78
|
+
auth_value_methods(:get_oidc_param, :get_additional_param)
|
78
79
|
|
79
80
|
# /userinfo
|
80
81
|
route(:userinfo) do |r|
|
@@ -245,8 +246,11 @@ module Rodauth
|
|
245
246
|
oauth_token[:id_token] = jwt_encode(id_token_claims)
|
246
247
|
end
|
247
248
|
|
249
|
+
# aka fill_with_standard_claims
|
248
250
|
def fill_with_account_claims(claims, account, scopes)
|
249
|
-
|
251
|
+
scopes_by_claim = scopes.each_with_object({}) do |scope, by_oidc|
|
252
|
+
next if scope == "openid"
|
253
|
+
|
250
254
|
oidc, param = scope.split(".", 2)
|
251
255
|
|
252
256
|
by_oidc[oidc] ||= []
|
@@ -254,21 +258,33 @@ module Rodauth
|
|
254
258
|
by_oidc[oidc] << param.to_sym if param
|
255
259
|
end
|
256
260
|
|
257
|
-
oidc_scopes =
|
258
|
-
|
259
|
-
return if oidc_scopes.empty?
|
261
|
+
oidc_scopes, additional_scopes = scopes_by_claim.keys.partition { |key| OIDC_SCOPES_MAP.key?(key) }
|
260
262
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
263
|
+
unless oidc_scopes.empty?
|
264
|
+
if respond_to?(:get_oidc_param)
|
265
|
+
oidc_scopes.each do |scope|
|
266
|
+
scope_claims = claims
|
267
|
+
params = scopes_by_claim[scope]
|
268
|
+
params = params.empty? ? OIDC_SCOPES_MAP[scope] : (OIDC_SCOPES_MAP[scope] & params)
|
265
269
|
|
266
|
-
|
267
|
-
|
270
|
+
scope_claims = (claims["address"] = {}) if scope == "address"
|
271
|
+
params.each do |param|
|
272
|
+
scope_claims[param] = __send__(:get_oidc_param, account, param)
|
273
|
+
end
|
268
274
|
end
|
275
|
+
else
|
276
|
+
warn "`get_oidc_param(account, claim)` must be implemented to use oidc scopes."
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
return if additional_scopes.empty?
|
281
|
+
|
282
|
+
if respond_to?(:get_additional_param)
|
283
|
+
additional_scopes.each do |scope|
|
284
|
+
claims[scope] = __send__(:get_additional_param, account, scope.to_sym)
|
269
285
|
end
|
270
286
|
else
|
271
|
-
warn "`
|
287
|
+
warn "`get_additional_param(account, claim)` must be implemented to use oidc scopes."
|
272
288
|
end
|
273
289
|
end
|
274
290
|
|
@@ -290,33 +306,27 @@ module Rodauth
|
|
290
306
|
end
|
291
307
|
end
|
292
308
|
|
293
|
-
def do_authorize(
|
309
|
+
def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
|
294
310
|
return super unless use_oauth_implicit_grant_type?
|
295
311
|
|
296
312
|
case param("response_type")
|
297
313
|
when "id_token"
|
298
|
-
|
314
|
+
response_params.replace(_do_authorize_id_token)
|
299
315
|
when "code token"
|
300
316
|
redirect_response_error("invalid_request") unless use_oauth_implicit_grant_type?
|
301
317
|
|
302
|
-
|
303
|
-
|
304
|
-
fragment_params.replace(params.map { |k, v| "#{k}=#{v}" })
|
318
|
+
response_params.replace(_do_authorize_code.merge(_do_authorize_token))
|
305
319
|
when "code id_token"
|
306
|
-
|
307
|
-
|
308
|
-
fragment_params.replace(params.map { |k, v| "#{k}=#{v}" })
|
320
|
+
response_params.replace(_do_authorize_code.merge(_do_authorize_id_token))
|
309
321
|
when "id_token token"
|
310
|
-
|
311
|
-
|
312
|
-
fragment_params.replace(params.map { |k, v| "#{k}=#{v}" })
|
322
|
+
response_params.replace(_do_authorize_id_token.merge(_do_authorize_token))
|
313
323
|
when "code id_token token"
|
314
|
-
params = _do_authorize_code.merge(_do_authorize_id_token).merge(_do_authorize_token)
|
315
324
|
|
316
|
-
|
325
|
+
response_params.replace(_do_authorize_code.merge(_do_authorize_id_token).merge(_do_authorize_token))
|
317
326
|
end
|
327
|
+
response_mode ||= "fragment" unless response_params.empty?
|
318
328
|
|
319
|
-
super(
|
329
|
+
super(response_params, response_mode)
|
320
330
|
end
|
321
331
|
|
322
332
|
def _do_authorize_id_token
|
@@ -351,13 +361,14 @@ module Rodauth
|
|
351
361
|
|
352
362
|
scope_claims.unshift("auth_time") if last_account_login_at
|
353
363
|
|
364
|
+
response_types_supported = metadata[:response_types_supported]
|
365
|
+
if use_oauth_implicit_grant_type?
|
366
|
+
response_types_supported += ["none", "id_token", "code token", "code id_token", "id_token token", "code id_token token"]
|
367
|
+
end
|
368
|
+
|
354
369
|
metadata.merge(
|
355
370
|
userinfo_endpoint: userinfo_url,
|
356
|
-
response_types_supported:
|
357
|
-
["none", "id_token", "code token", "code id_token", "id_token token", "code id_token token"],
|
358
|
-
response_modes_supported: %w[query fragment],
|
359
|
-
grant_types_supported: %w[authorization_code implicit],
|
360
|
-
|
371
|
+
response_types_supported: response_types_supported,
|
361
372
|
subject_types_supported: [oauth_jwt_subject_type],
|
362
373
|
|
363
374
|
id_token_signing_alg_values_supported: metadata[:token_endpoint_auth_signing_alg_values_supported],
|
data/templates/authorize.str
CHANGED
@@ -20,6 +20,7 @@
|
|
20
20
|
|
21
21
|
#{"<input type=\"hidden\" name=\"access_type\" value=\"#{rodauth.param("access_type")}\"/>" if rodauth.param_or_nil("access_type")}
|
22
22
|
#{"<input type=\"hidden\" name=\"response_type\" value=\"#{rodauth.param("response_type")}\"/>" if rodauth.param_or_nil("response_type")}
|
23
|
+
#{"<input type=\"hidden\" name=\"response_mode\" value=\"#{rodauth.param("response_mode")}\"/>" if rodauth.param_or_nil("response_mode")}
|
23
24
|
#{"<input type=\"hidden\" name=\"state\" value=\"#{rodauth.param("state")}\"/>" if rodauth.param_or_nil("state")}
|
24
25
|
#{"<input type=\"hidden\" name=\"nonce\" value=\"#{rodauth.param("nonce")}\"/>" if rodauth.param_or_nil("nonce")}
|
25
26
|
#{"<input type=\"hidden\" name=\"redirect_uri\" value=\"#{rodauth.redirect_uri}\"/>" if rodauth.param_or_nil("redirect_uri")}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodauth-oauth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Cardoso
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Implementation of the OAuth 2.0 protocol on top of rodauth.
|
14
14
|
email:
|
@@ -71,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
71
|
- !ruby/object:Gem::Version
|
72
72
|
version: '0'
|
73
73
|
requirements: []
|
74
|
-
rubygems_version: 3.1.
|
74
|
+
rubygems_version: 3.1.4
|
75
75
|
signing_key:
|
76
76
|
specification_version: 4
|
77
77
|
summary: Implementation of the OAuth 2.0 protocol on top of rodauth.
|