rodauth-oauth 0.10.4 → 1.0.0.pre.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/MIGRATION-GUIDE-v1.md +286 -0
- data/README.md +22 -30
- data/doc/release_notes/1_0_0_beta1.md +38 -0
- data/lib/generators/rodauth/oauth/install_generator.rb +0 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb +4 -6
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb +1 -1
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb +2 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb +1 -6
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application.html.erb +0 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_grants.html.erb +41 -0
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_applications.html.erb +2 -2
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_grants.html.erb +37 -0
- data/lib/generators/rodauth/oauth/templates/db/migrate/create_rodauth_oauth.rb +18 -29
- data/lib/rodauth/features/oauth_application_management.rb +59 -72
- data/lib/rodauth/features/oauth_assertion_base.rb +19 -23
- data/lib/rodauth/features/oauth_authorization_code_grant.rb +35 -88
- data/lib/rodauth/features/oauth_authorize_base.rb +103 -20
- data/lib/rodauth/features/oauth_base.rb +365 -302
- data/lib/rodauth/features/oauth_client_credentials_grant.rb +20 -18
- data/lib/rodauth/features/{oauth_device_grant.rb → oauth_device_code_grant.rb} +62 -73
- data/lib/rodauth/features/oauth_dynamic_client_registration.rb +46 -28
- data/lib/rodauth/features/oauth_grant_management.rb +70 -0
- data/lib/rodauth/features/oauth_implicit_grant.rb +25 -24
- data/lib/rodauth/features/oauth_jwt.rb +52 -688
- data/lib/rodauth/features/oauth_jwt_base.rb +435 -0
- data/lib/rodauth/features/oauth_jwt_bearer_grant.rb +45 -17
- data/lib/rodauth/features/oauth_jwt_jwks.rb +47 -0
- data/lib/rodauth/features/oauth_jwt_secured_authorization_request.rb +62 -0
- data/lib/rodauth/features/oauth_management_base.rb +2 -0
- data/lib/rodauth/features/oauth_pkce.rb +22 -26
- data/lib/rodauth/features/oauth_resource_indicators.rb +33 -21
- data/lib/rodauth/features/oauth_resource_server.rb +59 -0
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +5 -1
- data/lib/rodauth/features/oauth_token_introspection.rb +76 -46
- data/lib/rodauth/features/oauth_token_revocation.rb +46 -33
- data/lib/rodauth/features/oidc.rb +188 -95
- data/lib/rodauth/features/oidc_dynamic_client_registration.rb +89 -53
- data/lib/rodauth/oauth/database_extensions.rb +8 -6
- data/lib/rodauth/oauth/http_extensions.rb +61 -0
- data/lib/rodauth/oauth/railtie.rb +20 -0
- data/lib/rodauth/oauth/version.rb +1 -1
- data/lib/rodauth/oauth.rb +29 -1
- data/locales/en.yml +32 -22
- data/locales/pt.yml +32 -22
- data/templates/authorize.str +19 -24
- data/templates/device_search.str +1 -1
- data/templates/device_verification.str +2 -2
- data/templates/jwks_field.str +1 -0
- data/templates/new_oauth_application.str +1 -2
- data/templates/oauth_application.str +2 -2
- data/templates/oauth_application_oauth_grants.str +54 -0
- data/templates/oauth_applications.str +2 -2
- data/templates/oauth_grants.str +52 -0
- metadata +20 -16
- data/lib/generators/rodauth/oauth/templates/app/models/oauth_token.rb +0 -4
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_application_oauth_tokens.html.erb +0 -39
- data/lib/generators/rodauth/oauth/templates/app/views/rodauth/oauth_tokens.html.erb +0 -35
- data/lib/rodauth/features/oauth.rb +0 -9
- data/lib/rodauth/features/oauth_http_mac.rb +0 -86
- data/lib/rodauth/features/oauth_token_management.rb +0 -81
- data/lib/rodauth/oauth/refinements.rb +0 -48
- data/templates/jwt_public_key_field.str +0 -4
- data/templates/oauth_application_oauth_tokens.str +0 -52
- data/templates/oauth_tokens.str +0 -50
@@ -1,44 +1,40 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rodauth/oauth
|
3
|
+
require "rodauth/oauth"
|
4
4
|
|
5
5
|
module Rodauth
|
6
6
|
Feature.define(:oauth_pkce, :OauthPkce) do
|
7
|
-
using PrefixExtensions
|
8
|
-
|
9
7
|
depends :oauth_authorization_code_grant
|
10
8
|
|
11
|
-
auth_value_method :
|
12
|
-
|
13
|
-
auth_value_method :oauth_require_pkce, false
|
9
|
+
auth_value_method :oauth_require_pkce, true
|
14
10
|
auth_value_method :oauth_pkce_challenge_method, "S256"
|
15
11
|
|
16
12
|
auth_value_method :oauth_grants_code_challenge_column, :code_challenge
|
17
13
|
auth_value_method :oauth_grants_code_challenge_method_column, :code_challenge_method
|
18
14
|
|
19
|
-
auth_value_method :
|
20
|
-
translatable_method :
|
21
|
-
auth_value_method :
|
22
|
-
translatable_method :
|
15
|
+
auth_value_method :oauth_code_challenge_required_error_code, "invalid_request"
|
16
|
+
translatable_method :oauth_code_challenge_required_message, "code challenge required"
|
17
|
+
auth_value_method :oauth_unsupported_transform_algorithm_error_code, "invalid_request"
|
18
|
+
translatable_method :oauth_unsupported_transform_algorithm_message, "transform algorithm not supported"
|
23
19
|
|
24
20
|
private
|
25
21
|
|
26
|
-
def
|
27
|
-
return
|
22
|
+
def supports_auth_method?(oauth_application, auth_method)
|
23
|
+
return super unless auth_method == "none"
|
28
24
|
|
29
|
-
super
|
25
|
+
request.params.key?("code_verifier") || super
|
30
26
|
end
|
31
27
|
|
32
28
|
def validate_authorize_params
|
33
|
-
validate_pkce_challenge_params
|
29
|
+
validate_pkce_challenge_params
|
34
30
|
|
35
31
|
super
|
36
32
|
end
|
37
33
|
|
38
34
|
def create_oauth_grant(create_params = {})
|
39
35
|
# PKCE flow
|
40
|
-
if
|
41
|
-
code_challenge_method = param_or_nil("code_challenge_method")
|
36
|
+
if (code_challenge = param_or_nil("code_challenge"))
|
37
|
+
code_challenge_method = param_or_nil("code_challenge_method") || oauth_pkce_challenge_method
|
42
38
|
|
43
39
|
create_params[oauth_grants_code_challenge_column] = code_challenge
|
44
40
|
create_params[oauth_grants_code_challenge_method_column] = code_challenge_method
|
@@ -47,18 +43,18 @@ module Rodauth
|
|
47
43
|
super
|
48
44
|
end
|
49
45
|
|
50
|
-
def
|
51
|
-
|
52
|
-
if oauth_grant[oauth_grants_code_challenge_column]
|
53
|
-
code_verifier = param_or_nil("code_verifier")
|
46
|
+
def create_token_from_authorization_code(grant_params, *args, oauth_grant: nil)
|
47
|
+
oauth_grant ||= valid_locked_oauth_grant(grant_params)
|
54
48
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
49
|
+
if oauth_grant[oauth_grants_code_challenge_column]
|
50
|
+
code_verifier = param_or_nil("code_verifier")
|
51
|
+
|
52
|
+
redirect_response_error("invalid_request") unless code_verifier && check_valid_grant_challenge?(oauth_grant, code_verifier)
|
53
|
+
elsif oauth_require_pkce
|
54
|
+
redirect_response_error("code_challenge_required")
|
59
55
|
end
|
60
56
|
|
61
|
-
super
|
57
|
+
super({ oauth_grants_id_column => oauth_grant[oauth_grants_id_column] }, *args, oauth_grant: oauth_grant)
|
62
58
|
end
|
63
59
|
|
64
60
|
def validate_pkce_challenge_params
|
@@ -91,7 +87,7 @@ module Rodauth
|
|
91
87
|
|
92
88
|
def oauth_server_metadata_body(*)
|
93
89
|
super.tap do |data|
|
94
|
-
data[:code_challenge_methods_supported] = oauth_pkce_challenge_method
|
90
|
+
data[:code_challenge_methods_supported] = oauth_pkce_challenge_method
|
95
91
|
end
|
96
92
|
end
|
97
93
|
end
|
@@ -1,14 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rodauth/oauth
|
4
|
-
require "rodauth/oauth/ttl_store"
|
3
|
+
require "rodauth/oauth"
|
5
4
|
|
6
5
|
module Rodauth
|
7
6
|
Feature.define(:oauth_resource_indicators, :OauthResourceIndicators) do
|
8
7
|
depends :oauth_authorize_base
|
9
8
|
|
10
9
|
auth_value_method :oauth_grants_resource_column, :resource
|
11
|
-
auth_value_method :oauth_tokens_resource_column, :resource
|
12
10
|
|
13
11
|
def resource_indicators
|
14
12
|
return @resource_indicators if defined?(@resource_indicators)
|
@@ -38,9 +36,10 @@ module Rodauth
|
|
38
36
|
def require_oauth_authorization(*)
|
39
37
|
super
|
40
38
|
|
41
|
-
|
39
|
+
# done so to support token-in-grant-db, jwt, and resource-server mode
|
40
|
+
token_indicators = authorization_token[oauth_grants_resource_column] || authorization_token["resource"]
|
42
41
|
|
43
|
-
|
42
|
+
return unless token_indicators
|
44
43
|
|
45
44
|
token_indicators = token_indicators.split(" ") if token_indicators.is_a?(String)
|
46
45
|
|
@@ -49,7 +48,7 @@ module Rodauth
|
|
49
48
|
|
50
49
|
private
|
51
50
|
|
52
|
-
def
|
51
|
+
def validate_token_params
|
53
52
|
super
|
54
53
|
|
55
54
|
return unless resource_indicators
|
@@ -59,23 +58,16 @@ module Rodauth
|
|
59
58
|
end
|
60
59
|
end
|
61
60
|
|
62
|
-
def
|
61
|
+
def create_token_from_token(oauth_grant, update_params)
|
63
62
|
return super unless resource_indicators
|
64
63
|
|
65
|
-
return super unless oauth_token[oauth_tokens_oauth_grant_id_column]
|
66
|
-
|
67
|
-
oauth_grant = db[oauth_grants_table].where(
|
68
|
-
oauth_grants_id_column => oauth_token[oauth_tokens_oauth_grant_id_column],
|
69
|
-
oauth_grants_revoked_at_column => nil
|
70
|
-
).first
|
71
|
-
|
72
64
|
grant_indicators = oauth_grant[oauth_grants_resource_column]
|
73
65
|
|
74
66
|
grant_indicators = grant_indicators.split(" ") if grant_indicators.is_a?(String)
|
75
67
|
|
76
68
|
redirect_response_error("invalid_target") unless (grant_indicators - resource_indicators) != grant_indicators
|
77
69
|
|
78
|
-
super(
|
70
|
+
super(oauth_grant, update_params.merge(oauth_grants_resource_column => resource_indicators))
|
79
71
|
end
|
80
72
|
|
81
73
|
def check_valid_no_fragment_uri?(uri)
|
@@ -95,9 +87,11 @@ module Rodauth
|
|
95
87
|
end
|
96
88
|
end
|
97
89
|
|
98
|
-
def
|
90
|
+
def create_token_from_authorization_code(grant_params, *args, oauth_grant: nil)
|
99
91
|
return super unless resource_indicators
|
100
92
|
|
93
|
+
oauth_grant ||= valid_locked_oauth_grant(grant_params)
|
94
|
+
|
101
95
|
redirect_response_error("invalid_target") unless oauth_grant[oauth_grants_resource_column]
|
102
96
|
|
103
97
|
grant_indicators = oauth_grant[oauth_grants_resource_column]
|
@@ -106,7 +100,15 @@ module Rodauth
|
|
106
100
|
|
107
101
|
redirect_response_error("invalid_target") unless (grant_indicators - resource_indicators) != grant_indicators
|
108
102
|
|
109
|
-
|
103
|
+
# update ownership
|
104
|
+
if grant_indicators != resource_indicators
|
105
|
+
oauth_grant = __update_and_return__(
|
106
|
+
db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column]),
|
107
|
+
oauth_grants_resource_column => resource_indicators
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
super({ oauth_grants_id_column => oauth_grant[oauth_grants_id_column] }, *args, oauth_grant: oauth_grant)
|
110
112
|
end
|
111
113
|
|
112
114
|
def create_oauth_grant(create_params = {})
|
@@ -117,12 +119,12 @@ module Rodauth
|
|
117
119
|
end
|
118
120
|
|
119
121
|
module IndicatorIntrospection
|
120
|
-
def json_token_introspect_payload(
|
121
|
-
return super unless
|
122
|
+
def json_token_introspect_payload(grant)
|
123
|
+
return super unless grant[oauth_grants_id_column]
|
122
124
|
|
123
125
|
payload = super
|
124
126
|
|
125
|
-
token_indicators =
|
127
|
+
token_indicators = grant[oauth_grants_resource_column]
|
126
128
|
|
127
129
|
token_indicators = token_indicators.split(" ") if token_indicators.is_a?(String)
|
128
130
|
|
@@ -134,7 +136,7 @@ module Rodauth
|
|
134
136
|
def introspection_request(*)
|
135
137
|
payload = super
|
136
138
|
|
137
|
-
payload[
|
139
|
+
payload[oauth_grants_resource_column] = payload["aud"] if payload["aud"]
|
138
140
|
|
139
141
|
payload
|
140
142
|
end
|
@@ -146,6 +148,16 @@ module Rodauth
|
|
146
148
|
|
147
149
|
super.merge(aud: resource_indicators)
|
148
150
|
end
|
151
|
+
|
152
|
+
def jwt_decode(token, verify_aud: true, **args)
|
153
|
+
claims = super(token, verify_aud: false, **args)
|
154
|
+
|
155
|
+
return claims unless verify_aud
|
156
|
+
|
157
|
+
return unless claims["aud"] && claims["aud"].one? { |aud| request.url.starts_with?(aud) }
|
158
|
+
|
159
|
+
claims
|
160
|
+
end
|
149
161
|
end
|
150
162
|
|
151
163
|
def self.included(rodauth)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
5
|
+
module Rodauth
|
6
|
+
Feature.define(:oauth_resource_server, :OauthResourceServer) do
|
7
|
+
depends :oauth_token_introspection
|
8
|
+
|
9
|
+
auth_value_method :is_authorization_server?, false
|
10
|
+
|
11
|
+
auth_value_methods(
|
12
|
+
:before_introspection_request
|
13
|
+
)
|
14
|
+
|
15
|
+
def authorization_token
|
16
|
+
return @authorization_token if defined?(@authorization_token)
|
17
|
+
|
18
|
+
# check if there is a token
|
19
|
+
bearer_token = fetch_access_token
|
20
|
+
|
21
|
+
return unless bearer_token
|
22
|
+
|
23
|
+
# where in resource server, NOT the authorization server.
|
24
|
+
payload = introspection_request("access_token", bearer_token)
|
25
|
+
|
26
|
+
return unless payload["active"]
|
27
|
+
|
28
|
+
@authorization_token = payload
|
29
|
+
end
|
30
|
+
|
31
|
+
def require_oauth_authorization(*scopes)
|
32
|
+
authorization_required unless authorization_token
|
33
|
+
|
34
|
+
aux_scopes = authorization_token["scope"]
|
35
|
+
|
36
|
+
token_scopes = if aux_scopes
|
37
|
+
aux_scopes.split(oauth_scope_separator)
|
38
|
+
else
|
39
|
+
[]
|
40
|
+
end
|
41
|
+
|
42
|
+
authorization_required unless scopes.any? { |scope| token_scopes.include?(scope) }
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def introspection_request(token_type_hint, token)
|
48
|
+
introspect_url = URI("#{authorization_server_url}#{introspect_path}")
|
49
|
+
|
50
|
+
response = http_request(introspect_url, { "token_type_hint" => token_type_hint, "token" => token }) do |request|
|
51
|
+
before_introspection_request(request)
|
52
|
+
end
|
53
|
+
|
54
|
+
JSON.parse(response.body)
|
55
|
+
end
|
56
|
+
|
57
|
+
def before_introspection_request(request); end
|
58
|
+
end
|
59
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "onelogin/ruby-saml"
|
4
|
+
require "rodauth/oauth"
|
4
5
|
|
5
6
|
module Rodauth
|
6
7
|
Feature.define(:oauth_saml_bearer_grant, :OauthSamlBearerGrant) do
|
@@ -22,6 +23,10 @@ module Rodauth
|
|
22
23
|
:account_from_saml2_bearer_assertion
|
23
24
|
)
|
24
25
|
|
26
|
+
def oauth_grant_types_supported
|
27
|
+
super | %w[urn:ietf:params:oauth:grant-type:saml2-bearer]
|
28
|
+
end
|
29
|
+
|
25
30
|
private
|
26
31
|
|
27
32
|
def require_oauth_application_from_saml2_bearer_assertion_issuer(assertion)
|
@@ -94,7 +99,6 @@ module Rodauth
|
|
94
99
|
|
95
100
|
def oauth_server_metadata_body(*)
|
96
101
|
super.tap do |data|
|
97
|
-
data[:grant_types_supported] << "urn:ietf:params:oauth:grant-type:saml2-bearer"
|
98
102
|
data[:token_endpoint_auth_methods_supported] << "urn:ietf:params:oauth:client-assertion-type:saml2-bearer"
|
99
103
|
end
|
100
104
|
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rodauth/oauth"
|
4
|
+
require "rodauth/oauth/http_extensions"
|
5
|
+
|
3
6
|
module Rodauth
|
4
7
|
Feature.define(:oauth_token_introspection, :OauthTokenIntrospection) do
|
5
8
|
depends :oauth_base
|
@@ -7,47 +10,44 @@ module Rodauth
|
|
7
10
|
before "introspect"
|
8
11
|
|
9
12
|
auth_value_methods(
|
10
|
-
:
|
13
|
+
:resource_owner_identifier
|
11
14
|
)
|
12
15
|
|
13
16
|
# /introspect
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
+
auth_server_route(:introspect) do |r|
|
18
|
+
require_oauth_application_for_introspect
|
17
19
|
before_introspect_route
|
18
|
-
require_oauth_application
|
19
20
|
|
20
21
|
r.post do
|
21
22
|
catch_error do
|
22
|
-
|
23
|
+
validate_introspect_params
|
24
|
+
|
25
|
+
token_type_hint = param_or_nil("token_type_hint")
|
23
26
|
|
24
27
|
before_introspect
|
25
|
-
|
26
|
-
when "access_token"
|
27
|
-
|
28
|
+
oauth_grant = case token_type_hint
|
29
|
+
when "access_token", nil
|
30
|
+
if features.include?(:oauth_jwt) && oauth_jwt_access_tokens
|
31
|
+
jwt_decode(param("token"))
|
32
|
+
else
|
33
|
+
oauth_grant_by_token(param("token"))
|
34
|
+
end
|
28
35
|
when "refresh_token"
|
29
|
-
|
30
|
-
else
|
31
|
-
oauth_token_by_token(param("token")) || oauth_token_by_refresh_token(param("token"))
|
36
|
+
oauth_grant_by_refresh_token(param("token"))
|
32
37
|
end
|
33
38
|
|
34
|
-
if
|
35
|
-
redirect_response_error("invalid_request") if oauth_token && !token_from_application?(oauth_token, oauth_application)
|
36
|
-
elsif oauth_token
|
37
|
-
@oauth_application = db[oauth_applications_table].where(oauth_applications_id_column =>
|
38
|
-
oauth_token[oauth_tokens_oauth_application_id_column]).first
|
39
|
-
end
|
39
|
+
oauth_grant ||= oauth_grant_by_refresh_token(param("token")) if token_type_hint.nil?
|
40
40
|
|
41
|
-
json_response_success(json_token_introspect_payload(
|
41
|
+
json_response_success(json_token_introspect_payload(oauth_grant))
|
42
42
|
end
|
43
43
|
|
44
|
-
throw_json_response_error(
|
44
|
+
throw_json_response_error(oauth_invalid_response_status, "invalid_request")
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
48
|
# Token introspect
|
49
49
|
|
50
|
-
def
|
50
|
+
def validate_introspect_params(token_hint_types = %w[access_token refresh_token].freeze)
|
51
51
|
# check if valid token hint type
|
52
52
|
if param_or_nil("token_type_hint") && !token_hint_types.include?(param("token_type_hint"))
|
53
53
|
redirect_response_error("unsupported_token_type")
|
@@ -56,17 +56,35 @@ module Rodauth
|
|
56
56
|
redirect_response_error("invalid_request") unless param_or_nil("token")
|
57
57
|
end
|
58
58
|
|
59
|
-
def json_token_introspect_payload(
|
60
|
-
return { active: false } unless
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
59
|
+
def json_token_introspect_payload(grant_or_claims)
|
60
|
+
return { active: false } unless grant_or_claims
|
61
|
+
|
62
|
+
if grant_or_claims["sub"]
|
63
|
+
# JWT
|
64
|
+
{
|
65
|
+
active: true,
|
66
|
+
scope: grant_or_claims["scope"],
|
67
|
+
client_id: grant_or_claims["client_id"],
|
68
|
+
username: resource_owner_identifier(grant_or_claims),
|
69
|
+
token_type: "access_token",
|
70
|
+
exp: grant_or_claims["exp"],
|
71
|
+
iat: grant_or_claims["iat"],
|
72
|
+
nbf: grant_or_claims["nbf"],
|
73
|
+
sub: grant_or_claims["sub"],
|
74
|
+
aud: grant_or_claims["aud"],
|
75
|
+
iss: grant_or_claims["iss"],
|
76
|
+
jti: grant_or_claims["jti"]
|
77
|
+
}
|
78
|
+
else
|
79
|
+
{
|
80
|
+
active: true,
|
81
|
+
scope: grant_or_claims[oauth_grants_scopes_column],
|
82
|
+
client_id: oauth_application[oauth_applications_client_id_column],
|
83
|
+
username: resource_owner_identifier(grant_or_claims),
|
84
|
+
token_type: oauth_token_type,
|
85
|
+
exp: grant_or_claims[oauth_grants_expires_in_column].to_i
|
86
|
+
}
|
87
|
+
end
|
70
88
|
end
|
71
89
|
|
72
90
|
def check_csrf?
|
@@ -80,24 +98,17 @@ module Rodauth
|
|
80
98
|
|
81
99
|
private
|
82
100
|
|
83
|
-
def
|
84
|
-
|
85
|
-
http = Net::HTTP.new(auth_url.host, auth_url.port)
|
86
|
-
http.use_ssl = auth_url.scheme == "https"
|
101
|
+
def require_oauth_application_for_introspect
|
102
|
+
(token = ((v = request.env["HTTP_AUTHORIZATION"]) && v[/\A *Bearer (.*)\Z/, 1]))
|
87
103
|
|
88
|
-
|
89
|
-
request["content-type"] = "application/x-www-form-urlencoded"
|
90
|
-
request["accept"] = json_response_content_type
|
91
|
-
request.set_form_data({ "token_type_hint" => token_type_hint, "token" => token })
|
104
|
+
return require_oauth_application unless token
|
92
105
|
|
93
|
-
|
94
|
-
response = http.request(request)
|
95
|
-
authorization_required unless response.code.to_i == 200
|
106
|
+
oauth_application = current_oauth_application
|
96
107
|
|
97
|
-
|
98
|
-
end
|
108
|
+
authorization_required unless oauth_application
|
99
109
|
|
100
|
-
|
110
|
+
@oauth_application = oauth_application
|
111
|
+
end
|
101
112
|
|
102
113
|
def oauth_server_metadata_body(*)
|
103
114
|
super.tap do |data|
|
@@ -105,5 +116,24 @@ module Rodauth
|
|
105
116
|
data[:introspection_endpoint_auth_methods_supported] = %w[client_secret_basic]
|
106
117
|
end
|
107
118
|
end
|
119
|
+
|
120
|
+
def resource_owner_identifier(grant_or_claims)
|
121
|
+
if (account_id = grant_or_claims[oauth_grants_account_id_column])
|
122
|
+
account_ds(account_id).select(login_column).first[login_column]
|
123
|
+
elsif (app_id = grant_or_claims[oauth_grants_oauth_application_id_column])
|
124
|
+
db[oauth_applications_table].where(oauth_applications_id_column => app_id)
|
125
|
+
.select(oauth_applications_name_column)
|
126
|
+
.first[oauth_applications_name_column]
|
127
|
+
elsif (subject = grant_or_claims["sub"])
|
128
|
+
# JWT
|
129
|
+
if subject == grant_or_claims["client_id"]
|
130
|
+
db[oauth_applications_table].where(oauth_applications_client_id_column => subject)
|
131
|
+
.select(oauth_applications_name_column)
|
132
|
+
.first[oauth_applications_name_column]
|
133
|
+
else
|
134
|
+
account_ds(subject).select(login_column).first[login_column]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
108
138
|
end
|
109
139
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
3
5
|
module Rodauth
|
4
6
|
Feature.define(:oauth_token_revocation, :OauthTokenRevocation) do
|
5
7
|
depends :oauth_base
|
@@ -7,14 +9,10 @@ module Rodauth
|
|
7
9
|
before "revoke"
|
8
10
|
after "revoke"
|
9
11
|
|
10
|
-
notice_flash "The oauth
|
12
|
+
notice_flash "The oauth grant has been revoked", "revoke_oauth_grant"
|
11
13
|
|
12
14
|
# /revoke
|
13
|
-
|
14
|
-
next unless is_authorization_server?
|
15
|
-
|
16
|
-
before_revoke_route
|
17
|
-
|
15
|
+
auth_server_route(:revoke) do |r|
|
18
16
|
if logged_in?
|
19
17
|
require_account
|
20
18
|
require_oauth_application_from_account
|
@@ -22,24 +20,32 @@ module Rodauth
|
|
22
20
|
require_oauth_application
|
23
21
|
end
|
24
22
|
|
23
|
+
before_revoke_route
|
24
|
+
|
25
25
|
r.post do
|
26
26
|
catch_error do
|
27
|
-
|
27
|
+
validate_revoke_params
|
28
28
|
|
29
|
-
|
29
|
+
oauth_grant = nil
|
30
30
|
transaction do
|
31
31
|
before_revoke
|
32
|
-
|
32
|
+
oauth_grant = revoke_oauth_grant
|
33
33
|
after_revoke
|
34
34
|
end
|
35
35
|
|
36
36
|
if accepts_json?
|
37
|
-
|
38
|
-
"
|
39
|
-
|
40
|
-
|
37
|
+
json_payload = {
|
38
|
+
"revoked_at" => convert_timestamp(oauth_grant[oauth_grants_revoked_at_column])
|
39
|
+
}
|
40
|
+
if param("token_type_hint") == "refresh_token"
|
41
|
+
json_payload["refresh_token"] = oauth_grant[oauth_grants_refresh_token_column]
|
42
|
+
else
|
43
|
+
json_payload["token"] = oauth_grant[oauth_grants_token_column]
|
44
|
+
end
|
45
|
+
|
46
|
+
json_response_success json_payload
|
41
47
|
else
|
42
|
-
set_notice_flash
|
48
|
+
set_notice_flash revoke_oauth_grant_notice_flash
|
43
49
|
redirect request.referer || "/"
|
44
50
|
end
|
45
51
|
end
|
@@ -48,12 +54,17 @@ module Rodauth
|
|
48
54
|
end
|
49
55
|
end
|
50
56
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
|
57
|
+
def validate_revoke_params(token_hint_types = %w[access_token refresh_token].freeze)
|
58
|
+
token_hint = param_or_nil("token_type_hint")
|
59
|
+
|
60
|
+
if features.include?(:oauth_jwt) && oauth_jwt_access_tokens && (!token_hint || token_hint == "access_token")
|
61
|
+
# JWT access tokens can't be revoked
|
62
|
+
throw(:rodauth_error)
|
55
63
|
end
|
56
64
|
|
65
|
+
# check if valid token hint type
|
66
|
+
redirect_response_error("unsupported_token_type") if token_hint && !token_hint_types.include?(token_hint)
|
67
|
+
|
57
68
|
redirect_response_error("invalid_request") unless param_or_nil("token")
|
58
69
|
end
|
59
70
|
|
@@ -68,29 +79,31 @@ module Rodauth
|
|
68
79
|
|
69
80
|
private
|
70
81
|
|
71
|
-
def
|
82
|
+
def revoke_oauth_grant
|
72
83
|
token = param("token")
|
73
84
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
85
|
+
if param("token_type_hint") == "refresh_token"
|
86
|
+
oauth_grant = oauth_grant_by_refresh_token(token)
|
87
|
+
token_column = oauth_grants_refresh_token_column
|
88
|
+
else
|
89
|
+
oauth_grant = oauth_grant_by_token_ds(token).where(
|
90
|
+
oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column]
|
91
|
+
).first
|
92
|
+
token_column = oauth_grants_token_column
|
93
|
+
end
|
81
94
|
|
82
|
-
redirect_response_error("invalid_request") unless
|
95
|
+
redirect_response_error("invalid_request") unless oauth_grant
|
83
96
|
|
84
|
-
redirect_response_error("invalid_request") unless
|
97
|
+
redirect_response_error("invalid_request") unless grant_from_application?(oauth_grant, oauth_application)
|
85
98
|
|
86
|
-
update_params = {
|
99
|
+
update_params = { oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP }
|
87
100
|
|
88
|
-
ds = db[
|
101
|
+
ds = db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
|
89
102
|
|
90
|
-
|
103
|
+
oauth_grant = __update_and_return__(ds, update_params)
|
91
104
|
|
92
|
-
|
93
|
-
|
105
|
+
oauth_grant[token_column] = token
|
106
|
+
oauth_grant
|
94
107
|
|
95
108
|
# If the particular
|
96
109
|
# token is a refresh token and the authorization server supports the
|