rodauth-oauth 1.0.0.pre.beta2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -1
- data/MIGRATION-GUIDE-v1.md +12 -0
- data/README.md +25 -10
- data/doc/release_notes/0_1_0.md +2 -2
- data/doc/release_notes/0_2_0.md +1 -1
- data/doc/release_notes/0_3_0.md +1 -1
- data/doc/release_notes/0_5_0.md +2 -2
- data/doc/release_notes/0_8_0.md +2 -2
- data/doc/release_notes/1_0_0.md +79 -0
- data/doc/release_notes/1_1_0.md +9 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize_error.erb +10 -0
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +0 -1
- data/lib/rodauth/features/oauth_authorize_base.rb +49 -14
- data/lib/rodauth/features/oauth_base.rb +1 -0
- data/lib/rodauth/features/oauth_implicit_grant.rb +10 -0
- data/lib/rodauth/features/oidc.rb +37 -9
- data/lib/rodauth/features/oidc_rp_initiated_logout.rb +29 -26
- data/lib/rodauth/oauth/version.rb +1 -1
- data/locales/en.yml +2 -1
- data/locales/pt.yml +2 -1
- data/templates/authorize_error.str +12 -0
- metadata +17 -15
- data/doc/release_notes/1_0_0_beta1.md +0 -38
- data/doc/release_notes/1_0_0_beta2.md +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 12c86242a8a2001fba629cb6bd8e25886b8805fce5d0965ebc70377824e25e91
|
4
|
+
data.tar.gz: 2fdc78f81b737c9c0d0086f258a86807f8855d3f0bc9be89bffb6d6a90946ed3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be15b77c46a135d213cd6e6e6bc7000b961e036febdb8f75bb922f09554d68ab757d5094fe96816c83c5966a3094daa32ecbbee798b2a8bb72417df9506ac3b3
|
7
|
+
data.tar.gz: a5fec610e9193d449ef49fbfde36688398c9e4e576da5ea579165c306ecc51bc910d0d758c923a9bece59ef4e7651347f9ebb7951504f3354b101fe5f845173a
|
data/CHANGELOG.md
CHANGED
@@ -1 +1 @@
|
|
1
|
-
See the Release Notes under https://gitlab.com/
|
1
|
+
See the Release Notes under https://gitlab.com/os85/rodauth-oauth/-/tree/master/doc/release_notes
|
data/MIGRATION-GUIDE-v1.md
CHANGED
@@ -90,6 +90,10 @@ The client secret is hashed (with bcrypt) before being stored, by default. While
|
|
90
90
|
oauth_applications_client_secret_hash_column nil
|
91
91
|
```
|
92
92
|
|
93
|
+
## oauth applications: oauth_applications_homepage_url_column no longer required
|
94
|
+
|
95
|
+
The homepage url is no longer considered a require prooperty of an OAuth client application.
|
96
|
+
|
93
97
|
## oauth grants: access token and refresh token hashed by default
|
94
98
|
|
95
99
|
access token and refresh token columns are now hashed by default, and point to the same column as the main counterpart:
|
@@ -205,6 +209,14 @@ JWKs URI endpoint has been moved to its plugin. If you require this functionalit
|
|
205
209
|
enable :oauth_jwt, :oauth_jwt_jwks
|
206
210
|
```
|
207
211
|
|
212
|
+
## OIDC RP-initiated logout segregated in its plugin
|
213
|
+
|
214
|
+
It was previously being loaded in the `:oidc` plugin by default. If you require this funtionality, enable the plugin:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
enable :oidc_rp_initiated_logout
|
218
|
+
```
|
219
|
+
|
208
220
|
## routing functions renamed
|
209
221
|
|
210
222
|
Previously, loading well-known routes, the oauth server metadata, or oauth application/tokens (now grants) management dashboard implied calling a function on roda to load those routes. These have been renamed:
|
data/README.md
CHANGED
@@ -1,11 +1,25 @@
|
|
1
1
|
# Rodauth::Oauth
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/rodauth-oauth.svg)](http://rubygems.org/gems/rodauth-oauth)
|
4
|
-
[![pipeline status](https://gitlab.com/
|
5
|
-
[![coverage report](https://gitlab.com/
|
4
|
+
[![pipeline status](https://gitlab.com/os85/rodauth-oauth/badges/master/pipeline.svg)](https://gitlab.com/os85/rodauth-oauth/pipelines?page=1&scope=all&ref=master)
|
5
|
+
[![coverage report](https://gitlab.com/os85/rodauth-oauth/badges/master/coverage.svg?job=coverage)](https://os85.gitlab.io/rodauth-oauth/coverage/#_AllFiles)
|
6
6
|
|
7
7
|
This is an extension to the `rodauth` gem which implements the [OAuth 2.0 framework](https://tools.ietf.org/html/rfc6749) for an authorization server.
|
8
8
|
|
9
|
+
## Certification
|
10
|
+
[<img width="184" height="96" align="right" src="/openid-certified.jpg" alt="OpenID Certification">](https://openid.net/certification/)
|
11
|
+
|
12
|
+
`rodauth-oauth` is [certified](https://openid.net/certification/) for the following profiles of the OpenID Connect™ protocol:
|
13
|
+
|
14
|
+
* Basic OP
|
15
|
+
* Implicit OP
|
16
|
+
* Hybrid OP
|
17
|
+
* Config OP
|
18
|
+
* Dynamic OP
|
19
|
+
* Form Post OP
|
20
|
+
|
21
|
+
(it also passes the conformance tests for the RP-Initiated Logout OP).
|
22
|
+
|
9
23
|
## Features
|
10
24
|
|
11
25
|
This gem implements the following RFCs and features of OAuth:
|
@@ -32,6 +46,7 @@ This gem implements the following RFCs and features of OAuth:
|
|
32
46
|
|
33
47
|
* `oauth_dynamic_client_registration` - [Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591);
|
34
48
|
* OAuth application and token management dashboards;
|
49
|
+
* The recommendations for [Native Apps](https://www.rfc-editor.org/rfc/rfc8252);
|
35
50
|
|
36
51
|
It also implements the [OpenID Connect layer](https://openid.net/connect/) (via the `openid` feature) on top of the OAuth features it provides, including:
|
37
52
|
|
@@ -65,10 +80,10 @@ Or install it yourself as:
|
|
65
80
|
## Resources
|
66
81
|
| | |
|
67
82
|
| ------------- | ----------------------------------------------------------- |
|
68
|
-
| Website | https://
|
69
|
-
| Documentation | https://
|
70
|
-
| Wiki | https://gitlab.com/
|
71
|
-
| CI | https://gitlab.com/
|
83
|
+
| Website | https://os85.gitlab.io/rodauth-oauth/ |
|
84
|
+
| Documentation | https://os85.gitlab.io/rodauth-oauth/rdoc/ |
|
85
|
+
| Wiki | https://gitlab.com/os85/rodauth-oauth/wikis/home |
|
86
|
+
| CI | https://gitlab.com/os85/rodauth-oauth/pipelines |
|
72
87
|
|
73
88
|
## Articles
|
74
89
|
|
@@ -132,12 +147,12 @@ end
|
|
132
147
|
|
133
148
|
### Example (TL;DR)
|
134
149
|
|
135
|
-
Just [check our example applications](https://gitlab.com/
|
150
|
+
Just [check our example applications](https://gitlab.com/os85/rodauth-oauth/-/tree/master/examples/).
|
136
151
|
|
137
152
|
|
138
153
|
### Database migrations
|
139
154
|
|
140
|
-
You have to generate database tables for accounts, oauth applications, grants and tokens. In order for you to hit the ground running, [here's a set of migrations (using `sequel`) to generate the needed tables](https://gitlab.com/
|
155
|
+
You have to generate database tables for accounts, oauth applications, grants and tokens. In order for you to hit the ground running, [here's a set of migrations (using `sequel`) to generate the needed tables](https://gitlab.com/os85/rodauth-oauth/-/tree/master/test/migrate) (omit the first 2 if you already have account tables, and [follow recommendations from rodauth accordingly](https://github.com/jeremyevans/rodauth)).
|
141
156
|
|
142
157
|
You can change column names or even use existing tables, however, be aware that you'll have to define new column accessors at the `rodauth` plugin declaration level. Let's say, for instance, you'd like to change the `oauth_grants` table name to `access_grants`, and it's `code` column to `authorization_code`; then, you'd have to do the following:
|
143
158
|
|
@@ -270,7 +285,7 @@ end
|
|
270
285
|
|
271
286
|
`rodauth-oauth` supports translating all user-facing text found in all pages and forms, by integrating with [rodauth-i18n](https://github.com/janko/rodauth-i18n). Just set it up in your application and `rodauth` configuration.
|
272
287
|
|
273
|
-
Default translations shipping with `rodauth-oauth` can be found [in this directory](https://gitlab.com/
|
288
|
+
Default translations shipping with `rodauth-oauth` can be found [in this directory](https://gitlab.com/os85/rodauth-oauth/-/tree/master/locales). If they're not available for the languages you'd like to support, consider getting them translated from the english text, and contributing them to this repository via a Merge Request.
|
274
289
|
|
275
290
|
(This feature is available since `v0.7`.)
|
276
291
|
|
@@ -289,4 +304,4 @@ After checking out the repo, run `bundle install` to install dependencies. Then,
|
|
289
304
|
|
290
305
|
## Contributing
|
291
306
|
|
292
|
-
Bug reports and pull requests are welcome on Gitlab at https://gitlab.com/
|
307
|
+
Bug reports and pull requests are welcome on Gitlab at https://gitlab.com/os85/rodauth-oauth.
|
data/doc/release_notes/0_1_0.md
CHANGED
@@ -12,9 +12,9 @@ plugin :rodauth do
|
|
12
12
|
end
|
13
13
|
```
|
14
14
|
|
15
|
-
For more info about integrating it, [check the wiki](https://gitlab.com/
|
15
|
+
For more info about integrating it, [check the wiki](https://gitlab.com/os85/rodauth-oauth/-/wikis/home#openid-connect-since-v01).
|
16
16
|
|
17
|
-
It supports omniauth openID integrations out-of-the-box, [check the OpenID example, which integrates with omniauth_openid_connect](https://gitlab.com/
|
17
|
+
It supports omniauth openID integrations out-of-the-box, [check the OpenID example, which integrates with omniauth_openid_connect](https://gitlab.com/os85/rodauth-oauth/-/tree/master/examples).
|
18
18
|
|
19
19
|
#### Improvements
|
20
20
|
|
data/doc/release_notes/0_2_0.md
CHANGED
@@ -12,7 +12,7 @@ plugin :rodauth do
|
|
12
12
|
end
|
13
13
|
```
|
14
14
|
|
15
|
-
For more info about integrating it, [check the wiki](https://gitlab.com/
|
15
|
+
For more info about integrating it, [check the wiki](https://gitlab.com/os85/rodauth-oauth/-/wikis/SAML-Assertion-Access-Tokens).
|
16
16
|
|
17
17
|
##### Supporting rotating keys
|
18
18
|
|
data/doc/release_notes/0_3_0.md
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
#### Improvements
|
8
8
|
|
9
9
|
|
10
|
-
* Support for the OIDC authorize [`prompt` parameter](https://openid.net/specs/openid-connect-core-1_0.html) (sectionn 3.1.2.1). It supports the `none`, `login` and `consent` out-of-the-box, while providing support for `select-account` when paired with [rodauth-select-account, a rodauth feature to handle multiple accounts in the same session](https://gitlab.com/
|
10
|
+
* Support for the OIDC authorize [`prompt` parameter](https://openid.net/specs/openid-connect-core-1_0.html) (sectionn 3.1.2.1). It supports the `none`, `login` and `consent` out-of-the-box, while providing support for `select-account` when paired with [rodauth-select-account, a rodauth feature to handle multiple accounts in the same session](https://gitlab.com/os85/rodauth-select-account).
|
11
11
|
|
12
12
|
* Refresh Tokens are now expirable. The refresh token expiration period is governed by the `oauth_refresh_token_expires_in` option (default: 1 year), and is the period for which a refresh token can be used after its respective access token expired.
|
13
13
|
|
data/doc/release_notes/0_5_0.md
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
#### RP-Initiated Logout
|
4
4
|
|
5
|
-
The `:oidc` plugin can now do [RP-Initiated Logout](https://gitlab.com/
|
5
|
+
The `:oidc` plugin can now do [RP-Initiated Logout](https://gitlab.com/os85/rodauth-oauth/-/wikis/RP-Initiated-Logout). It's disabled by default, so read the docs to learn how to enable it.
|
6
6
|
|
7
7
|
#### Security
|
8
8
|
|
9
9
|
The `:oauth_jwt` (and by association, `:oidc`) plugin(s) verifies the claims of used JWT tokens. This is a **very important security fix**, as without it, there is no protection against replay attacks and other types of misuse of the JWT token.
|
10
10
|
|
11
|
-
A new auth method, `generate_jti(claims)`, was [added to the list of oauth_jwt plugin options](https://gitlab.com/
|
11
|
+
A new auth method, `generate_jti(claims)`, was [added to the list of oauth_jwt plugin options](https://gitlab.com/os85/rodauth-oauth/-/wikis/JWT-Access-Tokens#rodauth-options). By default, it'll hash the `aud` and `iat` claims together, but you can overwrite how this is done.
|
data/doc/release_notes/0_8_0.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
* Device code grant
|
6
6
|
|
7
|
-
`rodauth-oauth` now supports the [Device code grant RFC](https://gitlab.com/
|
7
|
+
`rodauth-oauth` now supports the [Device code grant RFC](https://gitlab.com/os85/rodauth-oauth/-/wikis/Device-Grant), via the `oauth_device_grant` feature.
|
8
8
|
|
9
9
|
* OAuth Tokens Management
|
10
10
|
|
@@ -12,7 +12,7 @@ An OAuth Tokens Management Dashboard is now provided (via `r.oauth_tokens` call
|
|
12
12
|
|
13
13
|
* Assertion Framework (+ SAML and JWT Bearer Grant)
|
14
14
|
|
15
|
-
A new plugin, `oauth_assertion_base`, was introduced to provide a baseline for implementing custom Bearer Assertion as per the [OAuth Client Assertion Framework RFC](https://gitlab.com/
|
15
|
+
A new plugin, `oauth_assertion_base`, was introduced to provide a baseline for implementing custom Bearer Assertion as per the [OAuth Client Assertion Framework RFC](https://gitlab.com/os85/rodauth-oauth/-/wikis/Client-Assertion-Framework). This in turn was used to refactor and reintroduce the [oauth_saml_bearer_grant](https://gitlab.com/os85/rodauth-oauth/-/wikis/SAML-Bearer-Assertions) and the [oauth_jwt_bearer_grant](https://gitlab.com/os85/rodauth-oauth/-/wikis/JWT-Bearer-Assertions) features, which implement the respective and most recent version of the assertion RFCs.
|
16
16
|
|
17
17
|
(as a result, `oauth_saml` was removed, which implemented a very old draft version of the SAML Bearer spec).
|
18
18
|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
## 1.0.0 (15/12/2022)
|
2
|
+
|
3
|
+
## Highlights
|
4
|
+
|
5
|
+
rodauth-oauth is now [OpenID certified](https://openid.net/certification/) for the following certification profiles:
|
6
|
+
|
7
|
+
* Basic OP
|
8
|
+
* Implicit OP
|
9
|
+
* Hybrid OP
|
10
|
+
* Config OP
|
11
|
+
* Dynamic OP
|
12
|
+
* Form Post OP
|
13
|
+
|
14
|
+
and passes the conformance tests for RP-Initiated Logout OP.
|
15
|
+
|
16
|
+
The OIDC server used to run the test can be found [here](https://gitlab.com/os85/rodauth-oauth/-/blob/master/examples/oidc/authentication_server.rb) and deployed [here](https://rodauth-oauth-oidc.onrender.com).
|
17
|
+
|
18
|
+
### Breaking changes
|
19
|
+
|
20
|
+
The full description of breaking changes, and suggestions on how to make the migration smoother, can be found in the [migration guide](https://gitlab.com/os85/rodauth-oauth/-/blob/6465b8522a78cf0037a55d3d4b81f68f7811be68/MIGRATION-GUIDE-v1.md).
|
21
|
+
|
22
|
+
A short list of the main highlights:
|
23
|
+
|
24
|
+
|
25
|
+
* Ruby 2.5 or higher is required.
|
26
|
+
* `oauth_http_mac` feature removed.
|
27
|
+
* `oauth_tokens` table (and resource) were removed (only `oauth_applications` and `oauth_grants`, access and refresh tokens are now properties of the latter).
|
28
|
+
* access and refresh tokens hashed by default when stored in the database.
|
29
|
+
* default oauth response mode is `"form_post"`.
|
30
|
+
* oauth specific features require explicit enablement of respective features (no more `enable :oauth`)
|
31
|
+
* refresh token policy is "rotation" by default
|
32
|
+
* homepage url is no longer a client application required property.
|
33
|
+
* OIDC RP-initiated logout extracted into `oidc_rp_initiated_logout` feature.
|
34
|
+
|
35
|
+
### Features
|
36
|
+
|
37
|
+
The following helpers are exposed in the `rodauth` object:
|
38
|
+
|
39
|
+
* `current_oauth_account` - returns the dataset row for the `rodauth` account associated to an oauth access token in the "authorization" header.
|
40
|
+
* `current_oauth_application` - returns the dataset row for the oauth application associated to an oauth access token in the "authorization" header.
|
41
|
+
|
42
|
+
When used in `rails` via `rodauth-rails`, both are exposed directly as controller helpers.
|
43
|
+
|
44
|
+
#### `oauth_resource_server` plugin
|
45
|
+
|
46
|
+
This plugin can be used as a convenience when configuring resource servers.
|
47
|
+
|
48
|
+
#### JAR support for request_uri query param
|
49
|
+
|
50
|
+
The `oauth_jwt_secured_authorization_request` plugin now supports a `request_uri` query param as well.
|
51
|
+
|
52
|
+
#### OIDC features
|
53
|
+
|
54
|
+
* The `oidc` plugin supports [essential claims](https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter), via the `claims` authorization request query parameter.
|
55
|
+
* id token built with `"c_hash"` and `"at_hash"` claims when they should.
|
56
|
+
|
57
|
+
### Improvements
|
58
|
+
|
59
|
+
* `:oauth_introspect` plugin: OAuth introspection endpoint exposes the token's `"username"` claim.
|
60
|
+
* endpoint client authentication supports "client credentials grant" access tokens.
|
61
|
+
* `acr_values_supported` exposed in the openid configuration.
|
62
|
+
* `oauth_request_object_signing_alg_allow_none` enables `"none"` as an accepted request object signing alg when `true` (`false` by default).
|
63
|
+
* OIDC `offline_access` supported.
|
64
|
+
|
65
|
+
### Bugfixes
|
66
|
+
|
67
|
+
* fixed `oidc` calculation of `"auth_time"` claim.
|
68
|
+
* JWT: "sub" is now always a string.
|
69
|
+
* `response_type` is now an authorization request required parameter (as per the RFC).
|
70
|
+
* `state` is now passed along when redirecting from authorization requests with `error`;
|
71
|
+
* access token can now be read from POST body or GET query params (as per the RFC).
|
72
|
+
* id token no longer shipping with claims with `null` value;
|
73
|
+
* id token no longer encoding claims by default (only when `response_type=id_token`, as per the RFC).
|
74
|
+
* support "JWT without kid" when doing jwt decoding for JWT tokens not generated in the provider (such as request objects).
|
75
|
+
* Set `iss` and `aud` claims in the Userinfo JWT response.
|
76
|
+
* Make sure errors are also delivered via form POST, when `response_mode=form_post`.
|
77
|
+
* Authorization request now shows an error page when `response_type` or `client_id` are missing, or `redirect_uri` is missing or invalid; a new `"authorize_error"` template is invoked in such cases.
|
78
|
+
* oidc: nonce present in id token when using the "id_token token" response type.
|
79
|
+
* error parameter delivered in URL fragment when failing an implicit grant autorization request.
|
@@ -0,0 +1,9 @@
|
|
1
|
+
## 1.0.0 (10/01/2023)
|
2
|
+
|
3
|
+
## Features
|
4
|
+
|
5
|
+
### Loopback Interface Redirection URI support
|
6
|
+
|
7
|
+
https://www.rfc-editor.org/rfc/rfc8252#section-7.3
|
8
|
+
|
9
|
+
Redirect URIs based on loopback addresses ("127.0.0.1", "::1") are now supported when used in an authorization request with an ephemeral port (@avdigrimm).
|
@@ -120,7 +120,6 @@ module Rodauth
|
|
120
120
|
return super unless supported_grant_type?(grant_type, "authorization_code")
|
121
121
|
|
122
122
|
grant_params = {
|
123
|
-
oauth_grants_type_column => grant_type,
|
124
123
|
oauth_grants_code_column => param("code"),
|
125
124
|
oauth_grants_redirect_uri_column => param("redirect_uri"),
|
126
125
|
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column]
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "ipaddr"
|
3
4
|
require "rodauth/oauth"
|
4
5
|
|
5
6
|
module Rodauth
|
@@ -10,6 +11,7 @@ module Rodauth
|
|
10
11
|
after "authorize"
|
11
12
|
|
12
13
|
view "authorize", "Authorize", "authorize"
|
14
|
+
view "authorize_error", "Authorize Error", "authorize_error"
|
13
15
|
|
14
16
|
button "Authorize", "oauth_authorize"
|
15
17
|
button "Back to Client Application", "oauth_authorize_post"
|
@@ -24,7 +26,7 @@ module Rodauth
|
|
24
26
|
translatable_method :oauth_applications_tos_uri_label, "Terms of service URL"
|
25
27
|
translatable_method :oauth_applications_policy_uri_label, "Policy URL"
|
26
28
|
translatable_method :oauth_unsupported_response_type_message, "Unsupported response type"
|
27
|
-
translatable_method :oauth_authorize_parameter_required, "'%<parameter>s'
|
29
|
+
translatable_method :oauth_authorize_parameter_required, "Invalid or missing '%<parameter>s'"
|
28
30
|
|
29
31
|
# /authorize
|
30
32
|
auth_server_route(:authorize) do |r|
|
@@ -65,7 +67,16 @@ module Rodauth
|
|
65
67
|
private
|
66
68
|
|
67
69
|
def validate_authorize_params
|
68
|
-
|
70
|
+
redirect_authorize_error("client_id") unless oauth_application
|
71
|
+
|
72
|
+
redirect_uris = oauth_application[oauth_applications_redirect_uri_column].split(" ")
|
73
|
+
|
74
|
+
if (redirect_uri = param_or_nil("redirect_uri"))
|
75
|
+
normalized_redirect_uri = normalize_redirect_uri_for_comparison(redirect_uri)
|
76
|
+
redirect_authorize_error("redirect_uri") unless redirect_uris.include?(normalized_redirect_uri)
|
77
|
+
elsif redirect_uris.size > 1
|
78
|
+
redirect_authorize_error("redirect_uri")
|
79
|
+
end
|
69
80
|
|
70
81
|
redirect_response_error("unsupported_response_type") unless check_valid_response_type?
|
71
82
|
|
@@ -80,18 +91,6 @@ module Rodauth
|
|
80
91
|
false
|
81
92
|
end
|
82
93
|
|
83
|
-
def check_valid_redirect_uri?
|
84
|
-
application_redirect_uris = oauth_application[oauth_applications_redirect_uri_column].split(" ")
|
85
|
-
|
86
|
-
if (redirect_uri = param_or_nil("redirect_uri"))
|
87
|
-
application_redirect_uris.include?(redirect_uri)
|
88
|
-
else
|
89
|
-
set_error_flash(oauth_authorize_parameter_required(parameter: "redirect_uri")) if application_redirect_uris.size > 1
|
90
|
-
|
91
|
-
true
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
94
|
ACCESS_TYPES = %w[offline online].freeze
|
96
95
|
|
97
96
|
def check_valid_access_type?
|
@@ -127,6 +126,21 @@ module Rodauth
|
|
127
126
|
request.env["REQUEST_METHOD"] = "POST"
|
128
127
|
end
|
129
128
|
|
129
|
+
def redirect_authorize_error(parameter, referer = request.referer || default_redirect)
|
130
|
+
error_message = oauth_authorize_parameter_required(parameter: parameter)
|
131
|
+
|
132
|
+
if accepts_json?
|
133
|
+
status_code = oauth_invalid_response_status
|
134
|
+
|
135
|
+
throw_json_response_error(status_code, "invalid_request", error_message)
|
136
|
+
else
|
137
|
+
scope.instance_variable_set(:@error, error_message)
|
138
|
+
scope.instance_variable_set(:@back_url, referer)
|
139
|
+
|
140
|
+
return_response(authorize_error_view)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
130
144
|
def authorization_required
|
131
145
|
if accepts_json?
|
132
146
|
throw_json_response_error(oauth_authorization_required_error_status, "invalid_client")
|
@@ -199,5 +213,26 @@ module Rodauth
|
|
199
213
|
end
|
200
214
|
create_params[oauth_grants_code_column]
|
201
215
|
end
|
216
|
+
|
217
|
+
def normalize_redirect_uri_for_comparison(redirect_uri)
|
218
|
+
uri = URI(redirect_uri)
|
219
|
+
|
220
|
+
return redirect_uri unless uri.scheme == "http" && uri.port
|
221
|
+
|
222
|
+
hostname = uri.hostname
|
223
|
+
|
224
|
+
# https://www.rfc-editor.org/rfc/rfc8252#section-7.3
|
225
|
+
# ignore (potentially ephemeral) port number for native clients per RFC8252
|
226
|
+
begin
|
227
|
+
ip = IPAddr.new(hostname)
|
228
|
+
uri.port = nil if ip.loopback?
|
229
|
+
rescue IPAddr::InvalidAddressError
|
230
|
+
# https://www.rfc-editor.org/rfc/rfc8252#section-8.3
|
231
|
+
# Although the use of localhost is NOT RECOMMENDED, it is still allowed.
|
232
|
+
uri.port = nil if hostname == "localhost"
|
233
|
+
end
|
234
|
+
|
235
|
+
uri.to_s
|
236
|
+
end
|
202
237
|
end
|
203
238
|
end
|
@@ -48,6 +48,16 @@ module Rodauth
|
|
48
48
|
generate_token(grant_params, false)
|
49
49
|
end
|
50
50
|
|
51
|
+
def _redirect_response_error(redirect_url, query_params)
|
52
|
+
response_types = param("response_type").split(/ +/)
|
53
|
+
|
54
|
+
return super if response_types.empty? || response_types == %w[code]
|
55
|
+
|
56
|
+
query_params = query_params.map { |k, v| "#{k}=#{v}" }
|
57
|
+
redirect_url.fragment = query_params.join("&")
|
58
|
+
redirect(redirect_url.to_s)
|
59
|
+
end
|
60
|
+
|
51
61
|
def authorize_response(params, mode)
|
52
62
|
return super unless mode == "fragment"
|
53
63
|
|
@@ -292,6 +292,11 @@ module Rodauth
|
|
292
292
|
end
|
293
293
|
|
294
294
|
super
|
295
|
+
|
296
|
+
return unless (response_type = param_or_nil("response_type"))
|
297
|
+
return unless response_type.include?("id_token")
|
298
|
+
|
299
|
+
redirect_response_error("invalid_request") unless param_or_nil("nonce")
|
295
300
|
end
|
296
301
|
|
297
302
|
def require_authorizable_account
|
@@ -440,10 +445,7 @@ module Rodauth
|
|
440
445
|
_generate_access_token(oauth_grant)
|
441
446
|
end
|
442
447
|
|
443
|
-
|
444
|
-
"code" => authorization_code,
|
445
|
-
**json_access_token_payload(oauth_grants_token_column => access_token)
|
446
|
-
}
|
448
|
+
json_access_token_payload(oauth_grants_token_column => access_token).merge("code" => authorization_code)
|
447
449
|
end
|
448
450
|
|
449
451
|
def create_token(*)
|
@@ -457,6 +459,9 @@ module Rodauth
|
|
457
459
|
|
458
460
|
return unless oauth_scopes.include?("openid")
|
459
461
|
|
462
|
+
signing_algorithm = oauth_application[oauth_applications_id_token_signed_response_alg_column] ||
|
463
|
+
oauth_jwt_keys.keys.first
|
464
|
+
|
460
465
|
id_token_claims = jwt_claims(oauth_grant)
|
461
466
|
|
462
467
|
id_token_claims[:nonce] = oauth_grant[oauth_grants_nonce_column] if oauth_grant[oauth_grants_nonce_column]
|
@@ -466,6 +471,16 @@ module Rodauth
|
|
466
471
|
# Time when the End-User authentication occurred.
|
467
472
|
id_token_claims[:auth_time] = get_oidc_account_last_login_at(oauth_grant[oauth_grants_account_id_column]).to_i
|
468
473
|
|
474
|
+
# Access Token hash value.
|
475
|
+
if (access_token = oauth_grant[oauth_grants_token_column])
|
476
|
+
id_token_claims[:at_hash] = id_token_hash(access_token, signing_algorithm)
|
477
|
+
end
|
478
|
+
|
479
|
+
# code hash value.
|
480
|
+
if (code = oauth_grant[oauth_grants_code_column])
|
481
|
+
id_token_claims[:c_hash] = id_token_hash(code, signing_algorithm)
|
482
|
+
end
|
483
|
+
|
469
484
|
account = db[accounts_table].where(account_id_column => oauth_grant[oauth_grants_account_id_column]).first
|
470
485
|
|
471
486
|
# this should never happen!
|
@@ -488,10 +503,7 @@ module Rodauth
|
|
488
503
|
|
489
504
|
params = {
|
490
505
|
jwks: oauth_application_jwks(oauth_application),
|
491
|
-
signing_algorithm:
|
492
|
-
oauth_application[oauth_applications_id_token_signed_response_alg_column] ||
|
493
|
-
oauth_jwt_keys.keys.first
|
494
|
-
),
|
506
|
+
signing_algorithm: signing_algorithm,
|
495
507
|
encryption_algorithm: oauth_application[oauth_applications_id_token_encrypted_response_alg_column],
|
496
508
|
encryption_method: oauth_application[oauth_applications_id_token_encrypted_response_enc_column]
|
497
509
|
}.compact
|
@@ -655,7 +667,8 @@ module Rodauth
|
|
655
667
|
when "id_token token"
|
656
668
|
redirect_response_error("invalid_request") unless supports_token_response_type?
|
657
669
|
|
658
|
-
|
670
|
+
grant_params = oidc_grant_params.merge(oauth_grants_type_column => "hybrid")
|
671
|
+
oauth_grant = _do_authorize_token(grant_params)
|
659
672
|
generate_id_token(oauth_grant)
|
660
673
|
|
661
674
|
response_params.replace(json_access_token_payload(oauth_grant))
|
@@ -664,6 +677,7 @@ module Rodauth
|
|
664
677
|
|
665
678
|
params = create_oauth_grant_with_token
|
666
679
|
oauth_grant = valid_oauth_grant_ds.where(oauth_grants_code_column => params["code"]).first
|
680
|
+
oauth_grant[oauth_grants_token_column] = params["access_token"]
|
667
681
|
generate_id_token(oauth_grant)
|
668
682
|
|
669
683
|
response_params.replace(params.merge("id_token" => oauth_grant[:id_token]))
|
@@ -785,5 +799,19 @@ module Rodauth
|
|
785
799
|
end
|
786
800
|
return_response(jwt)
|
787
801
|
end
|
802
|
+
|
803
|
+
def id_token_hash(hash, algo)
|
804
|
+
digest = case algo
|
805
|
+
when /256/ then Digest::SHA256
|
806
|
+
when /384/ then Digest::SHA384
|
807
|
+
when /512/ then Digest::SHA512
|
808
|
+
end
|
809
|
+
|
810
|
+
return unless digest
|
811
|
+
|
812
|
+
hash = digest.digest(hash)
|
813
|
+
hash = hash[0...hash.size / 2]
|
814
|
+
Base64.urlsafe_encode64(hash).tr("=", "")
|
815
|
+
end
|
788
816
|
end
|
789
817
|
end
|
@@ -19,31 +19,35 @@ module Rodauth
|
|
19
19
|
catch_error do
|
20
20
|
validate_oidc_logout_params
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
22
|
+
oauth_application = nil
|
23
|
+
|
24
|
+
if (id_token_hint = param_or_nil("id_token_hint"))
|
25
|
+
#
|
26
|
+
# why this is done:
|
27
|
+
#
|
28
|
+
# we need to decode the id token in order to get the application, because, if the
|
29
|
+
# signing key is application-specific, we don't know how to verify the signature
|
30
|
+
# beforehand. Hence, we have to do it twice: decode-and-do-not-verify, initialize
|
31
|
+
# the @oauth_application, and then decode-and-verify.
|
32
|
+
#
|
33
|
+
claims = jwt_decode(id_token_hint, verify_claims: false)
|
34
|
+
|
35
|
+
redirect_logout_with_error(oauth_invalid_client_message) unless claims
|
36
|
+
|
37
|
+
oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["aud"]).first
|
38
|
+
oauth_grant = db[oauth_grants_table]
|
39
|
+
.where(
|
40
|
+
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
|
41
|
+
oauth_grants_account_id_column => account_id
|
42
|
+
).first
|
43
|
+
|
44
|
+
# check whether ID token belongs to currently logged-in user
|
45
|
+
redirect_logout_with_error(oauth_invalid_client_message) unless oauth_grant && claims["sub"] == jwt_subject(oauth_grant,
|
46
|
+
oauth_application)
|
47
|
+
|
48
|
+
# When an id_token_hint parameter is present, the OP MUST validate that it was the issuer of the ID Token.
|
49
|
+
redirect_logout_with_error(oauth_invalid_client_message) unless claims && claims["iss"] == oauth_jwt_issuer
|
50
|
+
end
|
47
51
|
|
48
52
|
# now let's logout from IdP
|
49
53
|
transaction do
|
@@ -92,7 +96,6 @@ module Rodauth
|
|
92
96
|
# Logout
|
93
97
|
|
94
98
|
def validate_oidc_logout_params
|
95
|
-
redirect_logout_with_error(oauth_invalid_client_message) unless param_or_nil("id_token_hint")
|
96
99
|
# check if valid token hint type
|
97
100
|
return unless (redirect_uri = param_or_nil("post_logout_redirect_uri"))
|
98
101
|
|
data/locales/en.yml
CHANGED
@@ -9,6 +9,7 @@ en:
|
|
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
|
authorize_page_lead: "The application %{name} would like to access your data."
|
12
|
+
authorize_error_page_title: "Authorize Error"
|
12
13
|
oauth_cancel_button: "Cancel"
|
13
14
|
oauth_applications_page_title: "Oauth Applications"
|
14
15
|
oauth_application_page_title: "Oauth Application"
|
@@ -65,5 +66,5 @@ en:
|
|
65
66
|
oauth_unsupported_transform_algorithm_message: "transform algorithm not supported"
|
66
67
|
oauth_invalid_request_object_message: "request object is invalid"
|
67
68
|
oauth_invalid_scope_message: "The Access Token expired"
|
68
|
-
oauth_authorize_parameter_required: "'%{parameter}'
|
69
|
+
oauth_authorize_parameter_required: "Invalid or missing '%{parameter}'"
|
69
70
|
oauth_invalid_post_logout_redirect_uri_message: "Invalid post logout redirect URI"
|
data/locales/pt.yml
CHANGED
@@ -9,6 +9,7 @@ pt:
|
|
9
9
|
user_code_not_found_error_flash: "Não existe nenhum dispositivo a ser autorizado com o código de usuário inserido"
|
10
10
|
authorize_page_title: "Autorizar"
|
11
11
|
authorize_page_lead: "O aplicativo %{name} gostaria de aceder aos seus dados."
|
12
|
+
authorize_error_page_title: Erro de autorização
|
12
13
|
oauth_cancel_button: "Cancelar"
|
13
14
|
oauth_applications_page_title: "Aplicativos OAuth"
|
14
15
|
oauth_application_page_title: "Aplicativo Oauth"
|
@@ -65,5 +66,5 @@ pt:
|
|
65
66
|
oauth_unsupported_transform_algorithm_message: "algoritmo de transformação não suportado"
|
66
67
|
oauth_invalid_request_object_message: "request_object é inválido"
|
67
68
|
oauth_invalid_scope_message: "O Token de acesso expirou"
|
68
|
-
oauth_authorize_parameter_required: "'%{parameter}'
|
69
|
+
oauth_authorize_parameter_required: "'%{parameter}' inválido ou em falta"
|
69
70
|
oauth_invalid_post_logout_redirect_uri_message: "URI de redireccionamento pós-logout inválido"
|
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: 1.
|
4
|
+
version: 1.1.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:
|
11
|
+
date: 2023-01-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rodauth
|
@@ -66,8 +66,8 @@ extra_rdoc_files:
|
|
66
66
|
- doc/release_notes/0_9_1.md
|
67
67
|
- doc/release_notes/0_9_2.md
|
68
68
|
- doc/release_notes/0_9_3.md
|
69
|
-
- doc/release_notes/
|
70
|
-
- doc/release_notes/
|
69
|
+
- doc/release_notes/1_0_0.md
|
70
|
+
- doc/release_notes/1_1_0.md
|
71
71
|
files:
|
72
72
|
- CHANGELOG.md
|
73
73
|
- LICENSE.txt
|
@@ -105,12 +105,13 @@ files:
|
|
105
105
|
- doc/release_notes/0_9_1.md
|
106
106
|
- doc/release_notes/0_9_2.md
|
107
107
|
- doc/release_notes/0_9_3.md
|
108
|
-
- doc/release_notes/
|
109
|
-
- doc/release_notes/
|
108
|
+
- doc/release_notes/1_0_0.md
|
109
|
+
- doc/release_notes/1_1_0.md
|
110
110
|
- lib/generators/rodauth/oauth/install_generator.rb
|
111
111
|
- lib/generators/rodauth/oauth/templates/app/models/oauth_application.rb
|
112
112
|
- lib/generators/rodauth/oauth/templates/app/models/oauth_grant.rb
|
113
113
|
- lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb
|
114
|
+
- lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize_error.erb
|
114
115
|
- lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb
|
115
116
|
- lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb
|
116
117
|
- lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb
|
@@ -155,6 +156,7 @@ files:
|
|
155
156
|
- locales/en.yml
|
156
157
|
- locales/pt.yml
|
157
158
|
- templates/authorize.str
|
159
|
+
- templates/authorize_error.str
|
158
160
|
- templates/client_secret_field.str
|
159
161
|
- templates/description_field.str
|
160
162
|
- templates/device_search.str
|
@@ -169,15 +171,15 @@ files:
|
|
169
171
|
- templates/oauth_grants.str
|
170
172
|
- templates/redirect_uri_field.str
|
171
173
|
- templates/scope_field.str
|
172
|
-
homepage: https://gitlab.com/
|
174
|
+
homepage: https://gitlab.com/os85/rodauth-oauth
|
173
175
|
licenses:
|
174
176
|
- Apache-2.0
|
175
177
|
metadata:
|
176
|
-
homepage_uri: https://
|
177
|
-
documentation_uri: https://
|
178
|
-
bug_tracker_uri: https://gitlab.com/
|
179
|
-
source_code_uri: https://gitlab.com/
|
180
|
-
changelog_uri: https://gitlab.com/
|
178
|
+
homepage_uri: https://os85.gitlab.io/rodauth-oauth/
|
179
|
+
documentation_uri: https://os85.gitlab.io/rodauth-oauth/rdoc/
|
180
|
+
bug_tracker_uri: https://gitlab.com/os85/rodauth-oauth/issues
|
181
|
+
source_code_uri: https://gitlab.com/os85/rodauth-oauth
|
182
|
+
changelog_uri: https://gitlab.com/os85/rodauth-oauth/-/blob/master/CHANGELOG.md
|
181
183
|
rubygems_mfa_required: 'true'
|
182
184
|
post_install_message:
|
183
185
|
rdoc_options: []
|
@@ -190,11 +192,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
190
192
|
version: 2.5.0
|
191
193
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
192
194
|
requirements:
|
193
|
-
- - "
|
195
|
+
- - ">="
|
194
196
|
- !ruby/object:Gem::Version
|
195
|
-
version:
|
197
|
+
version: '0'
|
196
198
|
requirements: []
|
197
|
-
rubygems_version: 3.
|
199
|
+
rubygems_version: 3.3.7
|
198
200
|
signing_key:
|
199
201
|
specification_version: 4
|
200
202
|
summary: Implementation of the OAuth 2.0 protocol on top of rodauth.
|
@@ -1,38 +0,0 @@
|
|
1
|
-
## 1.0.0-beta1 (21/10/2022)
|
2
|
-
|
3
|
-
### Breaking changes
|
4
|
-
|
5
|
-
The full description of breaking changes, and suggestions on how to make the migration smoother, can be found in the [migration guide](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/blob/6465b8522a78cf0037a55d3d4b81f68f7811be68/MIGRATION-GUIDE-v1.md).
|
6
|
-
|
7
|
-
A short list of the main highlights:
|
8
|
-
|
9
|
-
|
10
|
-
* Ruby 2.5 or higher is required.
|
11
|
-
* `oauth_http_mac` feature removed.
|
12
|
-
* `oauth_tokens` table (and resource) were removed (only `oauth_applications` and `oauth_grants`, access and refresh tokens are now properties of the latter).
|
13
|
-
* access and refresh tokens hashed by default when stored in the database.
|
14
|
-
* default oauth response mode is `"form_post"`.
|
15
|
-
* oauth specific features require explicit enablement of respective features (no more `enable :oauth`)
|
16
|
-
* refresh token policy is "rotation" by default
|
17
|
-
|
18
|
-
### Features
|
19
|
-
|
20
|
-
The following helpers are exposed in the `rodauth` object:
|
21
|
-
|
22
|
-
* `current_oauth_account` - returns the dataset row for the `rodauth` account associated to an oauth access token in the "authorization" header.
|
23
|
-
* `current_oauth_application` - returns the dataset row for the oauth application associated to an oauth access token in the "authorization" header.
|
24
|
-
|
25
|
-
When used in `rails` via `rodauth-rails`, both are exposed directly as controller helpers.
|
26
|
-
|
27
|
-
#### `oauth_resource_server` plugin
|
28
|
-
|
29
|
-
This plugin can be used as a convenience when configuring resource servers.
|
30
|
-
|
31
|
-
### Improvements
|
32
|
-
|
33
|
-
* `:oauth_introspect` plugin: OAuth introspection endpoint exposes the token's `"username"` claim.
|
34
|
-
* endpoint client authentication supports "client credentials grant" access tokens.
|
35
|
-
|
36
|
-
### Bugfixes
|
37
|
-
|
38
|
-
* fixed `oidc` calculation of `"auth_time"` claim.
|
@@ -1,34 +0,0 @@
|
|
1
|
-
This version passes the conformance tests for the following OpenID Connect certification profiles:
|
2
|
-
|
3
|
-
* Basic certification
|
4
|
-
* Form-post basic certification
|
5
|
-
* Config certification
|
6
|
-
* Dynamic Config certification (`response_type=code`)
|
7
|
-
|
8
|
-
## Breaking Changes
|
9
|
-
|
10
|
-
* homepage url is no longer a client application required property.
|
11
|
-
* OIDC RP-initiated logout extracted into `oidc_rp_initiated_logout` feature.
|
12
|
-
|
13
|
-
## Features
|
14
|
-
|
15
|
-
* `oauth_jwt_secured_authorization_request` now supports a `request_uri` query param as well.
|
16
|
-
* `oidc` supports essential claims, via the `claims` authorization request query parameter.
|
17
|
-
|
18
|
-
## Improvements
|
19
|
-
|
20
|
-
* exposing `acr_values_supported` in the openid configuration.
|
21
|
-
* `oauth_request_object_signing_alg_allow_none` enables `"none"` as an accepted request object signing alg when `true` (`false` by default).
|
22
|
-
* OIDC `offline_access` supported.
|
23
|
-
|
24
|
-
## Bugfixes
|
25
|
-
|
26
|
-
* JWT: "sub" is now always a string.
|
27
|
-
* `response_type` is now an authorization request required parameter (as per the RFC).
|
28
|
-
* `state` is now passed along when redirecting from authorization requeests with `error`;
|
29
|
-
* access token can now be read from POST body or GET quety params (as per the RFC).
|
30
|
-
* id token no longer shipping with claims with `null` value;
|
31
|
-
* id token no longer encoding claims by default (only when `response_type=id_token`, as per the RFC).
|
32
|
-
* support "JWT without kid" when doing jwt decoding for JWT tokens not generated in the provider (such as request objects).
|
33
|
-
* Set `iss` and `aud` claims in the Userinfo JWT response.
|
34
|
-
* Make sure errors are also delivered via form POST, when `response_mode=form_post`.
|