rodauth-oauth 0.10.3 → 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/0_10_3.md +1 -1
- data/doc/release_notes/0_10_4.md +11 -0
- 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 -678
- 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 +39 -22
- data/lib/rodauth/features/oauth_resource_server.rb +38 -0
- data/lib/rodauth/features/oauth_saml_bearer_grant.rb +5 -1
- data/lib/rodauth/features/oauth_token_introspection.rb +76 -45
- data/lib/rodauth/features/oauth_token_revocation.rb +46 -31
- 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 +21 -17
- 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_authorization_server.rb +0 -0
- 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)
|
@@ -20,7 +18,12 @@ module Rodauth
|
|
20
18
|
if json_request? || param_or_nil("request") # signed request
|
21
19
|
resources = Array(resources)
|
22
20
|
else
|
23
|
-
query = request.form_data?
|
21
|
+
query = if request.form_data?
|
22
|
+
request.body.rewind
|
23
|
+
request.body.read
|
24
|
+
else
|
25
|
+
request.query_string
|
26
|
+
end
|
24
27
|
# resource query param does not conform to rack parsing rules
|
25
28
|
resources = URI.decode_www_form(query).each_with_object([]) do |(k, v), memo|
|
26
29
|
memo << v if k == "resource"
|
@@ -33,9 +36,10 @@ module Rodauth
|
|
33
36
|
def require_oauth_authorization(*)
|
34
37
|
super
|
35
38
|
|
36
|
-
|
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"]
|
37
41
|
|
38
|
-
|
42
|
+
return unless token_indicators
|
39
43
|
|
40
44
|
token_indicators = token_indicators.split(" ") if token_indicators.is_a?(String)
|
41
45
|
|
@@ -44,7 +48,7 @@ module Rodauth
|
|
44
48
|
|
45
49
|
private
|
46
50
|
|
47
|
-
def
|
51
|
+
def validate_token_params
|
48
52
|
super
|
49
53
|
|
50
54
|
return unless resource_indicators
|
@@ -54,23 +58,16 @@ module Rodauth
|
|
54
58
|
end
|
55
59
|
end
|
56
60
|
|
57
|
-
def
|
61
|
+
def create_token_from_token(oauth_grant, update_params)
|
58
62
|
return super unless resource_indicators
|
59
63
|
|
60
|
-
return super unless oauth_token[oauth_tokens_oauth_grant_id_column]
|
61
|
-
|
62
|
-
oauth_grant = db[oauth_grants_table].where(
|
63
|
-
oauth_grants_id_column => oauth_token[oauth_tokens_oauth_grant_id_column],
|
64
|
-
oauth_grants_revoked_at_column => nil
|
65
|
-
).first
|
66
|
-
|
67
64
|
grant_indicators = oauth_grant[oauth_grants_resource_column]
|
68
65
|
|
69
66
|
grant_indicators = grant_indicators.split(" ") if grant_indicators.is_a?(String)
|
70
67
|
|
71
68
|
redirect_response_error("invalid_target") unless (grant_indicators - resource_indicators) != grant_indicators
|
72
69
|
|
73
|
-
super(
|
70
|
+
super(oauth_grant, update_params.merge(oauth_grants_resource_column => resource_indicators))
|
74
71
|
end
|
75
72
|
|
76
73
|
def check_valid_no_fragment_uri?(uri)
|
@@ -90,9 +87,11 @@ module Rodauth
|
|
90
87
|
end
|
91
88
|
end
|
92
89
|
|
93
|
-
def
|
90
|
+
def create_token_from_authorization_code(grant_params, *args, oauth_grant: nil)
|
94
91
|
return super unless resource_indicators
|
95
92
|
|
93
|
+
oauth_grant ||= valid_locked_oauth_grant(grant_params)
|
94
|
+
|
96
95
|
redirect_response_error("invalid_target") unless oauth_grant[oauth_grants_resource_column]
|
97
96
|
|
98
97
|
grant_indicators = oauth_grant[oauth_grants_resource_column]
|
@@ -101,7 +100,15 @@ module Rodauth
|
|
101
100
|
|
102
101
|
redirect_response_error("invalid_target") unless (grant_indicators - resource_indicators) != grant_indicators
|
103
102
|
|
104
|
-
|
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)
|
105
112
|
end
|
106
113
|
|
107
114
|
def create_oauth_grant(create_params = {})
|
@@ -112,12 +119,12 @@ module Rodauth
|
|
112
119
|
end
|
113
120
|
|
114
121
|
module IndicatorIntrospection
|
115
|
-
def json_token_introspect_payload(
|
116
|
-
return super unless
|
122
|
+
def json_token_introspect_payload(grant)
|
123
|
+
return super unless grant[oauth_grants_id_column]
|
117
124
|
|
118
125
|
payload = super
|
119
126
|
|
120
|
-
token_indicators =
|
127
|
+
token_indicators = grant[oauth_grants_resource_column]
|
121
128
|
|
122
129
|
token_indicators = token_indicators.split(" ") if token_indicators.is_a?(String)
|
123
130
|
|
@@ -129,7 +136,7 @@ module Rodauth
|
|
129
136
|
def introspection_request(*)
|
130
137
|
payload = super
|
131
138
|
|
132
|
-
payload[
|
139
|
+
payload[oauth_grants_resource_column] = payload["aud"] if payload["aud"]
|
133
140
|
|
134
141
|
payload
|
135
142
|
end
|
@@ -141,6 +148,16 @@ module Rodauth
|
|
141
148
|
|
142
149
|
super.merge(aud: resource_indicators)
|
143
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
|
144
161
|
end
|
145
162
|
|
146
163
|
def self.included(rodauth)
|
@@ -1,7 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rodauth/oauth"
|
4
|
+
|
3
5
|
module Rodauth
|
4
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
|
+
|
5
15
|
def authorization_token
|
6
16
|
return @authorization_token if defined?(@authorization_token)
|
7
17
|
|
@@ -17,5 +27,33 @@ module Rodauth
|
|
17
27
|
|
18
28
|
@authorization_token = payload
|
19
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
|
20
58
|
end
|
21
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,46 +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
20
|
|
19
21
|
r.post do
|
20
22
|
catch_error do
|
21
|
-
|
23
|
+
validate_introspect_params
|
24
|
+
|
25
|
+
token_type_hint = param_or_nil("token_type_hint")
|
22
26
|
|
23
27
|
before_introspect
|
24
|
-
|
25
|
-
when "access_token"
|
26
|
-
|
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
|
27
35
|
when "refresh_token"
|
28
|
-
|
29
|
-
else
|
30
|
-
oauth_token_by_token(param("token")) || oauth_token_by_refresh_token(param("token"))
|
36
|
+
oauth_grant_by_refresh_token(param("token"))
|
31
37
|
end
|
32
38
|
|
33
|
-
if
|
34
|
-
redirect_response_error("invalid_request") if oauth_token && !token_from_application?(oauth_token, oauth_application)
|
35
|
-
elsif oauth_token
|
36
|
-
@oauth_application = db[oauth_applications_table].where(oauth_applications_id_column =>
|
37
|
-
oauth_token[oauth_tokens_oauth_application_id_column]).first
|
38
|
-
end
|
39
|
+
oauth_grant ||= oauth_grant_by_refresh_token(param("token")) if token_type_hint.nil?
|
39
40
|
|
40
|
-
json_response_success(json_token_introspect_payload(
|
41
|
+
json_response_success(json_token_introspect_payload(oauth_grant))
|
41
42
|
end
|
42
43
|
|
43
|
-
throw_json_response_error(
|
44
|
+
throw_json_response_error(oauth_invalid_response_status, "invalid_request")
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
47
48
|
# Token introspect
|
48
49
|
|
49
|
-
def
|
50
|
+
def validate_introspect_params(token_hint_types = %w[access_token refresh_token].freeze)
|
50
51
|
# check if valid token hint type
|
51
52
|
if param_or_nil("token_type_hint") && !token_hint_types.include?(param("token_type_hint"))
|
52
53
|
redirect_response_error("unsupported_token_type")
|
@@ -55,17 +56,35 @@ module Rodauth
|
|
55
56
|
redirect_response_error("invalid_request") unless param_or_nil("token")
|
56
57
|
end
|
57
58
|
|
58
|
-
def json_token_introspect_payload(
|
59
|
-
return { active: false } unless
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
69
88
|
end
|
70
89
|
|
71
90
|
def check_csrf?
|
@@ -79,24 +98,17 @@ module Rodauth
|
|
79
98
|
|
80
99
|
private
|
81
100
|
|
82
|
-
def
|
83
|
-
|
84
|
-
http = Net::HTTP.new(auth_url.host, auth_url.port)
|
85
|
-
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]))
|
86
103
|
|
87
|
-
|
88
|
-
request["content-type"] = "application/x-www-form-urlencoded"
|
89
|
-
request["accept"] = json_response_content_type
|
90
|
-
request.set_form_data({ "token_type_hint" => token_type_hint, "token" => token })
|
104
|
+
return require_oauth_application unless token
|
91
105
|
|
92
|
-
|
93
|
-
response = http.request(request)
|
94
|
-
authorization_required unless response.code.to_i == 200
|
106
|
+
oauth_application = current_oauth_application
|
95
107
|
|
96
|
-
|
97
|
-
end
|
108
|
+
authorization_required unless oauth_application
|
98
109
|
|
99
|
-
|
110
|
+
@oauth_application = oauth_application
|
111
|
+
end
|
100
112
|
|
101
113
|
def oauth_server_metadata_body(*)
|
102
114
|
super.tap do |data|
|
@@ -104,5 +116,24 @@ module Rodauth
|
|
104
116
|
data[:introspection_endpoint_auth_methods_supported] = %w[client_secret_basic]
|
105
117
|
end
|
106
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
|
107
138
|
end
|
108
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,27 +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
|
-
|
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
|
79
94
|
|
80
|
-
redirect_response_error("invalid_request") unless
|
95
|
+
redirect_response_error("invalid_request") unless oauth_grant
|
81
96
|
|
82
|
-
redirect_response_error("invalid_request") unless
|
97
|
+
redirect_response_error("invalid_request") unless grant_from_application?(oauth_grant, oauth_application)
|
83
98
|
|
84
|
-
update_params = {
|
99
|
+
update_params = { oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP }
|
85
100
|
|
86
|
-
ds = db[
|
101
|
+
ds = db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
|
87
102
|
|
88
|
-
|
103
|
+
oauth_grant = __update_and_return__(ds, update_params)
|
89
104
|
|
90
|
-
|
91
|
-
|
105
|
+
oauth_grant[token_column] = token
|
106
|
+
oauth_grant
|
92
107
|
|
93
108
|
# If the particular
|
94
109
|
# token is a refresh token and the authorization server supports the
|