rodauth-oauth 0.8.0 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -3
  3. data/doc/release_notes/0_9_0.md +56 -0
  4. data/doc/release_notes/0_9_1.md +9 -0
  5. data/doc/release_notes/0_9_2.md +10 -0
  6. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +22 -1
  7. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +8 -3
  8. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +8 -2
  9. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +1 -0
  10. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +1 -0
  11. data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +1 -0
  12. data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +13 -1
  13. data/lib/rodauth/features/oauth.rb +2 -2
  14. data/lib/rodauth/features/oauth_application_management.rb +23 -7
  15. data/lib/rodauth/features/oauth_assertion_base.rb +1 -1
  16. data/lib/rodauth/features/oauth_authorization_code_grant.rb +4 -1
  17. data/lib/rodauth/features/oauth_base.rb +57 -14
  18. data/lib/rodauth/features/oauth_client_credentials_grant.rb +33 -0
  19. data/lib/rodauth/features/oauth_device_grant.rb +4 -5
  20. data/lib/rodauth/features/oauth_dynamic_client_registration.rb +252 -0
  21. data/lib/rodauth/features/oauth_jwt.rb +251 -49
  22. data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +1 -0
  23. data/lib/rodauth/features/oauth_management_base.rb +72 -0
  24. data/lib/rodauth/features/oauth_pkce.rb +1 -1
  25. data/lib/rodauth/features/oauth_token_management.rb +8 -6
  26. data/lib/rodauth/features/oidc.rb +37 -7
  27. data/lib/rodauth/features/oidc_dynamic_client_registration.rb +147 -0
  28. data/lib/rodauth/oauth/jwe_extensions.rb +64 -0
  29. data/lib/rodauth/oauth/ttl_store.rb +9 -3
  30. data/lib/rodauth/oauth/version.rb +1 -1
  31. data/locales/en.yml +6 -1
  32. data/templates/authorize.str +50 -1
  33. data/templates/jwks_field.str +4 -0
  34. data/templates/jwt_public_key_field.str +1 -1
  35. data/templates/new_oauth_application.str +1 -1
  36. data/templates/oauth_application.str +1 -1
  37. data/templates/oauth_application_oauth_tokens.str +1 -0
  38. data/templates/oauth_applications.str +1 -0
  39. data/templates/oauth_tokens.str +1 -0
  40. data/templates/scope_field.str +3 -2
  41. metadata +14 -3
  42. data/templates/jws_jwk_field.str +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7381dc47a766e5ff725d77331e045bbd97e09bff282d7b9b9d7b176011c87fa3
4
- data.tar.gz: 0c8562b520431858d3ad9b88e311087ad98747eaeb473d16286a061e8a9d84b9
3
+ metadata.gz: 2514b45f82f9e8dda98f15dc2c1ccc0eeba306c9d1ee40e6fa47e4999d766c1c
4
+ data.tar.gz: d68579829772121a157b7bd654fb40999921af46673910caa21892462655ed0e
5
5
  SHA512:
6
- metadata.gz: 82d32250cadc973d9cb0618b6654ad783ac77529d2000f6d24b1759ec4d3d39f4764e64a3ac3c0e72b752f47e35331489e7feba6060f1239049954f004c7485e
7
- data.tar.gz: c9bb9d578f40d924e4c2bad34840257f9de5a38e9d0cf427bf5d63ca1f672e0cbfeeffb8d513e0210e17a9060bb871c328e8ab88d64de334c54ca29bc3ff338f
6
+ metadata.gz: 8121458a789119610c920c835fc99cde76d4eca41fb7bb48acbe9c1e2f4be89f68c9370235335d7dc8c6b3b715b6825a4d77bafbda37551464ebf35a516f55de
7
+ data.tar.gz: bdd2c1d2bee336459186606b2bfb293cd690333a58c421798155e6bc53e418b71bbd142ac19e48ddcff701f28eaaa6c65c8d045c715a7f84690fb5dd6865ddbe
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 flow](https://tools.ietf.org/html/rfc6749#section-1.3);
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))).
@@ -70,7 +73,7 @@ Or install it yourself as:
70
73
 
