rodauth-oauth 0.10.4 → 1.0.0.pre.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|