rodauth-oauth 0.9.3 → 0.10.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/README.md +5 -3
- data/doc/release_notes/0_10_0.md +100 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +27 -3
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +4 -0
- data/lib/rodauth/features/oauth_base.rb +5 -0
- data/lib/rodauth/features/oauth_jwt.rb +59 -14
- data/lib/rodauth/features/oauth_resource_indicators.rb +153 -0
- data/lib/rodauth/features/oidc.rb +120 -14
- data/lib/rodauth/oauth/version.rb +1 -1
- data/locales/en.yml +2 -1
- data/locales/pt.yml +57 -0
- data/templates/authorize.str +13 -3
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f9b68ff6e15b91128db72a07fa91b86afb70352f9582fa8c27e7abfe3c0dc17c
|
|
4
|
+
data.tar.gz: 1c35b67bc10619c8de31cbcef514636e7975307a0cfc02585ae10ec97de74be1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2cf0e357529093b45834697c54bae5eaf17419885e04ccba279d18e65464aa8d8fb2e49da09dd5c96c83331e0f60915e993af5dfc7decfff4c8752b5401dfe8a
|
|
7
|
+
data.tar.gz: 784d5184526ff8dcbc3c112eb58311705baf82e1bf17b40b87cd55dc43f0b06cc6bf7c2cb71f67caf315008b14d3fe4b9fe8eea991a0475586bf4effb0d77ed3
|
data/README.md
CHANGED
|
@@ -21,14 +21,16 @@ This gem implements the following RFCs and features of OAuth:
|
|
|
21
21
|
* `oauth_token_introspection` - [Token introspection](https://tools.ietf.org/html/rfc7662);
|
|
22
22
|
* [Authorization Server Metadata](https://tools.ietf.org/html/rfc8414);
|
|
23
23
|
* `oauth_pkce` - [PKCE](https://tools.ietf.org/html/rfc7636);
|
|
24
|
-
* Access Type (Token refresh online and offline);
|
|
25
24
|
* `oauth_jwt` - [JWT Access Tokens](https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-07);
|
|
25
|
+
* Supports [JWT Secured Authorization Request](https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-20);
|
|
26
|
+
* `oauth_resource_indicators` - [Resource Indicators](https://datatracker.ietf.org/doc/html/rfc8707);
|
|
27
|
+
* Access Type (Token refresh online and offline);
|
|
26
28
|
* `oauth_http_mac` - [MAC Authentication Scheme](https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-02);
|
|
27
29
|
* `oauth_assertion_base` - [Assertion Framework](https://datatracker.ietf.org/doc/html/rfc7521);
|
|
28
30
|
* `oauth_saml_bearer_grant` - [SAML 2.0 Bearer Assertion](https://datatracker.ietf.org/doc/html/rfc7522);
|
|
29
31
|
* `oauth_jwt_bearer_grant` - [JWT Bearer Assertion](https://datatracker.ietf.org/doc/html/rfc7523);
|
|
30
|
-
|
|
31
|
-
* [Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591);
|
|
32
|
+
|
|
33
|
+
* `oauth_dynamic_client_registration` - [Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591);
|
|
32
34
|
* OAuth application and token management dashboards;
|
|
33
35
|
|
|
34
36
|
It also implements the [OpenID Connect layer](https://openid.net/connect/) (via the `openid` feature) on top of the OAuth features it provides, including:
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
## 0.10.0 (10/06/2022)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
#### Resource Indicators
|
|
6
|
+
|
|
7
|
+
RFC: https://datatracker.ietf.org/doc/html/rfc8707
|
|
8
|
+
|
|
9
|
+
`rodauth-oauth` now supports Resource Indicators, via the optional `:oauth_resource_indicators` feature.
|
|
10
|
+
|
|
11
|
+
#### JWT: extra options
|
|
12
|
+
|
|
13
|
+
The following extra option values were added:
|
|
14
|
+
|
|
15
|
+
* `oauth_jwt_jwe_keys`
|
|
16
|
+
* `oauth_jwt_public_keys`
|
|
17
|
+
* `oauth_jwt_jwe_public_keys`
|
|
18
|
+
|
|
19
|
+
`:oauth_jwt_jwe_keys` should be used to store all provider combos of encryption keys, indexed by an algo/method tuple:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
oauth_jwt_jwe_keys { { %w[RSA-OAEP A128CBC-HS256] => key } }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The first element of the hash should indicate the preferred encryption mode, when no combination is specifically requested.
|
|
26
|
+
|
|
27
|
+
It should be considered the most future-proof way of declaring JWE keys, and support for `oauth_jwt_jwe_key` and friends should be soon deprecated.
|
|
28
|
+
|
|
29
|
+
Both `oauth_jwt_public_keys` and `oauth_jwt_jwe_public_keys` provide a way to declare multiple keys to be exposed as the provider JWKs in the `/jwks` endpoint.
|
|
30
|
+
|
|
31
|
+
### Improvements
|
|
32
|
+
|
|
33
|
+
* Added translations for portuguese.
|
|
34
|
+
|
|
35
|
+
#### OpenID Connect improvements
|
|
36
|
+
|
|
37
|
+
* The `:oidc` feature now depends on `rodauth`'s [account_expiration](http://rodauth.jeremyevans.net/rdoc/files/doc/account_expiration_rdoc.html) feature.
|
|
38
|
+
|
|
39
|
+
Although a more-involved-somewhat-breaking change, it was required in order to keep track of account login event timestamps, necessary for correct `"auth_time"` calculation (see the first bugfix mention for more details, and Breaking Changes for migration path).
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
* Support for the `ui_locales` parameter was added. This feature depends on the `:i18n` feature provided by [rodauth-i18n](https://github.com/janko/rodauth-i18n).
|
|
43
|
+
* Support for the `claims_locales` parameter was added, in that the `get_oidc_param` and `get_additional_param`, when accepting a 3rd parameter, will be passed a locale code:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
# given "claims_locales=en pt"
|
|
47
|
+
|
|
48
|
+
get_oidc_param { |account, param, locale| }
|
|
49
|
+
# will be called twice for the same param, one with locale as "en", another as "pt"
|
|
50
|
+
|
|
51
|
+
get_oidc_param { |account, param| }
|
|
52
|
+
# will be called once without locale
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
* Support for `max_age` parameter was added.
|
|
56
|
+
|
|
57
|
+
* Support for `acr_values` parameter was added.
|
|
58
|
+
|
|
59
|
+
When "phr", and a `rodauth` 2-factor feature (like [otp](http://rodauth.jeremyevans.net/rdoc/files/doc/otp_rdoc.html)) is enabled, the user will be requested for 2-factor authentication before performing the OpenID Authorization Request.
|
|
60
|
+
|
|
61
|
+
When "phrh", and `rodauth`'s [webauthn_login](http://rodauth.jeremyevans.net/rdoc/files/doc/webauthn_login_rdoc.html) feature is enabled, the user will be requested for WebAuthn authentication before performing the OpenID Authorization Request.
|
|
62
|
+
|
|
63
|
+
Any other acr values are considered provider-specific, and the `require_acr_value(acr_value)` option should be provided to deal with it (it'll be called after authentication is ensured and before the authorization request is processed).
|
|
64
|
+
|
|
65
|
+
### Bugfixes
|
|
66
|
+
|
|
67
|
+
* reverted the `"auth_time"` calculation "fix" introduced in 0.9.3, which broke compliance with the RFC (the implementation prior to that was also broken, hence why `"account_expiration"` plugin was introduced as a dependency).
|
|
68
|
+
|
|
69
|
+
### Breaking Changes
|
|
70
|
+
|
|
71
|
+
As you read already, the `"account_expiration"` feature is now required by default by `"oidc"`. In order to migrate to it, here's a suggested strategy:
|
|
72
|
+
|
|
73
|
+
1. Add the relevant database tables
|
|
74
|
+
|
|
75
|
+
Add a migration looking roughly like this:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
create_table(:account_activity_times) do
|
|
79
|
+
foreign_key :id, :accounts, primary_key: true, type: Integer
|
|
80
|
+
DateTime :last_activity_at, null: false
|
|
81
|
+
DateTime :last_login_at, null: false
|
|
82
|
+
DateTime :expired_at
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
2. Update and deploy `rodauth-oauth` 0.10.0
|
|
87
|
+
|
|
88
|
+
(Nothing required beyond `enable :oidc`.)
|
|
89
|
+
|
|
90
|
+
3. Set `:last_login_at` to a value.
|
|
91
|
+
|
|
92
|
+
Like now. You can , for example, run this SQL:
|
|
93
|
+
|
|
94
|
+
```sql
|
|
95
|
+
UPDATE account_activity_times SET last_login_at = CURRENT_TIMESTAMP;
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
That's it, nothing fancy or accurate. Yes, the `last_login_at` is wrong, but as sessions expire, it should go back to normal.
|
|
@@ -34,13 +34,37 @@
|
|
|
34
34
|
</div>
|
|
35
35
|
<% end %>
|
|
36
36
|
<%= hidden_field_tag :client_id, params[:client_id] %>
|
|
37
|
-
<% %i[access_type response_type state
|
|
37
|
+
<% %i[access_type response_type response_mode state redirect_uri].each do |oauth_param| %>
|
|
38
38
|
<% if params[oauth_param] %>
|
|
39
39
|
<%= hidden_field_tag oauth_param, params[oauth_param] %>
|
|
40
40
|
<% end %>
|
|
41
41
|
<% end %>
|
|
42
|
-
<% if
|
|
43
|
-
|
|
42
|
+
<% if rodauth.features.include?(:oauth_resource_indicators) && rodauth.resource_indicators %>
|
|
43
|
+
<% rodauth.resource_indicators.each do |resource| %>
|
|
44
|
+
<%= hidden_field_tag "resource", resource %>
|
|
45
|
+
<% end %>
|
|
46
|
+
<% end %>
|
|
47
|
+
<% if rodauth.features.include?(:oauth_pkce) %>
|
|
48
|
+
<% if params[:code_challenge] %>
|
|
49
|
+
<%= hidden_field_tag :code_challenge, params[:code_challenge] %>
|
|
50
|
+
<% end %>
|
|
51
|
+
<% if params[:code_challenge_method] %>
|
|
52
|
+
<%= hidden_field_tag :code_challenge_method, params[:code_challenge_method] %>
|
|
53
|
+
<% end %>
|
|
54
|
+
<% end %>
|
|
55
|
+
<% if rodauth.features.include?(:oidc) %>
|
|
56
|
+
<% if params[:nonce] %>
|
|
57
|
+
<%= hidden_field_tag :nonce, params[:nonce] %>
|
|
58
|
+
<% end %>
|
|
59
|
+
<% if params[:ui_locales] %>
|
|
60
|
+
<%= hidden_field_tag :ui_locales, params[:ui_locales] %>
|
|
61
|
+
<% end %>
|
|
62
|
+
<% if params[:claims_locales] %>
|
|
63
|
+
<%= hidden_field_tag :claims_locales, params[:claims_locales] %>
|
|
64
|
+
<% end %>
|
|
65
|
+
<% if params[:acr_values] %>
|
|
66
|
+
<%= hidden_field_tag :acr, params[:acr_values] %>
|
|
67
|
+
<% end %>
|
|
44
68
|
<% end %>
|
|
45
69
|
</div>
|
|
46
70
|
<p class="text-center">
|
|
@@ -52,6 +52,8 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
|
|
|
52
52
|
# device code grant
|
|
53
53
|
# t.string :user_code, null: true, unique: true
|
|
54
54
|
# t.datetime :last_polled_at, null: true
|
|
55
|
+
# when using :oauth_resource_indicators feature
|
|
56
|
+
# t.string :resource
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
create_table :oauth_tokens do |t|
|
|
@@ -78,6 +80,8 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
|
|
|
78
80
|
# uncomment to use OIDC nonce
|
|
79
81
|
# t.string :nonce
|
|
80
82
|
# t.datetime :auth_time
|
|
83
|
+
# when using :oauth_resource_indicators feature
|
|
84
|
+
# t.string :resource
|
|
81
85
|
end
|
|
82
86
|
end
|
|
83
87
|
end
|
|
@@ -425,6 +425,11 @@ module Rodauth
|
|
|
425
425
|
oauth_tokens_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_token_expires_in)
|
|
426
426
|
}.merge(params)
|
|
427
427
|
|
|
428
|
+
if create_params[oauth_tokens_scopes_column].is_a?(Array)
|
|
429
|
+
create_params[oauth_tokens_scopes_column] =
|
|
430
|
+
create_params[oauth_tokens_scopes_column].join(" ")
|
|
431
|
+
end
|
|
432
|
+
|
|
428
433
|
rescue_from_uniqueness_error do
|
|
429
434
|
access_token = _generate_access_token(create_params)
|
|
430
435
|
refresh_token = _generate_refresh_token(create_params) if should_generate_refresh_token
|
|
@@ -44,10 +44,13 @@ module Rodauth
|
|
|
44
44
|
|
|
45
45
|
auth_value_method :oauth_jwt_keys, {}
|
|
46
46
|
auth_value_method :oauth_jwt_key, nil
|
|
47
|
+
auth_value_method :oauth_jwt_public_keys, {}
|
|
47
48
|
auth_value_method :oauth_jwt_public_key, nil
|
|
48
49
|
auth_value_method :oauth_jwt_algorithm, "RS256"
|
|
49
50
|
|
|
51
|
+
auth_value_method :oauth_jwt_jwe_keys, {}
|
|
50
52
|
auth_value_method :oauth_jwt_jwe_key, nil
|
|
53
|
+
auth_value_method :oauth_jwt_jwe_public_keys, {}
|
|
51
54
|
auth_value_method :oauth_jwt_jwe_public_key, nil
|
|
52
55
|
auth_value_method :oauth_jwt_jwe_algorithm, nil
|
|
53
56
|
auth_value_method :oauth_jwt_jwe_encryption_method, nil
|
|
@@ -407,10 +410,11 @@ module Rodauth
|
|
|
407
410
|
|
|
408
411
|
def jwt_encode(payload,
|
|
409
412
|
jwks: nil,
|
|
410
|
-
jwe_key: oauth_jwt_jwe_public_key || oauth_jwt_jwe_key,
|
|
411
|
-
signing_algorithm: oauth_jwt_algorithm,
|
|
412
413
|
encryption_algorithm: oauth_jwt_jwe_algorithm,
|
|
413
|
-
encryption_method: oauth_jwt_jwe_encryption_method
|
|
414
|
+
encryption_method: oauth_jwt_jwe_encryption_method,
|
|
415
|
+
jwe_key: oauth_jwt_jwe_keys[[encryption_algorithm,
|
|
416
|
+
encryption_method]] || oauth_jwt_jwe_public_key || oauth_jwt_jwe_key,
|
|
417
|
+
signing_algorithm: oauth_jwt_algorithm || oauth_jwt_keys.keys.first)
|
|
414
418
|
payload[:jti] = generate_jti(payload)
|
|
415
419
|
jwt = JSON::JWT.new(payload)
|
|
416
420
|
|
|
@@ -427,6 +431,7 @@ module Rodauth
|
|
|
427
431
|
jwe = jwt.encrypt(jwk, encryption_algorithm.to_sym, encryption_method.to_sym)
|
|
428
432
|
jwe.to_s
|
|
429
433
|
elsif jwe_key
|
|
434
|
+
jwe_key = jwe_key.first if jwe_key.is_a?(Array)
|
|
430
435
|
algorithm = encryption_algorithm.to_sym if encryption_algorithm
|
|
431
436
|
meth = encryption_method.to_sym if encryption_method
|
|
432
437
|
jwt.encrypt(jwe_key, algorithm, meth)
|
|
@@ -438,18 +443,23 @@ module Rodauth
|
|
|
438
443
|
def jwt_decode(
|
|
439
444
|
token,
|
|
440
445
|
jwks: nil,
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
jwe_key: oauth_jwt_jwe_key,
|
|
446
|
+
jws_algorithm: oauth_jwt_algorithm || oauth_jwt_public_key.keys.first || oauth_jwt_keys.keys.first,
|
|
447
|
+
jws_key: oauth_jwt_public_key || oauth_jwt_keys[jws_algorithm] || _jwt_key,
|
|
444
448
|
jws_encryption_algorithm: oauth_jwt_jwe_algorithm,
|
|
445
449
|
jws_encryption_method: oauth_jwt_jwe_encryption_method,
|
|
450
|
+
jwe_key: oauth_jwt_jwe_keys[[jws_encryption_algorithm, jws_encryption_method]] || oauth_jwt_jwe_key,
|
|
446
451
|
verify_claims: true,
|
|
447
452
|
verify_jti: true,
|
|
448
453
|
verify_iss: true,
|
|
449
454
|
verify_aud: false,
|
|
450
455
|
**
|
|
451
456
|
)
|
|
452
|
-
|
|
457
|
+
jws_key = jws_key.first if jws_key.is_a?(Array)
|
|
458
|
+
|
|
459
|
+
if jwe_key
|
|
460
|
+
jwe_key = jwe_key.first if jwe_key.is_a?(Array)
|
|
461
|
+
token = JSON::JWT.decode(token, jwe_key).plain_text
|
|
462
|
+
end
|
|
453
463
|
|
|
454
464
|
claims = if is_authorization_server?
|
|
455
465
|
if oauth_jwt_legacy_public_key
|
|
@@ -487,6 +497,21 @@ module Rodauth
|
|
|
487
497
|
|
|
488
498
|
def jwks_set
|
|
489
499
|
@jwks_set ||= [
|
|
500
|
+
*(
|
|
501
|
+
unless oauth_jwt_public_keys.empty?
|
|
502
|
+
oauth_jwt_public_keys.flat_map { |algo, pkeys| pkeys.map { |pkey| JSON::JWK.new(pkey).merge(use: "sig", alg: algo) } }
|
|
503
|
+
end
|
|
504
|
+
),
|
|
505
|
+
*(
|
|
506
|
+
unless oauth_jwt_jwe_public_keys.empty?
|
|
507
|
+
oauth_jwt_jwe_public_keys.flat_map do |(algo, _enc), pkeys|
|
|
508
|
+
pkeys.map do |pkey|
|
|
509
|
+
JSON::JWK.new(pkey).merge(use: "enc", alg: algo)
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
),
|
|
514
|
+
# legacy
|
|
490
515
|
(JSON::JWK.new(oauth_jwt_public_key).merge(use: "sig", alg: oauth_jwt_algorithm) if oauth_jwt_public_key),
|
|
491
516
|
(JSON::JWK.new(oauth_jwt_legacy_public_key).merge(use: "sig", alg: oauth_jwt_legacy_algorithm) if oauth_jwt_legacy_public_key),
|
|
492
517
|
(JSON::JWK.new(oauth_jwt_jwe_public_key).merge(use: "enc", alg: oauth_jwt_jwe_algorithm) if oauth_jwt_jwe_public_key)
|
|
@@ -522,7 +547,8 @@ module Rodauth
|
|
|
522
547
|
JWT::JWK.import(data).keypair
|
|
523
548
|
end
|
|
524
549
|
|
|
525
|
-
def jwt_encode(payload,
|
|
550
|
+
def jwt_encode(payload,
|
|
551
|
+
signing_algorithm: oauth_jwt_algorithm || oauth_jwt_keys.keys.first)
|
|
526
552
|
headers = {}
|
|
527
553
|
|
|
528
554
|
key = oauth_jwt_keys[signing_algorithm] || _jwt_key
|
|
@@ -545,11 +571,11 @@ module Rodauth
|
|
|
545
571
|
def jwt_encode_with_jwe(
|
|
546
572
|
payload,
|
|
547
573
|
jwks: nil,
|
|
548
|
-
jwe_key: oauth_jwt_jwe_public_key || oauth_jwt_jwe_key,
|
|
549
574
|
encryption_algorithm: oauth_jwt_jwe_algorithm,
|
|
550
|
-
encryption_method: oauth_jwt_jwe_encryption_method,
|
|
575
|
+
encryption_method: oauth_jwt_jwe_encryption_method,
|
|
576
|
+
jwe_key: oauth_jwt_jwe_public_key || oauth_jwt_jwe_keys[[encryption_algorithm, encryption_method]] || oauth_jwt_jwe_key,
|
|
577
|
+
**args
|
|
551
578
|
)
|
|
552
|
-
|
|
553
579
|
token = jwt_encode_without_jwe(payload, **args)
|
|
554
580
|
|
|
555
581
|
return token unless encryption_algorithm && encryption_method
|
|
@@ -557,6 +583,7 @@ module Rodauth
|
|
|
557
583
|
if jwks && jwks.any? { |k| k[:use] == "enc" }
|
|
558
584
|
JWE.__rodauth_oauth_encrypt_from_jwks(token, jwks, alg: encryption_algorithm, enc: encryption_method)
|
|
559
585
|
elsif jwe_key
|
|
586
|
+
jwe_key = jwe_key.first if jwe_key.is_a?(Array)
|
|
560
587
|
params = {
|
|
561
588
|
zip: "DEF",
|
|
562
589
|
copyright: oauth_jwt_jwe_copyright
|
|
@@ -576,13 +603,15 @@ module Rodauth
|
|
|
576
603
|
def jwt_decode(
|
|
577
604
|
token,
|
|
578
605
|
jwks: nil,
|
|
579
|
-
|
|
580
|
-
|
|
606
|
+
jws_algorithm: oauth_jwt_algorithm || oauth_jwt_public_key.keys.first || oauth_jwt_keys.keys.first,
|
|
607
|
+
jws_key: oauth_jwt_public_key || oauth_jwt_keys[jws_algorithm] || _jwt_key,
|
|
581
608
|
verify_claims: true,
|
|
582
609
|
verify_jti: true,
|
|
583
610
|
verify_iss: true,
|
|
584
611
|
verify_aud: false
|
|
585
612
|
)
|
|
613
|
+
jws_key = jws_key.first if jws_key.is_a?(Array)
|
|
614
|
+
|
|
586
615
|
# verifying the JWT implies verifying:
|
|
587
616
|
#
|
|
588
617
|
# issuer: check that server generated the token
|
|
@@ -631,15 +660,16 @@ module Rodauth
|
|
|
631
660
|
def jwt_decode_with_jwe(
|
|
632
661
|
token,
|
|
633
662
|
jwks: nil,
|
|
634
|
-
jwe_key: oauth_jwt_jwe_key,
|
|
635
663
|
jws_encryption_algorithm: oauth_jwt_jwe_algorithm,
|
|
636
664
|
jws_encryption_method: oauth_jwt_jwe_encryption_method,
|
|
665
|
+
jwe_key: oauth_jwt_jwe_keys[[jws_encryption_algorithm, jws_encryption_method]] || oauth_jwt_jwe_key,
|
|
637
666
|
**args
|
|
638
667
|
)
|
|
639
668
|
|
|
640
669
|
token = if jwks && jwks.any? { |k| k[:use] == "enc" }
|
|
641
670
|
JWE.__rodauth_oauth_decrypt_from_jwks(token, jwks, alg: jws_encryption_algorithm, enc: jws_encryption_method)
|
|
642
671
|
elsif jwe_key
|
|
672
|
+
jwe_key = jwe_key.first if jwe_key.is_a?(Array)
|
|
643
673
|
JWE.decrypt(token, jwe_key)
|
|
644
674
|
else
|
|
645
675
|
token
|
|
@@ -656,6 +686,21 @@ module Rodauth
|
|
|
656
686
|
|
|
657
687
|
def jwks_set
|
|
658
688
|
@jwks_set ||= [
|
|
689
|
+
*(
|
|
690
|
+
unless oauth_jwt_public_keys.empty?
|
|
691
|
+
oauth_jwt_public_keys.flat_map { |algo, pkeys| pkeys.map { |pkey| JWT::JWK.new(pkey).export.merge(use: "sig", alg: algo) } }
|
|
692
|
+
end
|
|
693
|
+
),
|
|
694
|
+
*(
|
|
695
|
+
unless oauth_jwt_jwe_public_keys.empty?
|
|
696
|
+
oauth_jwt_jwe_public_keys.flat_map do |(algo, _enc), pkeys|
|
|
697
|
+
pkeys.map do |pkey|
|
|
698
|
+
JWT::JWK.new(pkey).export.merge(use: "enc", alg: algo)
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
end
|
|
702
|
+
),
|
|
703
|
+
# legacy
|
|
659
704
|
(JWT::JWK.new(oauth_jwt_public_key).export.merge(use: "sig", alg: oauth_jwt_algorithm) if oauth_jwt_public_key),
|
|
660
705
|
(
|
|
661
706
|
if oauth_jwt_legacy_public_key
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
require "rodauth/oauth/version"
|
|
4
|
+
require "rodauth/oauth/ttl_store"
|
|
5
|
+
|
|
6
|
+
module Rodauth
|
|
7
|
+
Feature.define(:oauth_resource_indicators, :OauthResourceIndicators) do
|
|
8
|
+
depends :oauth_base
|
|
9
|
+
|
|
10
|
+
auth_value_method :oauth_grants_resource_column, :resource
|
|
11
|
+
auth_value_method :oauth_tokens_resource_column, :resource
|
|
12
|
+
|
|
13
|
+
def resource_indicators
|
|
14
|
+
return @resource_indicators if defined?(@resource_indicators)
|
|
15
|
+
|
|
16
|
+
resources = param_or_nil("resource")
|
|
17
|
+
|
|
18
|
+
return unless resources
|
|
19
|
+
|
|
20
|
+
if json_request? || param_or_nil("request") # signed request
|
|
21
|
+
resources = Array(resources)
|
|
22
|
+
else
|
|
23
|
+
query = request.form_data? ? request.body.read : request.query_string
|
|
24
|
+
# resource query param does not conform to rack parsing rules
|
|
25
|
+
resources = URI.decode_www_form(query).each_with_object([]) do |(k, v), memo|
|
|
26
|
+
memo << v if k == "resource"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@resource_indicators = resources
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def require_oauth_authorization(*)
|
|
34
|
+
super
|
|
35
|
+
|
|
36
|
+
return unless authorization_token[oauth_tokens_resource_column]
|
|
37
|
+
|
|
38
|
+
token_indicators = authorization_token[oauth_tokens_resource_column]
|
|
39
|
+
|
|
40
|
+
token_indicators = token_indicators.split(" ") if token_indicators.is_a?(String)
|
|
41
|
+
|
|
42
|
+
authorization_required unless token_indicators.any? { |resource| base_url.start_with?(resource) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def validate_oauth_token_params
|
|
48
|
+
super
|
|
49
|
+
|
|
50
|
+
return unless resource_indicators
|
|
51
|
+
|
|
52
|
+
resource_indicators.each do |resource|
|
|
53
|
+
redirect_response_error("invalid_target") unless check_valid_no_fragment_uri?(resource)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def create_oauth_token_from_token(oauth_token, update_params)
|
|
58
|
+
return super unless resource_indicators
|
|
59
|
+
|
|
60
|
+
return super unless oauth_token[oauth_tokens_oauth_grant_id_column]
|
|
61
|
+
|
|
62
|
+
oauth_grant = db[oauth_grants_table].where(
|
|
63
|
+
oauth_grants_id_column => oauth_token[oauth_tokens_oauth_grant_id_column],
|
|
64
|
+
oauth_grants_revoked_at_column => nil
|
|
65
|
+
).first
|
|
66
|
+
|
|
67
|
+
grant_indicators = oauth_grant[oauth_grants_resource_column]
|
|
68
|
+
|
|
69
|
+
grant_indicators = grant_indicators.split(" ") if grant_indicators.is_a?(String)
|
|
70
|
+
|
|
71
|
+
redirect_response_error("invalid_target") unless (grant_indicators - resource_indicators) != grant_indicators
|
|
72
|
+
|
|
73
|
+
super(oauth_token, update_params.merge(oauth_tokens_resource_column => resource_indicators))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def check_valid_no_fragment_uri?(uri)
|
|
77
|
+
check_valid_uri?(uri) && URI.parse(uri).fragment.nil?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
module IndicatorAuthorizationCodeGrant
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def validate_oauth_grant_params
|
|
84
|
+
super
|
|
85
|
+
|
|
86
|
+
return unless resource_indicators
|
|
87
|
+
|
|
88
|
+
resource_indicators.each do |resource|
|
|
89
|
+
redirect_response_error("invalid_target") unless check_valid_no_fragment_uri?(resource)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def create_oauth_token_from_authorization_code(oauth_grant, create_params)
|
|
94
|
+
return super unless resource_indicators
|
|
95
|
+
|
|
96
|
+
redirect_response_error("invalid_target") unless oauth_grant[oauth_grants_resource_column]
|
|
97
|
+
|
|
98
|
+
grant_indicators = oauth_grant[oauth_grants_resource_column]
|
|
99
|
+
|
|
100
|
+
grant_indicators = grant_indicators.split(" ") if grant_indicators.is_a?(String)
|
|
101
|
+
|
|
102
|
+
redirect_response_error("invalid_target") unless (grant_indicators - resource_indicators) != grant_indicators
|
|
103
|
+
|
|
104
|
+
super(oauth_grant, create_params.merge(oauth_tokens_resource_column => resource_indicators))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def create_oauth_grant(create_params = {})
|
|
108
|
+
create_params[oauth_grants_resource_column] = resource_indicators.join(" ") if resource_indicators
|
|
109
|
+
|
|
110
|
+
super
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
module IndicatorIntrospection
|
|
115
|
+
def json_token_introspect_payload(token)
|
|
116
|
+
return super unless token[oauth_tokens_oauth_grant_id_column]
|
|
117
|
+
|
|
118
|
+
payload = super
|
|
119
|
+
|
|
120
|
+
token_indicators = token[oauth_tokens_resource_column]
|
|
121
|
+
|
|
122
|
+
token_indicators = token_indicators.split(" ") if token_indicators.is_a?(String)
|
|
123
|
+
|
|
124
|
+
payload[:aud] = token_indicators
|
|
125
|
+
|
|
126
|
+
payload
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def introspection_request(*)
|
|
130
|
+
payload = super
|
|
131
|
+
|
|
132
|
+
payload[oauth_tokens_resource_column] = payload["aud"] if payload["aud"]
|
|
133
|
+
|
|
134
|
+
payload
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
module IndicatorJwt
|
|
139
|
+
def jwt_claims(*)
|
|
140
|
+
return super unless resource_indicators
|
|
141
|
+
|
|
142
|
+
super.merge(aud: resource_indicators)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def self.included(rodauth)
|
|
147
|
+
super
|
|
148
|
+
rodauth.send(:include, IndicatorAuthorizationCodeGrant) if rodauth.features.include?(:oauth_authorization_code_grant)
|
|
149
|
+
rodauth.send(:include, IndicatorIntrospection) if rodauth.features.include?(:oauth_token_introspection)
|
|
150
|
+
rodauth.send(:include, IndicatorJwt) if rodauth.features.include?(:oauth_jwt)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -60,7 +60,7 @@ module Rodauth
|
|
|
60
60
|
id_token_signing_alg_values_supported
|
|
61
61
|
].freeze
|
|
62
62
|
|
|
63
|
-
depends :oauth_jwt
|
|
63
|
+
depends :account_expiration, :oauth_jwt
|
|
64
64
|
|
|
65
65
|
auth_value_method :oauth_application_default_scope, "openid"
|
|
66
66
|
auth_value_method :oauth_application_scopes, %w[openid]
|
|
@@ -73,8 +73,9 @@ module Rodauth
|
|
|
73
73
|
auth_value_method :oauth_applications_userinfo_encrypted_response_enc_column, :userinfo_encrypted_response_enc
|
|
74
74
|
|
|
75
75
|
auth_value_method :oauth_grants_nonce_column, :nonce
|
|
76
|
+
auth_value_method :oauth_grants_acr_column, :acr
|
|
76
77
|
auth_value_method :oauth_tokens_nonce_column, :nonce
|
|
77
|
-
auth_value_method :
|
|
78
|
+
auth_value_method :oauth_tokens_acr_column, :acr
|
|
78
79
|
|
|
79
80
|
translatable_method :invalid_scope_message, "The Access Token expired"
|
|
80
81
|
|
|
@@ -88,7 +89,13 @@ module Rodauth
|
|
|
88
89
|
auth_value_method :oauth_applications_post_logout_redirect_uri_column, :post_logout_redirect_uri
|
|
89
90
|
auth_value_method :use_rp_initiated_logout?, false
|
|
90
91
|
|
|
91
|
-
auth_value_methods(
|
|
92
|
+
auth_value_methods(
|
|
93
|
+
:get_oidc_param,
|
|
94
|
+
:get_additional_param,
|
|
95
|
+
:require_acr_value_phr,
|
|
96
|
+
:require_acr_value_phrh,
|
|
97
|
+
:require_acr_value
|
|
98
|
+
)
|
|
92
99
|
|
|
93
100
|
# /userinfo
|
|
94
101
|
route(:userinfo) do |r|
|
|
@@ -252,14 +259,43 @@ module Rodauth
|
|
|
252
259
|
|
|
253
260
|
private
|
|
254
261
|
|
|
262
|
+
if defined?(::I18n)
|
|
263
|
+
def before_authorize_route
|
|
264
|
+
if (ui_locales = param_or_nil("ui_locales"))
|
|
265
|
+
ui_locales = ui_locales.split(" ").map(&:to_sym)
|
|
266
|
+
ui_locales &= ::I18n.available_locales
|
|
267
|
+
|
|
268
|
+
::I18n.locale = ui_locales.first unless ui_locales.empty?
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
super
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def validate_oauth_grant_params
|
|
276
|
+
return super unless (max_age = param_or_nil("max_age"))
|
|
277
|
+
|
|
278
|
+
max_age = Integer(max_age)
|
|
279
|
+
|
|
280
|
+
redirect_response_error("invalid_request") unless max_age.positive?
|
|
281
|
+
|
|
282
|
+
return unless Time.now - last_account_login_at > max_age
|
|
283
|
+
|
|
284
|
+
# force user to re-login
|
|
285
|
+
clear_session
|
|
286
|
+
set_session_value(login_redirect_session_key, request.fullpath)
|
|
287
|
+
redirect require_login_redirect
|
|
288
|
+
end
|
|
289
|
+
|
|
255
290
|
def require_authorizable_account
|
|
256
|
-
try_prompt
|
|
291
|
+
try_prompt
|
|
257
292
|
super
|
|
293
|
+
try_acr_values
|
|
258
294
|
end
|
|
259
295
|
|
|
260
296
|
# this executes before checking for a logged in account
|
|
261
297
|
def try_prompt
|
|
262
|
-
prompt = param_or_nil("prompt")
|
|
298
|
+
return unless (prompt = param_or_nil("prompt"))
|
|
263
299
|
|
|
264
300
|
case prompt
|
|
265
301
|
when "none"
|
|
@@ -314,16 +350,46 @@ module Rodauth
|
|
|
314
350
|
end
|
|
315
351
|
end
|
|
316
352
|
|
|
317
|
-
def
|
|
318
|
-
return
|
|
353
|
+
def try_acr_values
|
|
354
|
+
return unless (acr_values = param_or_nil("acr_values"))
|
|
355
|
+
|
|
356
|
+
acr_values.split(" ").each do |acr_value|
|
|
357
|
+
case acr_value
|
|
358
|
+
when "phr" then require_acr_value_phr
|
|
359
|
+
when "phrh" then require_acr_value_phrh
|
|
360
|
+
else
|
|
361
|
+
require_acr_value(acr_value)
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
319
365
|
|
|
320
|
-
|
|
366
|
+
def require_acr_value_phr
|
|
367
|
+
return unless respond_to?(:require_two_factor_authenticated)
|
|
368
|
+
|
|
369
|
+
require_two_factor_authenticated
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def require_acr_value_phrh
|
|
373
|
+
require_acr_value_phr && two_factor_login_type_match?("webauthn")
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def require_acr_value(_acr); end
|
|
377
|
+
|
|
378
|
+
def create_oauth_grant(create_params = {})
|
|
379
|
+
if (nonce = param_or_nil("nonce"))
|
|
380
|
+
create_params[oauth_grants_nonce_column] = nonce
|
|
381
|
+
end
|
|
382
|
+
if (acr = param_or_nil("acr"))
|
|
383
|
+
create_params[oauth_grants_acr_column] = acr
|
|
384
|
+
end
|
|
385
|
+
super
|
|
321
386
|
end
|
|
322
387
|
|
|
323
388
|
def create_oauth_token_from_authorization_code(oauth_grant, create_params)
|
|
324
|
-
|
|
389
|
+
create_params[oauth_tokens_nonce_column] = oauth_grant[oauth_grants_nonce_column] if oauth_grant[oauth_grants_nonce_column]
|
|
390
|
+
create_params[oauth_tokens_acr_column] = oauth_grant[oauth_grants_acr_column] if oauth_grant[oauth_grants_acr_column]
|
|
325
391
|
|
|
326
|
-
super
|
|
392
|
+
super
|
|
327
393
|
end
|
|
328
394
|
|
|
329
395
|
def create_oauth_token(*)
|
|
@@ -338,11 +404,13 @@ module Rodauth
|
|
|
338
404
|
return unless oauth_scopes.include?("openid")
|
|
339
405
|
|
|
340
406
|
id_token_claims = jwt_claims(oauth_token)
|
|
407
|
+
|
|
341
408
|
id_token_claims[:nonce] = oauth_token[oauth_tokens_nonce_column] if oauth_token[oauth_tokens_nonce_column]
|
|
342
409
|
|
|
410
|
+
id_token_claims[:acr] = oauth_token[oauth_tokens_acr_column] if oauth_token[oauth_tokens_acr_column]
|
|
411
|
+
|
|
343
412
|
# Time when the End-User authentication occurred.
|
|
344
|
-
|
|
345
|
-
id_token_claims[:auth_time] = oauth_token[oauth_tokens_auth_time_column].to_i
|
|
413
|
+
id_token_claims[:auth_time] = last_account_login_at.to_i
|
|
346
414
|
|
|
347
415
|
account = db[accounts_table].where(account_id_column => oauth_token[oauth_tokens_account_id_column]).first
|
|
348
416
|
|
|
@@ -377,16 +445,23 @@ module Rodauth
|
|
|
377
445
|
|
|
378
446
|
oidc_scopes, additional_scopes = scopes_by_claim.keys.partition { |key| OIDC_SCOPES_MAP.key?(key) }
|
|
379
447
|
|
|
448
|
+
if (claims_locales = param_or_nil("claims_locales"))
|
|
449
|
+
claims_locales = claims_locales.split(" ").map(&:to_sym)
|
|
450
|
+
end
|
|
451
|
+
|
|
380
452
|
unless oidc_scopes.empty?
|
|
381
453
|
if respond_to?(:get_oidc_param)
|
|
454
|
+
get_oidc_param = proxy_get_param(:get_oidc_param, claims, claims_locales)
|
|
455
|
+
|
|
382
456
|
oidc_scopes.each do |scope|
|
|
383
457
|
scope_claims = claims
|
|
384
458
|
params = scopes_by_claim[scope]
|
|
385
459
|
params = params.empty? ? OIDC_SCOPES_MAP[scope] : (OIDC_SCOPES_MAP[scope] & params)
|
|
386
460
|
|
|
387
461
|
scope_claims = (claims["address"] = {}) if scope == "address"
|
|
462
|
+
|
|
388
463
|
params.each do |param|
|
|
389
|
-
|
|
464
|
+
get_oidc_param[account, param, scope_claims]
|
|
390
465
|
end
|
|
391
466
|
end
|
|
392
467
|
else
|
|
@@ -397,14 +472,39 @@ module Rodauth
|
|
|
397
472
|
return if additional_scopes.empty?
|
|
398
473
|
|
|
399
474
|
if respond_to?(:get_additional_param)
|
|
475
|
+
get_additional_param = proxy_get_param(:get_additional_param, claims, claims_locales)
|
|
476
|
+
|
|
400
477
|
additional_scopes.each do |scope|
|
|
401
|
-
|
|
478
|
+
get_additional_param[account, scope.to_sym]
|
|
402
479
|
end
|
|
403
480
|
else
|
|
404
481
|
warn "`get_additional_param(account, claim)` must be implemented to use oidc scopes."
|
|
405
482
|
end
|
|
406
483
|
end
|
|
407
484
|
|
|
485
|
+
def proxy_get_param(get_param_func, claims, claims_locales)
|
|
486
|
+
meth = method(get_param_func)
|
|
487
|
+
if meth.arity == 2
|
|
488
|
+
->(account, param, cl = claims) { cl[param] = meth[account, param] }
|
|
489
|
+
elsif claims_locales.nil?
|
|
490
|
+
->(account, param, cl = claims) { cl[param] = meth[account, param, nil] }
|
|
491
|
+
else
|
|
492
|
+
lambda do |account, param, cl = claims|
|
|
493
|
+
claims_values = claims_locales.map do |locale|
|
|
494
|
+
meth[account, param, locale]
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
if claims_values.uniq.size == 1
|
|
498
|
+
cl[param] = claims_values.first
|
|
499
|
+
else
|
|
500
|
+
claims_locales.zip(claims_values).each do |locale, value|
|
|
501
|
+
cl["#{param}##{locale}"] = value
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
408
508
|
def json_access_token_payload(oauth_token)
|
|
409
509
|
payload = super
|
|
410
510
|
payload["id_token"] = oauth_token[:id_token] if oauth_token[:id_token]
|
|
@@ -452,6 +552,12 @@ module Rodauth
|
|
|
452
552
|
oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
|
453
553
|
oauth_tokens_scopes_column => scopes
|
|
454
554
|
}
|
|
555
|
+
if (nonce = param_or_nil("nonce"))
|
|
556
|
+
create_params[oauth_grants_nonce_column] = nonce
|
|
557
|
+
end
|
|
558
|
+
if (acr = param_or_nil("acr"))
|
|
559
|
+
create_params[oauth_grants_acr_column] = acr
|
|
560
|
+
end
|
|
455
561
|
oauth_token = generate_oauth_token(create_params, false)
|
|
456
562
|
generate_id_token(oauth_token)
|
|
457
563
|
params = json_access_token_payload(oauth_token)
|
data/locales/en.yml
CHANGED
|
@@ -7,7 +7,7 @@ en:
|
|
|
7
7
|
revoke_oauth_token_notice_flash: "The oauth token has been revoked"
|
|
8
8
|
device_verification_notice_flash: "The device is verified"
|
|
9
9
|
user_code_not_found_error_flash: "No device to authorize with the given user code"
|
|
10
|
-
|
|
10
|
+
authorize_page_title: "Authorize"
|
|
11
11
|
oauth_applications_page_title: "Oauth Applications"
|
|
12
12
|
oauth_application_page_title: "Oauth Application"
|
|
13
13
|
new_oauth_application_page_title: "New Oauth Application"
|
|
@@ -17,6 +17,7 @@ en:
|
|
|
17
17
|
device_search_page_title: "Device Search"
|
|
18
18
|
oauth_management_pagination_previous_button: "Previous"
|
|
19
19
|
oauth_management_pagination_next_button: "Next"
|
|
20
|
+
oauth_tokens_scopes_label: "Scopes"
|
|
20
21
|
oauth_applications_name_label: "Name"
|
|
21
22
|
oauth_applications_description_label: "Description"
|
|
22
23
|
oauth_applications_scopes_label: "Default scopes"
|
data/locales/pt.yml
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
pt:
|
|
2
|
+
rodauth:
|
|
3
|
+
require_authorization_error_flash: "Autorize para continuar"
|
|
4
|
+
create_oauth_application_error_flash: "Aconteceu um erro ao registar o aplicativo oauth"
|
|
5
|
+
create_oauth_application_notice_flash: "O seu aplicativo oauth foi registado com sucesso"
|
|
6
|
+
revoke_unauthorized_account_error_flash: "Não está autorizado a revogar este token"
|
|
7
|
+
revoke_oauth_token_notice_flash: "O token oauth foi revogado com sucesso"
|
|
8
|
+
device_verification_notice_flash: "O dispositivo foi verificado com sucesso"
|
|
9
|
+
user_code_not_found_error_flash: "Não existe nenhum dispositivo a ser autorizado com o código de usuário inserido"
|
|
10
|
+
authorize_page_title: "Autorizar"
|
|
11
|
+
oauth_applications_page_title: "Aplicativos OAuth"
|
|
12
|
+
oauth_application_page_title: "Aplicativo Oauth"
|
|
13
|
+
new_oauth_application_page_title: "Novo Aplicativo Oauth"
|
|
14
|
+
oauth_application_oauth_tokens_page_title: "Tokens Oauth do Aplicativo"
|
|
15
|
+
oauth_tokens_page_title: "Os meus Tokens Oauth"
|
|
16
|
+
device_verification_page_title: "Verificação de dispositivo"
|
|
17
|
+
device_search_page_title: "Pesquisa de dispositivo"
|
|
18
|
+
oauth_management_pagination_previous_button: "Anterior"
|
|
19
|
+
oauth_management_pagination_next_button: "Próxima"
|
|
20
|
+
oauth_tokens_scopes_label: "Escopos"
|
|
21
|
+
oauth_applications_name_label: "Nome"
|
|
22
|
+
oauth_applications_description_label: "Descrição"
|
|
23
|
+
oauth_applications_scopes_label: "Escopos prédefinidos"
|
|
24
|
+
oauth_applications_contacts_label: "Contactos"
|
|
25
|
+
oauth_applications_homepage_url_label: "URL da página principal"
|
|
26
|
+
oauth_applications_tos_uri_label: "URL dos termos de serviço"
|
|
27
|
+
oauth_applications_policy_uri_label: "URL das diretrizes"
|
|
28
|
+
oauth_applications_redirect_uri_label: "URL para redireccionamento"
|
|
29
|
+
oauth_applications_client_secret_label: "Segredo de cliente"
|
|
30
|
+
oauth_applications_client_id_label: "ID do cliente"
|
|
31
|
+
oauth_grant_user_code_label: "Código do usuário"
|
|
32
|
+
oauth_grant_user_jws_jwk_label: "Chaves JSON Web"
|
|
33
|
+
oauth_grant_user_jwt_public_key_label: "Chave pública"
|
|
34
|
+
oauth_application_button: "Registar"
|
|
35
|
+
oauth_authorize_button: "Autorizar"
|
|
36
|
+
oauth_token_revoke_button: "Revogar"
|
|
37
|
+
oauth_authorize_post_button: "Voltar para o aplicativo cliente"
|
|
38
|
+
oauth_device_verification_button: "Verificar"
|
|
39
|
+
oauth_device_search_button: "Pesquisar"
|
|
40
|
+
invalid_client_message: "A autenticação do cliente falhou"
|
|
41
|
+
invalid_grant_type_message: "Tipo de atribuição inválida"
|
|
42
|
+
invalid_grant_message: "Atribuição inválida"
|
|
43
|
+
invalid_scope_message: "Escopo inválido"
|
|
44
|
+
invalid_url_message: "URL inválido"
|
|
45
|
+
unsupported_token_type_message: "Sugestão de tipo de token inválida"
|
|
46
|
+
unique_error_message: "já está sendo utilizado"
|
|
47
|
+
null_error_message: "não está preenchido"
|
|
48
|
+
already_in_use_message: "erro ao gerar token único"
|
|
49
|
+
expired_token_message: "o código de dispositivo expirou"
|
|
50
|
+
access_denied_message: "o pedido de autorização foi negado"
|
|
51
|
+
authorization_pending_message: "o pedido de autorização ainda está pendente"
|
|
52
|
+
slow_down_message: "o pedido de autorização ainda está pendente mas o intervalo de actualização deve ser aumentado"
|
|
53
|
+
code_challenge_required_message: "código de negociação necessário"
|
|
54
|
+
unsupported_transform_algorithm_message: "algoritmo de transformação não suportado"
|
|
55
|
+
request_uri_not_supported_message: "request_uri não é suportado"
|
|
56
|
+
invalid_request_object_message: "request_object é inválido"
|
|
57
|
+
invalid_scope_message: "O Token de acesso expirou"
|
data/templates/authorize.str
CHANGED
|
@@ -81,10 +81,20 @@
|
|
|
81
81
|
#{"<input type=\"hidden\" name=\"response_type\" value=\"#{rodauth.param("response_type")}\"/>" if rodauth.param_or_nil("response_type")}
|
|
82
82
|
#{"<input type=\"hidden\" name=\"response_mode\" value=\"#{rodauth.param("response_mode")}\"/>" if rodauth.param_or_nil("response_mode")}
|
|
83
83
|
#{"<input type=\"hidden\" name=\"state\" value=\"#{rodauth.param("state")}\"/>" if rodauth.param_or_nil("state")}
|
|
84
|
-
#{"<input type=\"hidden\" name=\"nonce\" value=\"#{rodauth.param("nonce")}\"/>" if rodauth.param_or_nil("nonce")}
|
|
85
84
|
#{"<input type=\"hidden\" name=\"redirect_uri\" value=\"#{rodauth.redirect_uri}\"/>" if rodauth.param_or_nil("redirect_uri")}
|
|
86
|
-
#{"<input type=\"hidden\" name=\"code_challenge\" value=\"#{rodauth.param("code_challenge")}\"/>" if rodauth.param_or_nil("code_challenge")}
|
|
87
|
-
#{"<input type=\"hidden\" name=\"code_challenge_method\" value=\"#{rodauth.param("code_challenge_method")}\"/>" if rodauth.param_or_nil("code_challenge_method")}
|
|
85
|
+
#{"<input type=\"hidden\" name=\"code_challenge\" value=\"#{rodauth.param("code_challenge")}\"/>" if rodauth.features.include?(:oauth_pkce) && rodauth.param_or_nil("code_challenge")}
|
|
86
|
+
#{"<input type=\"hidden\" name=\"code_challenge_method\" value=\"#{rodauth.param("code_challenge_method")}\"/>" if rodauth.features.include?(:oauth_pkce) && rodauth.param_or_nil("code_challenge_method")}
|
|
87
|
+
#{"<input type=\"hidden\" name=\"nonce\" value=\"#{rodauth.param("nonce")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("nonce")}
|
|
88
|
+
#{"<input type=\"hidden\" name=\"ui_locales\" value=\"#{rodauth.param("ui_locales")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("ui_locales")}
|
|
89
|
+
#{"<input type=\"hidden\" name=\"claims_locales\" value=\"#{rodauth.param("claims_locales")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("claims_locales")}
|
|
90
|
+
#{"<input type=\"hidden\" name=\"acr\" value=\"#{rodauth.param("acr_values")}\"/>" if rodauth.features.include?(:oidc) && rodauth.param_or_nil("acr_values")}
|
|
91
|
+
#{
|
|
92
|
+
if rodauth.features.include?(:oauth_resource_indicators) && rodauth.resource_indicators
|
|
93
|
+
rodauth.resource_indicators.map do |resource|
|
|
94
|
+
"<input type=\"hidden\" name=\"resource\" value=\"#{resource}\"/>"
|
|
95
|
+
end.join
|
|
96
|
+
end
|
|
97
|
+
}
|
|
88
98
|
</div>
|
|
89
99
|
<p class="text-center">
|
|
90
100
|
<input type="submit" class="btn btn-outline-primary" value="#{h(rodauth.oauth_authorize_button)}"/>
|
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.10.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: 2022-
|
|
11
|
+
date: 2022-06-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rodauth
|
|
@@ -39,6 +39,7 @@ extra_rdoc_files:
|
|
|
39
39
|
- doc/release_notes/0_0_4.md
|
|
40
40
|
- doc/release_notes/0_0_5.md
|
|
41
41
|
- doc/release_notes/0_0_6.md
|
|
42
|
+
- doc/release_notes/0_10_0.md
|
|
42
43
|
- doc/release_notes/0_1_0.md
|
|
43
44
|
- doc/release_notes/0_2_0.md
|
|
44
45
|
- doc/release_notes/0_3_0.md
|
|
@@ -70,6 +71,7 @@ files:
|
|
|
70
71
|
- doc/release_notes/0_0_4.md
|
|
71
72
|
- doc/release_notes/0_0_5.md
|
|
72
73
|
- doc/release_notes/0_0_6.md
|
|
74
|
+
- doc/release_notes/0_10_0.md
|
|
73
75
|
- doc/release_notes/0_1_0.md
|
|
74
76
|
- doc/release_notes/0_2_0.md
|
|
75
77
|
- doc/release_notes/0_3_0.md
|
|
@@ -120,6 +122,7 @@ files:
|
|
|
120
122
|
- lib/rodauth/features/oauth_jwt_bearer_grant.rb
|
|
121
123
|
- lib/rodauth/features/oauth_management_base.rb
|
|
122
124
|
- lib/rodauth/features/oauth_pkce.rb
|
|
125
|
+
- lib/rodauth/features/oauth_resource_indicators.rb
|
|
123
126
|
- lib/rodauth/features/oauth_resource_server.rb
|
|
124
127
|
- lib/rodauth/features/oauth_saml_bearer_grant.rb
|
|
125
128
|
- lib/rodauth/features/oauth_token_introspection.rb
|
|
@@ -135,6 +138,7 @@ files:
|
|
|
135
138
|
- lib/rodauth/oauth/ttl_store.rb
|
|
136
139
|
- lib/rodauth/oauth/version.rb
|
|
137
140
|
- locales/en.yml
|
|
141
|
+
- locales/pt.yml
|
|
138
142
|
- templates/authorize.str
|
|
139
143
|
- templates/client_secret_field.str
|
|
140
144
|
- templates/description_field.str
|