71
74
  ## Usage
72
75
 
73
- This tutorial assumes you already read the documentation and know how to set up `rodauth`. After that, integrating `roda-auth` will look like:
76
+ This tutorial assumes you already read the documentation and know how to set up `rodauth`. After that, integrating `rodauth-oauth` will look like:
74
77
 
75
78
  ```ruby
76
79
  plugin :rodauth do
@@ -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.
@@ -0,0 +1,9 @@
1
+ ### 0.9.1 (08/05/2022)
2
+
3
+ #### Improvements
4
+
5
+ Using `return_response`, introduced in `rodauth` v2.23, which accomplishes better integration with rails response logging mechanism when used under `rodauth-rails`.
6
+
7
+ #### Bugfixes
8
+
9
+ * Fixing namespacing issue which required anyone to have to `require "rodauth-oauth"` before loading it (no need to anymore).
@@ -0,0 +1,10 @@
1
+ ### 0.9.2 (11/05/2022)
2
+
3
+ #### Bugfixes
4
+
5
+ * Fixed remaining namespacing fix issues requiring usage of `require "rodauth-oauth"`.
6
+ * Fixed wrong expectation of database for resource-server mode when `:oauth_management_base` plugin was used.
7
+ * oidc: fixed incorrect grant creation flow whenn using `nonce` param.
8
+ * oidc: fixed jwt encoding regression when not setting encryption method/algorithmm for client applications.
9
+ * templates: added missing jwks field to the "New oauth application" form.
10
+ * Several fixes on the example OIDC applications, mostly around CSRF breakage when using latest version of `omniauth`.
@@ -1,5 +1,26 @@
1
1
  <%= form_tag rodauth.authorize_path, method: :post do %>
2
- <p class="lead">The application <%= rodauth.oauth_application[rodauth.oauth_applications_name_column] %> would like to access your data.</p>
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>
@@ -28,9 +28,14 @@
28
28
  </div>
29
29
  <% if rodauth.features.include?(:oauth_jwt) %>
30
30
  <div class="form-group">
31
- <%= label_tag "jws_jwk", rodauth.oauth_applications_jws_jwk_label %>
32
- <%= text_field_tag "jws_jwk", rodauth.param('jws_jwk'), id: "jws-jwk", class: "form-control#{' is-invalid' if rodauth.field_error('jws_jwk')}" %>
33
- <%= rodauth.field_error('jws_jwk') %>
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
- <dt><%= rodauth.oauth_applications_jws_jwk_label %>: </dt>
18
- <dd><%= oauth_application[rodauth.oauth_applications_jws_jwk_column] %></dd>
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 %>
@@ -35,4 +35,5 @@
35
35
  <% end %>
36
36
  </tbody>
37
37
  </table>
38
+ <%= rodauth.oauth_management_pagination_links(@oauth_tokens) %>
38
39
  <% end %>
@@ -26,4 +26,5 @@
26
26
  <% end %>
27
27
  </tbody>
28
28
  </table>
29
+ <%= rodauth.oauth_management_pagination_links(oauth_applications_ds) %>
29
30
  <% end %>
@@ -31,4 +31,5 @@
31
31
  <% end %>
32
32
  </tbody>
33
33
  </table>
34
+ <%= rodauth.oauth_management_pagination_links(oauth_tokens) %>
34
35
  <% 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
- :oauth_device_grant, :oauth_token_introspection, :oauth_token_revocation,
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 :oauth_base
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 description scopes homepage_url redirect_uri client_secret].freeze
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
@@ -33,7 +33,12 @@ module Rodauth
33
33
 
34
34
  translatable_method :oauth_applications_name_label, "Name"
35
35
  translatable_method :oauth_applications_description_label, "Description"
36
- translatable_method :oauth_applications_scopes_label, "Scopes"
36
+ translatable_method :oauth_applications_scopes_label, "Default 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
- oauth_tokens = db[oauth_tokens_table].where(oauth_tokens_oauth_application_id_column => id)
92
- scope.instance_variable_set(:@oauth_tokens, oauth_tokens)
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 == "authorization_code"
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(
@@ -4,6 +4,8 @@ require "time"
4
4
  require "base64"
5
5
  require "securerandom"
6
6
  require "net/http"
7
+ require "rodauth/version"
8
+ require "rodauth/oauth/version"
7
9
  require "rodauth/oauth/ttl_store"
8
10
  require "rodauth/oauth/database_extensions"
9
11
  require "rodauth/oauth/refinements"
@@ -29,6 +31,7 @@ module Rodauth
29
31
  auth_value_method :oauth_unique_id_generation_retries, 3
30
32
 
31
33
  auth_value_method :oauth_response_mode, "query"
34
+ auth_value_method :oauth_auth_methods_supported, %w[client_secret_basic client_secret_post]
32
35
 
33
36
  auth_value_method :oauth_scope_separator, " "
34
37
 
@@ -58,6 +61,9 @@ module Rodauth
58
61
  name description scopes
59
62
  client_id client_secret
60
63
  homepage_url redirect_uri
64
+ token_endpoint_auth_method grant_types response_types
65
+ logo_uri tos_uri policy_uri jwks jwks_uri
66
+ contacts software_id software_version
61
67
  ].each do |column|
62
68
  auth_value_method :"oauth_applications_#{column}_column", column
63
69
  end
@@ -331,26 +337,45 @@ module Rodauth
331
337
  #
332
338
  def require_oauth_application
333
339
  # get client credentials
340
+ auth_method = nil
334
341
  client_id = client_secret = nil
335
342
 
336
343
  if (token = ((v = request.env["HTTP_AUTHORIZATION"]) && v[/\A *Basic (.*)\Z/, 1]))
337
344
  # client_secret_basic
338
345
  client_id, client_secret = Base64.decode64(token).split(/:/, 2)
346
+ auth_method = "client_secret_basic"
339
347
  else
340
348
  # client_secret_post
341
349
  client_id = param_or_nil("client_id")
342
350
  client_secret = param_or_nil("client_secret")
351
+ auth_method = "client_secret_post" if client_secret
343
352
  end
344
353
 
345
354
  authorization_required unless client_id
346
355
 
347
356
  @oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => client_id).first
348
357
 
349
- authorization_required unless authorized_oauth_application?(@oauth_application, client_secret)
358
+ authorization_required unless @oauth_application
359
+
360
+ authorization_required unless authorized_oauth_application?(@oauth_application, client_secret, auth_method)
361
+ end
362
+
363
+ def authorized_oauth_application?(oauth_application, client_secret, auth_method)
364
+ supported_auth_methods = if oauth_application[oauth_applications_token_endpoint_auth_method_column]
365
+ oauth_application[oauth_applications_token_endpoint_auth_method_column].split(/ +/)
366
+ else
367
+ oauth_auth_methods_supported
368
+ end
369
+
370
+ if auth_method
371
+ supported_auth_methods.include?(auth_method) && secret_matches?(oauth_application, client_secret)
372
+ else
373
+ supported_auth_methods.include?("none")
374
+ end
350
375
  end
351
376
 
352
- def authorized_oauth_application?(oauth_application, client_secret)
353
- oauth_application && secret_matches?(oauth_application, client_secret)
377
+ def no_auth_oauth_application?(_oauth_application)
378
+ supported_auth_methods.include?("none")
354
379
  end
355
380
 
356
381
  def require_oauth_application_from_account
@@ -515,8 +540,7 @@ module Rodauth
515
540
  end
516
541
 
517
542
  def create_oauth_token(grant_type)
518
- case grant_type
519
- when "refresh_token"
543
+ if supported_grant_type?(grant_type, "refresh_token")
520
544
  # fetch potentially revoked oauth token
521
545
  oauth_token = oauth_token_by_refresh_token(param("refresh_token"), revoked: true)
522
546
 
@@ -594,7 +618,17 @@ module Rodauth
594
618
  end
595
619
  end
596
620
 
597
- def oauth_server_metadata_body(path)
621
+ def supported_grant_type?(grant_type, expected_grant_type = grant_type)
622
+ return false unless grant_type == expected_grant_type
623
+
624
+ return true unless (grant_types_supported = oauth_application[oauth_applications_grant_types_column])
625
+
626
+ grant_types_supported = grant_types_supported.split(/ +/)
627
+
628
+ grant_types_supported.include?(grant_type)
629
+ end
630
+
631
+ def oauth_server_metadata_body(path = nil)
598
632
  issuer = base_url
599
633
  issuer += "/#{path}" if path
600
634
 
@@ -604,8 +638,8 @@ module Rodauth
604
638
  scopes_supported: oauth_application_scopes,
605
639
  response_types_supported: [],
606
640
  response_modes_supported: [],
607
- grant_types_supported: [],
608
- token_endpoint_auth_methods_supported: %w[client_secret_basic client_secret_post],
641
+ grant_types_supported: %w[refresh_token],
642
+ token_endpoint_auth_methods_supported: oauth_auth_methods_supported,
609
643
  service_documentation: oauth_metadata_service_documentation,
610
644
  ui_locales_supported: oauth_metadata_ui_locales_supported,
611
645
  op_policy_uri: oauth_metadata_op_policy_uri,
@@ -655,11 +689,10 @@ module Rodauth
655
689
  response["Pragma"] = "no-cache"
656
690
  end
657
691
  json_payload = _json_response_body(body)
658
- response.write(json_payload)
659
- request.halt
692
+ return_response(json_payload)
660
693
  end
661
694
 
662
- def throw_json_response_error(status, error_code)
695
+ def throw_json_response_error(status, error_code, message = nil)
663
696
  set_response_error_status(status)
664
697
  code = if respond_to?(:"#{error_code}_error_code")
665
698
  send(:"#{error_code}_error_code")
@@ -667,12 +700,11 @@ module Rodauth
667
700
  error_code
668
701
  end
669
702
  payload = { "error" => code }
670
- payload["error_description"] = send(:"#{error_code}_message") if respond_to?(:"#{error_code}_message")
703
+ payload["error_description"] = message || (send(:"#{error_code}_message") if respond_to?(:"#{error_code}_message"))
671
704
  json_payload = _json_response_body(payload)
672
705
  response["Content-Type"] ||= json_response_content_type
673
706
  response["WWW-Authenticate"] = oauth_token_type.upcase if status == 401
674
- response.write(json_payload)
675
- request.halt
707
+ return_response(json_payload)
676
708
  end
677
709
 
678
710
  unless method_defined?(:_json_response_body)
@@ -685,6 +717,13 @@ module Rodauth
685
717
  end
686
718
  end
687
719
 
720
+ if Gem::Version.new(Rodauth.version) < Gem::Version.new("2.23")
721
+ def return_response(body = nil)
722
+ response.write(body) if body
723
+ request.halt
724
+ end
725
+ end
726
+
688
727
  def authorization_required
689
728
  if accepts_json?
690
729
  throw_json_response_error(authorization_required_error_status, "invalid_client")
@@ -700,6 +739,10 @@ module Rodauth
700
739
  (scopes - oauth_application[oauth_applications_scopes_column].split(oauth_scope_separator)).empty?
701
740
  end
702
741
 
742
+ def check_valid_uri?(uri)
743
+ URI::DEFAULT_PARSER.make_regexp(oauth_valid_uri_schemes).match?(uri)
744
+ end
745
+
703
746
  # Resource server mode
704
747
 
705
748
  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 && oauth_application && use_oauth_device_code_grant_type?
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
- case grant_type
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
- when "device_code"
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