rodauth-oauth 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -2
- data/doc/release_notes/0_9_0.md +56 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +22 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +8 -3
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +8 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +1 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +1 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +1 -0
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +13 -1
- data/lib/rodauth/features/oauth.rb +2 -2
- data/lib/rodauth/features/oauth_application_management.rb +22 -6
- data/lib/rodauth/features/oauth_assertion_base.rb +1 -1
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +4 -1
- data/lib/rodauth/features/oauth_base.rb +46 -10
- data/lib/rodauth/features/oauth_client_credentials_grant.rb +33 -0
- data/lib/rodauth/features/oauth_device_grant.rb +4 -5
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +252 -0
- data/lib/rodauth/features/oauth_jwt.rb +248 -49
- data/lib/rodauth/features/oauth_management_base.rb +68 -0
- data/lib/rodauth/features/oauth_pkce.rb +1 -1
- data/lib/rodauth/features/oauth_token_management.rb +8 -6
- data/lib/rodauth/features/oidc.rb +32 -3
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +147 -0
- data/lib/rodauth/oauth/jwe_extensions.rb +64 -0
- data/lib/rodauth/oauth/ttl_store.rb +9 -3
- data/lib/rodauth/oauth/version.rb +1 -1
- data/locales/en.yml +5 -0
- data/templates/authorize.str +50 -1
- data/templates/jwks_field.str +4 -0
- data/templates/oauth_application.str +1 -1
- data/templates/oauth_application_oauth_tokens.str +1 -0
- data/templates/oauth_applications.str +1 -0
- data/templates/oauth_tokens.str +1 -0
- metadata +10 -3
- data/templates/jws_jwk_field.str +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae63d25ed38845cc8186a0484cf7a223f4939b2695d61b58bc2df7d3aec1af0c
|
4
|
+
data.tar.gz: bb0361efcde688c2dda720825513af035a7befcb36460c11d6d80226e8791bbe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28a740ca518ec609cfcbffc61fbaa8f71fb049a8221ccb7858b9357b8f4eee9b3fe263b1a2747f1839677f07d05f3a2cf8c2c4370d1ae8c1b3fe4c33d54012c1
|
7
|
+
data.tar.gz: ad1d38778909f1f7bb8d6e69fbb44177c02eb96c2d40a94e54e0a1221efc871b7fc0e5c364845b38b5a7bb35d33610a9fdcddbb2e95f18be47a6b039f986ed9f
|
data/README.md
CHANGED
@@ -11,9 +11,10 @@ This gem implements the following RFCs and features of OAuth:
|
|
11
11
|
|
12
12
|
* `oauth` - [The OAuth 2.0 protocol framework](https://tools.ietf.org/html/rfc6749):
|
13
13
|
* [Access Token generation](https://tools.ietf.org/html/rfc6749#section-1.4);
|
14
|
-
* [Access Token refresh](https://tools.ietf.org/html/rfc6749#section-1.5);
|
15
|
-
* `oauth_authorization_code_grant` - [Authorization grant
|
14
|
+
* [Access Token refresh token grant](https://tools.ietf.org/html/rfc6749#section-1.5);
|
15
|
+
* `oauth_authorization_code_grant` - [Authorization code grant](https://tools.ietf.org/html/rfc6749#section-1.3);
|
16
16
|
* `oauth_implicit_grant` - [Implicit grant (off by default)](https://tools.ietf.org/html/rfc6749#section-4.2);
|
17
|
+
* `oauth_client_credentials_grant` - [Client credentials grant (off by default)](https://tools.ietf.org/html/rfc6749#section-4.4);
|
17
18
|
* `oauth_device_grant` - [Device code grant (off by default)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-device-flow-15);
|
18
19
|
* `oauth_token_revocation` - [Token revocation](https://tools.ietf.org/html/rfc7009);
|
19
20
|
* `oauth_token_introspection` - [Token introspection](https://tools.ietf.org/html/rfc7662);
|
@@ -26,6 +27,7 @@ This gem implements the following RFCs and features of OAuth:
|
|
26
27
|
* `oauth_saml_bearer_grant` - [SAML 2.0 Bearer Assertion](https://datatracker.ietf.org/doc/html/rfc7522);
|
27
28
|
* `oauth_jwt_bearer_grant` - [JWT Bearer Assertion](https://datatracker.ietf.org/doc/html/rfc7523);
|
28
29
|
* [JWT Secured Authorization Requests](https://tools.ietf.org/html/draft-ietf-oauth-jwsreq-20);
|
30
|
+
* [Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591);
|
29
31
|
* OAuth application and token management dashboards;
|
30
32
|
|
31
33
|
It also implements the [OpenID Connect layer](https://openid.net/connect/) (via the `openid` feature) on top of the OAuth features it provides, including:
|
@@ -33,6 +35,7 @@ It also implements the [OpenID Connect layer](https://openid.net/connect/) (via
|
|
33
35
|
* [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html);
|
34
36
|
* [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0-29.html);
|
35
37
|
* [OpenID Multiple Response Types](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html);
|
38
|
+
* [OpenID Connect Dynamic Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html);
|
36
39
|
* [RP Initiated Logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html);
|
37
40
|
|
38
41
|
This gem supports also rails (through [rodauth-rails]((https://github.com/janko/rodauth-rails))).
|
@@ -0,0 +1,56 @@
|
|
1
|
+
### 0.9.0 (18/04/2022)
|
2
|
+
|
3
|
+
#### Features
|
4
|
+
|
5
|
+
##### Dynamic client registration
|
6
|
+
|
7
|
+
`rodauth-oauth` now supports the [Oauth Dynamic client registration RFC](https://datatracker.ietf.org/doc/html/rfc7591), via the `oauth_dynamic_client_registration` feature; it also supports [the OpenID variant](https://openid.net/specs/openid-connect-registration-1_0.html), via the `oidc_dynamic_client_registration` feature.
|
8
|
+
|
9
|
+
With it, you now have the option to enable API-driven client application registration.
|
10
|
+
|
11
|
+
##### Client Credentials grant
|
12
|
+
|
13
|
+
`rodauth-oauth` now supports the [Client Credentials grant](https://tools.ietf.org/html/rfc6749#section-4.4), via the `oauth_client_credentials_grant` feature.
|
14
|
+
|
15
|
+
|
16
|
+
#### Improvements
|
17
|
+
|
18
|
+
##### OAuth Applications & Tokens paginated list pages
|
19
|
+
|
20
|
+
The management dashboards for OAuth Applications & Tokens were loading the full dataset into the HTML view. They'll now only show 20 records by default, and present pagination links to navigate across pages (for the default templates).
|
21
|
+
|
22
|
+
##### More Oauth Application properties
|
23
|
+
|
24
|
+
As a result of implementing "OAuth Dynamic client registration", new functionality is unlocked when the following database columns are set on the oauth applications table:
|
25
|
+
|
26
|
+
* `token_endpoint_auth_method` - enables oauth application-scoped verification of used client authentication method.
|
27
|
+
* `grant_types` - scopes the supported grant types for the given application.
|
28
|
+
* `response_type` - scopes the supported response types for the given application.
|
29
|
+
* `logo_uri` - stores an image link which can be used to load and display a logo in the authorization form.
|
30
|
+
* `tos_uri` - stores a link to the oauth application "Terms of Service" page.
|
31
|
+
* `policy_uri` - stores a link to the oauth application "Policy" page.
|
32
|
+
* `jwks_uri` - stores a link where to load the oauth application JWKs from.
|
33
|
+
* `jwks` - stores the JWKS from the oauth application.
|
34
|
+
* `contacts` stores the contacts.
|
35
|
+
* `software_id` - stores the software unique identifier.
|
36
|
+
* `software_version` - stores the software version for the unique identifier.
|
37
|
+
* `subject_type` - stores the subject type used for calculating the JWT `sub` claim for the applicatiion.
|
38
|
+
* `request_object_signing_alg` - stores the signing algorithm which request objects coming from the application will be signed with.
|
39
|
+
* `request_object_encryption_alg` - stores the encryption algorithm which request objects coming from the application will be encrypted with.
|
40
|
+
* `request_object_encryption_enc` - stores the encryption method which request objects coming from the application will be encrypted with.
|
41
|
+
* `id_token_signed_response_alg` - stores the signing algorithm which id tokens from the application will be signed with.
|
42
|
+
* `id_token_encrypted_response_alg` - stores the encryption algorithm which id tokens from the application will be encrypted with.
|
43
|
+
* `id_token_encrypted_response_enc` - stores the encryption method which id tokens from the application will be encrypted with.
|
44
|
+
* `userinfo_signed_response_alg` - stores the signing algorithm which JWT-encoded userinfo payloads from the application will be signed with.
|
45
|
+
* `userinfo_encrypted_response_alg` - stores the encryption algorithm which JWT-encoded userinfo payloads from the application will be encrypted with.
|
46
|
+
* `userinfo_encrypted_response_enc` - stores the encryption method which JWT-encoded userinfo payloads from the application will be encrypted with.
|
47
|
+
|
48
|
+
|
49
|
+
##### TTL Store has finer grained lock
|
50
|
+
|
51
|
+
The TTL Store, used for the JWKs cache rotation p.ex., had a lock around the section which would involve the HTTP request for the JWKs, which would block the process for the duration of it. The lock has been removed around that area, and if two requests happen for the same URL, first one wins.
|
52
|
+
|
53
|
+
#### Deprecations and breaking changes
|
54
|
+
|
55
|
+
* (`oauth_jwt` plugin) `:oauth_jwt_algorithm` option default is now `"RS256"` (previous one was `"HS256"`, and yes, this an assymetric cryptography move).
|
56
|
+
* (`oauth_jwt` plugin) `jws_jwk` option (and all the labels and params) is deprecated.
|
@@ -1,5 +1,26 @@
|
|
1
1
|
<%= form_tag rodauth.authorize_path, method: :post do %>
|
2
|
-
|
2
|
+
<% if rodauth.oauth_application[rodauth.oauth_applications_logo_uri_column] %>
|
3
|
+
<%= image_tag rodauth.oauth_application[rodauth.oauth_applications_logo_uri_column] %>
|
4
|
+
<% end %>
|
5
|
+
<p class="lead">The application <%= link_to rodauth.oauth_application[rodauth.oauth_applications_name_column], rodauth.oauth_application[rodauth.oauth_applications_homepage_url_column] %> would like to access your data.</p>
|
6
|
+
|
7
|
+
<div class="list-group">
|
8
|
+
<% if rodauth.oauth_application[rodauth.oauth_applications_tos_uri_column] %>
|
9
|
+
<%= link_to rodauth.oauth_applications_tos_uri_label, rodauth.oauth_application[rodauth.oauth_applications_tos_uri_column], class: "list-group-item" %>
|
10
|
+
<% end %>
|
11
|
+
<% if rodauth.oauth_application[rodauth.oauth_applications_policy_uri_column] %>
|
12
|
+
<%= link_to rodauth.oauth_applications_policy_uri_label, rodauth.oauth_application[rodauth.oauth_applications_policy_uri_column], class: "list-group-item" %>
|
13
|
+
<% end %>
|
14
|
+
</div>
|
15
|
+
|
16
|
+
<% if rodauth.oauth_application[rodauth.oauth_applications_contacts_column] %>
|
17
|
+
<div class="list-group">
|
18
|
+
<h3 class="display-6"><%= rodauth.oauth_applications_contacts_label %></h3>
|
19
|
+
<% rodauth.oauth_application[rodauth.oauth_applications_contacts_column].split(/ +/).each do |contact| %>
|
20
|
+
<div class="list-group-item"><%= contact %></div>
|
21
|
+
<% end %>
|
22
|
+
</div>
|
23
|
+
<% end %>
|
3
24
|
|
4
25
|
<div class="form-group">
|
5
26
|
<h1 class="display-6"><%= rodauth.oauth_tokens_scopes_label %></h1>
|
data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb
CHANGED
@@ -28,9 +28,14 @@
|
|
28
28
|
</div>
|
29
29
|
<% if rodauth.features.include?(:oauth_jwt) %>
|
30
30
|
<div class="form-group">
|
31
|
-
<%= label_tag "
|
32
|
-
<%= text_field_tag "
|
33
|
-
<%= rodauth.field_error('
|
31
|
+
<%= label_tag "jwks", rodauth.oauth_applications_jwks_label %>
|
32
|
+
<%= text_field_tag "jwks", rodauth.param('jwks'), id: "jwks", class: "form-control#{' is-invalid' if rodauth.field_error('jwks')}" %>
|
33
|
+
<%= rodauth.field_error('jwks') %>
|
34
|
+
</div>
|
35
|
+
<div class="form-group">
|
36
|
+
<%= label_tag "jwks_uri", rodauth.oauth_applications_jwks_uri_label %>
|
37
|
+
<%= text_field_tag "jwks_uri", rodauth.param('jwks_uri'), id: "jwks-uri", class: "form-control#{' is-invalid' if rodauth.field_error('jwks_uri')}" %>
|
38
|
+
<%= rodauth.field_error('jwks_uri') %>
|
34
39
|
</div>
|
35
40
|
<div class="form-group">
|
36
41
|
<%= label_tag "jwt_public_key", rodauth.oauth_applications_jwt_public_key_label %>
|
@@ -14,8 +14,14 @@
|
|
14
14
|
<dt><%= rodauth.oauth_applications_scopes_label %>: </dt>
|
15
15
|
<dd><%= oauth_application[rodauth.oauth_applications_scopes_column] %></dd>
|
16
16
|
<% if rodauth.features.include?(:oauth_jwt) %>
|
17
|
-
|
18
|
-
|
17
|
+
<% if oauth_application[rodauth.oauth_applications_jwks_column] %>
|
18
|
+
<dt><%= rodauth.oauth_applications_jwks_label %>: </dt>
|
19
|
+
<dd><%= oauth_application[rodauth.oauth_applications_jwks_column] %></dd>
|
20
|
+
<% end %>
|
21
|
+
<% if oauth_application[rodauth.oauth_applications_jwks_uri_column] %>
|
22
|
+
<dt><%= rodauth.oauth_applications_jwks_uri_label %>: </dt>
|
23
|
+
<dd><%= oauth_application[rodauth.oauth_applications_jwks_uri_column] %></dd>
|
24
|
+
<% end %>
|
19
25
|
<dt><%= rodauth.oauth_applications_jwt_public_key_label %>: </dt>
|
20
26
|
<dd><%= oauth_application[rodauth.oauth_applications_jwt_public_key_column] %></dd>
|
21
27
|
<% end %>
|
@@ -11,9 +11,21 @@ class CreateRodauthOauth < ActiveRecord::Migration<%= migration_version %>
|
|
11
11
|
t.string :client_secret, null: false, index: { unique: true }
|
12
12
|
t.string :scopes, null: false
|
13
13
|
t.datetime :created_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
|
14
|
+
# extra params
|
15
|
+
# t.string :token_endpoint_auth_method, null: true
|
16
|
+
# t.string :grant_types, null: true
|
17
|
+
# t.string :response_types, null: true
|
18
|
+
# t.string :client_uri, null: true
|
19
|
+
# t.string :logo_uri, null: true
|
20
|
+
# t.string :tos_uri, null: true
|
21
|
+
# t.string :policy_uri, null: true
|
22
|
+
# t.string :jwks_uri, null: true
|
23
|
+
# t.string :jwks, null: true
|
24
|
+
# t.string :contacts, null: true
|
25
|
+
# t.string :software_id, null: true
|
26
|
+
# t.string :software_version, null: true
|
14
27
|
# JWT/OIDC per application signing verification
|
15
28
|
# t.text :jwt_public_key, null: true
|
16
|
-
# t.text :jws_jwk, null: true
|
17
29
|
# RP-initiated logout
|
18
30
|
# t.string :post_logout_redirect_uri, null: false
|
19
31
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Rodauth
|
4
4
|
Feature.define(:oauth, :Oauth) do
|
5
5
|
depends :oauth_base, :oauth_authorization_code_grant, :oauth_pkce, :oauth_implicit_grant,
|
6
|
-
:
|
7
|
-
:oauth_application_management, :oauth_token_management
|
6
|
+
:oauth_client_credentials_grant, :oauth_device_grant, :oauth_token_introspection,
|
7
|
+
:oauth_token_revocation, :oauth_application_management, :oauth_token_management
|
8
8
|
end
|
9
9
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Rodauth
|
4
4
|
Feature.define(:oauth_application_management, :OauthApplicationManagement) do
|
5
|
-
depends :
|
5
|
+
depends :oauth_management_base
|
6
6
|
|
7
7
|
before "create_oauth_application"
|
8
8
|
after "create_oauth_application"
|
@@ -18,10 +18,10 @@ module Rodauth
|
|
18
18
|
auth_value_method :oauth_valid_uri_schemes, %w[https]
|
19
19
|
|
20
20
|
# Application
|
21
|
-
APPLICATION_REQUIRED_PARAMS = %w[name
|
21
|
+
APPLICATION_REQUIRED_PARAMS = %w[name scopes homepage_url redirect_uri client_secret].freeze
|
22
22
|
auth_value_method :oauth_application_required_params, APPLICATION_REQUIRED_PARAMS
|
23
23
|
|
24
|
-
(APPLICATION_REQUIRED_PARAMS + %w[client_id]).each do |param|
|
24
|
+
(APPLICATION_REQUIRED_PARAMS + %w[description client_id]).each do |param|
|
25
25
|
auth_value_method :"oauth_application_#{param}_param", param
|
26
26
|
configuration_module_eval do
|
27
27
|
define_method :"#{param}_label" do
|
@@ -34,6 +34,11 @@ module Rodauth
|
|
34
34
|
translatable_method :oauth_applications_name_label, "Name"
|
35
35
|
translatable_method :oauth_applications_description_label, "Description"
|
36
36
|
translatable_method :oauth_applications_scopes_label, "Scopes"
|
37
|
+
translatable_method :oauth_applications_contacts_label, "Contacts"
|
38
|
+
translatable_method :oauth_applications_tos_uri_label, "Terms of service"
|
39
|
+
translatable_method :oauth_applications_policy_uri_label, "Policy"
|
40
|
+
translatable_method :oauth_applications_jwks_label, "JSON Web Keys"
|
41
|
+
translatable_method :oauth_applications_jwks_uri_label, "JSON Web Keys URI"
|
37
42
|
translatable_method :oauth_applications_homepage_url_label, "Homepage URL"
|
38
43
|
translatable_method :oauth_applications_redirect_uri_label, "Redirect URI"
|
39
44
|
translatable_method :oauth_applications_client_secret_label, "Client Secret"
|
@@ -43,7 +48,9 @@ module Rodauth
|
|
43
48
|
|
44
49
|
auth_value_method :oauth_applications_oauth_tokens_path, "oauth-tokens"
|
45
50
|
auth_value_method :oauth_applications_route, "oauth-applications"
|
51
|
+
auth_value_method :oauth_applications_per_page, 20
|
46
52
|
auth_value_method :oauth_applications_id_pattern, Integer
|
53
|
+
auth_value_method :oauth_tokens_per_page, 20
|
47
54
|
|
48
55
|
translatable_method :invalid_url_message, "Invalid URL"
|
49
56
|
translatable_method :null_error_message, "is not filled"
|
@@ -88,8 +95,12 @@ module Rodauth
|
|
88
95
|
end
|
89
96
|
|
90
97
|
request.on(oauth_applications_oauth_tokens_path) do
|
91
|
-
|
92
|
-
|
98
|
+
page = Integer(param_or_nil("page") || 1)
|
99
|
+
per_page = per_page_param(oauth_tokens_per_page)
|
100
|
+
oauth_tokens = db[oauth_tokens_table]
|
101
|
+
.where(oauth_tokens_oauth_application_id_column => id)
|
102
|
+
.order(Sequel.desc(oauth_tokens_id_column))
|
103
|
+
scope.instance_variable_set(:@oauth_tokens, oauth_tokens.paginate(page, per_page))
|
93
104
|
request.get do
|
94
105
|
oauth_application_oauth_tokens_view
|
95
106
|
end
|
@@ -97,8 +108,13 @@ module Rodauth
|
|
97
108
|
end
|
98
109
|
|
99
110
|
request.get do
|
111
|
+
page = Integer(param_or_nil("page") || 1)
|
112
|
+
per_page = per_page_param(oauth_applications_per_page)
|
100
113
|
scope.instance_variable_set(:@oauth_applications, db[oauth_applications_table]
|
101
|
-
.where(oauth_applications_account_id_column => account_id)
|
114
|
+
.where(oauth_applications_account_id_column => account_id)
|
115
|
+
.order(Sequel.desc(oauth_applications_id_column))
|
116
|
+
.paginate(page, per_page))
|
117
|
+
|
102
118
|
oauth_applications_view
|
103
119
|
end
|
104
120
|
|
@@ -55,7 +55,7 @@ module Rodauth
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def create_oauth_token(grant_type)
|
58
|
-
return super unless assertion_grant_type?(grant_type)
|
58
|
+
return super unless assertion_grant_type?(grant_type) && supported_grant_type?(grant_type)
|
59
59
|
|
60
60
|
account = __send__(:"account_from_#{assertion_grant_type}_assertion", param("assertion"))
|
61
61
|
|
@@ -26,6 +26,9 @@ module Rodauth
|
|
26
26
|
end
|
27
27
|
|
28
28
|
translatable_method :oauth_tokens_scopes_label, "Scopes"
|
29
|
+
translatable_method :oauth_applications_contacts_label, "Contacts"
|
30
|
+
translatable_method :oauth_applications_tos_uri_label, "Terms of service URL"
|
31
|
+
translatable_method :oauth_applications_policy_uri_label, "Policy URL"
|
29
32
|
|
30
33
|
# /authorize
|
31
34
|
route(:authorize) do |r|
|
@@ -172,7 +175,7 @@ module Rodauth
|
|
172
175
|
end
|
173
176
|
|
174
177
|
def create_oauth_token(grant_type)
|
175
|
-
return super unless grant_type
|
178
|
+
return super unless supported_grant_type?(grant_type, "authorization_code")
|
176
179
|
|
177
180
|
# fetch oauth grant
|
178
181
|
oauth_grant = db[oauth_grants_table].where(
|
@@ -29,6 +29,7 @@ module Rodauth
|
|
29
29
|
auth_value_method :oauth_unique_id_generation_retries, 3
|
30
30
|
|
31
31
|
auth_value_method :oauth_response_mode, "query"
|
32
|
+
auth_value_method :oauth_auth_methods_supported, %w[client_secret_basic client_secret_post]
|
32
33
|
|
33
34
|
auth_value_method :oauth_scope_separator, " "
|
34
35
|
|
@@ -58,6 +59,9 @@ module Rodauth
|
|
58
59
|
name description scopes
|
59
60
|
client_id client_secret
|
60
61
|
homepage_url redirect_uri
|
62
|
+
token_endpoint_auth_method grant_types response_types
|
63
|
+
logo_uri tos_uri policy_uri jwks jwks_uri
|
64
|
+
contacts software_id software_version
|
61
65
|
].each do |column|
|
62
66
|
auth_value_method :"oauth_applications_#{column}_column", column
|
63
67
|
end
|
@@ -331,26 +335,45 @@ module Rodauth
|
|
331
335
|
#
|
332
336
|
def require_oauth_application
|
333
337
|
# get client credentials
|
338
|
+
auth_method = nil
|
334
339
|
client_id = client_secret = nil
|
335
340
|
|
336
341
|
if (token = ((v = request.env["HTTP_AUTHORIZATION"]) && v[/\A *Basic (.*)\Z/, 1]))
|
337
342
|
# client_secret_basic
|
338
343
|
client_id, client_secret = Base64.decode64(token).split(/:/, 2)
|
344
|
+
auth_method = "client_secret_basic"
|
339
345
|
else
|
340
346
|
# client_secret_post
|
341
347
|
client_id = param_or_nil("client_id")
|
342
348
|
client_secret = param_or_nil("client_secret")
|
349
|
+
auth_method = "client_secret_post" if client_secret
|
343
350
|
end
|
344
351
|
|
345
352
|
authorization_required unless client_id
|
346
353
|
|
347
354
|
@oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => client_id).first
|
348
355
|
|
349
|
-
authorization_required unless
|
356
|
+
authorization_required unless @oauth_application
|
357
|
+
|
358
|
+
authorization_required unless authorized_oauth_application?(@oauth_application, client_secret, auth_method)
|
350
359
|
end
|
351
360
|
|
352
|
-
def authorized_oauth_application?(oauth_application, client_secret)
|
353
|
-
|
361
|
+
def authorized_oauth_application?(oauth_application, client_secret, auth_method)
|
362
|
+
supported_auth_methods = if oauth_application[oauth_applications_token_endpoint_auth_method_column]
|
363
|
+
oauth_application[oauth_applications_token_endpoint_auth_method_column].split(/ +/)
|
364
|
+
else
|
365
|
+
oauth_auth_methods_supported
|
366
|
+
end
|
367
|
+
|
368
|
+
if auth_method
|
369
|
+
supported_auth_methods.include?(auth_method) && secret_matches?(oauth_application, client_secret)
|
370
|
+
else
|
371
|
+
supported_auth_methods.include?("none")
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def no_auth_oauth_application?(_oauth_application)
|
376
|
+
supported_auth_methods.include?("none")
|
354
377
|
end
|
355
378
|
|
356
379
|
def require_oauth_application_from_account
|
@@ -515,8 +538,7 @@ module Rodauth
|
|
515
538
|
end
|
516
539
|
|
517
540
|
def create_oauth_token(grant_type)
|
518
|
-
|
519
|
-
when "refresh_token"
|
541
|
+
if supported_grant_type?(grant_type, "refresh_token")
|
520
542
|
# fetch potentially revoked oauth token
|
521
543
|
oauth_token = oauth_token_by_refresh_token(param("refresh_token"), revoked: true)
|
522
544
|
|
@@ -594,7 +616,17 @@ module Rodauth
|
|
594
616
|
end
|
595
617
|
end
|
596
618
|
|
597
|
-
def
|
619
|
+
def supported_grant_type?(grant_type, expected_grant_type = grant_type)
|
620
|
+
return false unless grant_type == expected_grant_type
|
621
|
+
|
622
|
+
return true unless (grant_types_supported = oauth_application[oauth_applications_grant_types_column])
|
623
|
+
|
624
|
+
grant_types_supported = grant_types_supported.split(/ +/)
|
625
|
+
|
626
|
+
grant_types_supported.include?(grant_type)
|
627
|
+
end
|
628
|
+
|
629
|
+
def oauth_server_metadata_body(path = nil)
|
598
630
|
issuer = base_url
|
599
631
|
issuer += "/#{path}" if path
|
600
632
|
|
@@ -604,8 +636,8 @@ module Rodauth
|
|
604
636
|
scopes_supported: oauth_application_scopes,
|
605
637
|
response_types_supported: [],
|
606
638
|
response_modes_supported: [],
|
607
|
-
grant_types_supported: [],
|
608
|
-
token_endpoint_auth_methods_supported:
|
639
|
+
grant_types_supported: %w[refresh_token],
|
640
|
+
token_endpoint_auth_methods_supported: oauth_auth_methods_supported,
|
609
641
|
service_documentation: oauth_metadata_service_documentation,
|
610
642
|
ui_locales_supported: oauth_metadata_ui_locales_supported,
|
611
643
|
op_policy_uri: oauth_metadata_op_policy_uri,
|
@@ -659,7 +691,7 @@ module Rodauth
|
|
659
691
|
request.halt
|
660
692
|
end
|
661
693
|
|
662
|
-
def throw_json_response_error(status, error_code)
|
694
|
+
def throw_json_response_error(status, error_code, message = nil)
|
663
695
|
set_response_error_status(status)
|
664
696
|
code = if respond_to?(:"#{error_code}_error_code")
|
665
697
|
send(:"#{error_code}_error_code")
|
@@ -667,7 +699,7 @@ module Rodauth
|
|
667
699
|
error_code
|
668
700
|
end
|
669
701
|
payload = { "error" => code }
|
670
|
-
payload["error_description"] = send(:"#{error_code}_message") if respond_to?(:"#{error_code}_message")
|
702
|
+
payload["error_description"] = message || (send(:"#{error_code}_message") if respond_to?(:"#{error_code}_message"))
|
671
703
|
json_payload = _json_response_body(payload)
|
672
704
|
response["Content-Type"] ||= json_response_content_type
|
673
705
|
response["WWW-Authenticate"] = oauth_token_type.upcase if status == 401
|
@@ -700,6 +732,10 @@ module Rodauth
|
|
700
732
|
(scopes - oauth_application[oauth_applications_scopes_column].split(oauth_scope_separator)).empty?
|
701
733
|
end
|
702
734
|
|
735
|
+
def check_valid_uri?(uri)
|
736
|
+
URI::DEFAULT_PARSER.make_regexp(oauth_valid_uri_schemes).match?(uri)
|
737
|
+
end
|
738
|
+
|
703
739
|
# Resource server mode
|
704
740
|
|
705
741
|
SERVER_METADATA = OAuth::TtlStore.new
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rodauth
|
4
|
+
Feature.define(:oauth_client_credentials_grant, :OauthClientCredentialsGrant) do
|
5
|
+
depends :oauth_base
|
6
|
+
|
7
|
+
auth_value_method :use_oauth_client_credentials_grant_type?, false
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def create_oauth_token(grant_type)
|
12
|
+
return super unless grant_type == "client_credentials" && use_oauth_client_credentials_grant_type?
|
13
|
+
|
14
|
+
create_params = {
|
15
|
+
oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
16
|
+
oauth_tokens_scopes_column => scopes.join(oauth_scope_separator)
|
17
|
+
}
|
18
|
+
generate_oauth_token(create_params, false)
|
19
|
+
end
|
20
|
+
|
21
|
+
def oauth_server_metadata_body(*)
|
22
|
+
super.tap do |data|
|
23
|
+
data[:grant_types_supported] << "client_credentials" if use_oauth_client_credentials_grant_type?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def check_valid_response_type?
|
28
|
+
return true if use_oauth_implicit_grant_type? && param_or_nil("response_type") == "token"
|
29
|
+
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -124,18 +124,17 @@ module Rodauth
|
|
124
124
|
.rjust(user_code_size, "0")
|
125
125
|
end
|
126
126
|
|
127
|
-
def authorized_oauth_application?(oauth_application, client_secret)
|
127
|
+
def authorized_oauth_application?(oauth_application, client_secret, _)
|
128
128
|
# skip if using device grant
|
129
129
|
#
|
130
130
|
# requests may be performed by devices with no knowledge of client secret.
|
131
|
-
return true if !client_secret &&
|
131
|
+
return true if !client_secret && use_oauth_device_code_grant_type?
|
132
132
|
|
133
133
|
super
|
134
134
|
end
|
135
135
|
|
136
136
|
def create_oauth_token(grant_type)
|
137
|
-
|
138
|
-
when "urn:ietf:params:oauth:grant-type:device_code"
|
137
|
+
if supported_grant_type?(grant_type, "urn:ietf:params:oauth:grant-type:device_code")
|
139
138
|
throw_json_response_error(invalid_oauth_response_status, "invalid_grant_type") unless use_oauth_device_code_grant_type?
|
140
139
|
|
141
140
|
oauth_grant = db[oauth_grants_table].where(
|
@@ -168,7 +167,7 @@ module Rodauth
|
|
168
167
|
end
|
169
168
|
end
|
170
169
|
oauth_token
|
171
|
-
|
170
|
+
elsif grant_type == "device_code"
|
172
171
|
redirect_response_error("invalid_grant_type") unless use_oauth_device_code_grant_type?
|
173
172
|
|
174
173
|
# fetch oauth grant
|