doorkeeper 5.8.0 → 5.8.2
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/CHANGELOG.md +14 -0
- data/app/controllers/doorkeeper/tokens_controller.rb +28 -7
- data/config/locales/en.yml +5 -1
- data/lib/doorkeeper/errors.rb +14 -2
- data/lib/doorkeeper/oauth/authorization_code_request.rb +1 -5
- data/lib/doorkeeper/oauth/base_request.rb +2 -1
- data/lib/doorkeeper/oauth/error.rb +4 -3
- data/lib/doorkeeper/oauth/error_response.rb +2 -1
- data/lib/doorkeeper/oauth/pre_authorization.rb +8 -4
- data/lib/doorkeeper/oauth/scopes.rb +18 -0
- data/lib/doorkeeper/rails/helpers.rb +3 -1
- data/lib/doorkeeper/revocable_tokens/revocable_access_token.rb +21 -0
- data/lib/doorkeeper/revocable_tokens/revocable_refresh_token.rb +21 -0
- data/lib/doorkeeper/version.rb +1 -1
- data/lib/doorkeeper.rb +5 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de574cec8c17af2fd1026081acc0bf592c71ecbf947d92546df6b4d48ce3b5ce
|
4
|
+
data.tar.gz: eb282ce352bbd4491014b753535ff24e804ca801e02aef8a6ded3f7ca5951e64
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68b668d79eb5532cb4dbe660eb26269d67eb545b5dbd12bae15c087752c7e5447e1f60326725c0d838189a746de5718e8644605dcdd946d4f2cf29a556297369
|
7
|
+
data.tar.gz: 8ad2d79f707129abd0787710cc86ee563af9887f986dec03edc396c691ffb26e18279b3693bc15658d059e78dd7dc2ce3093a137c03bb16e7858832d9e80c368
|
data/CHANGELOG.md
CHANGED
@@ -9,6 +9,20 @@ User-visible changes worth mentioning.
|
|
9
9
|
|
10
10
|
Add your entry here.
|
11
11
|
|
12
|
+
## 5.8.2
|
13
|
+
|
14
|
+
- [#1755] Fix the error message for force_pkce
|
15
|
+
- [#1761] Memoize authentication failure
|
16
|
+
- [#1762] Allow missing client to trigger invalid client error when force_pkce is enabled
|
17
|
+
- [#1767] Make sure error handling happens on a controller level opposed to action level to account for the controller being extended
|
18
|
+
|
19
|
+
## 5.8.1
|
20
|
+
|
21
|
+
- [#1752] Bump the range of supported Ruby and Rails versions
|
22
|
+
- [#1747] Fix unknown pkce method error when configured
|
23
|
+
- [#1744] Allow for expired refresh tokens to be revoked
|
24
|
+
- [#1754] Fix refresh tokens with dynamic scopes
|
25
|
+
|
12
26
|
## 5.8.0
|
13
27
|
|
14
28
|
- [#1739] Add support for dynamic scopes
|
@@ -4,12 +4,14 @@ module Doorkeeper
|
|
4
4
|
class TokensController < Doorkeeper::ApplicationMetalController
|
5
5
|
before_action :validate_presence_of_client, only: [:revoke]
|
6
6
|
|
7
|
+
rescue_from Errors::DoorkeeperError do |e|
|
8
|
+
handle_token_exception(e)
|
9
|
+
end
|
10
|
+
|
7
11
|
def create
|
8
12
|
headers.merge!(authorize_response.headers)
|
9
13
|
render json: authorize_response.body,
|
10
14
|
status: authorize_response.status
|
11
|
-
rescue Errors::DoorkeeperError => e
|
12
|
-
handle_token_exception(e)
|
13
15
|
end
|
14
16
|
|
15
17
|
# OAuth 2.0 Token Revocation - https://datatracker.ietf.org/doc/html/rfc7009
|
@@ -113,19 +115,38 @@ module Doorkeeper
|
|
113
115
|
# The authorization server responds with HTTP status code 200 if the token
|
114
116
|
# has been revoked successfully or if the client submitted an invalid
|
115
117
|
# token
|
116
|
-
|
118
|
+
revocable_token.revoke if revocable_token.revocable?
|
117
119
|
end
|
118
120
|
|
119
121
|
def token
|
120
|
-
|
122
|
+
revocable_token&.token
|
123
|
+
end
|
124
|
+
|
125
|
+
def revocable_token
|
126
|
+
return @revocable_token if defined? @revocable_token
|
127
|
+
|
128
|
+
@revocable_token =
|
121
129
|
if params[:token_type_hint] == "refresh_token"
|
122
|
-
|
130
|
+
refresh_token
|
123
131
|
else
|
124
|
-
|
125
|
-
Doorkeeper.config.access_token_model.by_refresh_token(params["token"])
|
132
|
+
access_token || refresh_token
|
126
133
|
end
|
127
134
|
end
|
128
135
|
|
136
|
+
def refresh_token
|
137
|
+
token = Doorkeeper.config.access_token_model.by_refresh_token(params["token"])
|
138
|
+
return unless token
|
139
|
+
|
140
|
+
RevocableTokens::RevocableRefreshToken.new(token)
|
141
|
+
end
|
142
|
+
|
143
|
+
def access_token
|
144
|
+
token = Doorkeeper.config.access_token_model.by_token(params["token"])
|
145
|
+
return unless token
|
146
|
+
|
147
|
+
RevocableTokens::RevocableAccessToken.new(token)
|
148
|
+
end
|
149
|
+
|
129
150
|
def strategy
|
130
151
|
@strategy ||= server.token_request(params[:grant_type])
|
131
152
|
end
|
data/config/locales/en.yml
CHANGED
@@ -96,11 +96,15 @@ en:
|
|
96
96
|
unknown: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
|
97
97
|
missing_param: 'Missing required parameter: %{value}.'
|
98
98
|
request_not_authorized: 'Request need to be authorized. Required parameter for authorizing request is missing or invalid.'
|
99
|
+
invalid_code_challenge: 'Code challenge is required.'
|
99
100
|
invalid_redirect_uri: "The requested redirect uri is malformed or doesn't match client redirect URI."
|
100
101
|
unauthorized_client: 'The client is not authorized to perform this request using this method.'
|
101
102
|
access_denied: 'The resource owner or authorization server denied the request.'
|
102
103
|
invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
|
103
|
-
invalid_code_challenge_method:
|
104
|
+
invalid_code_challenge_method:
|
105
|
+
zero: 'The authorization server does not support PKCE as there are no accepted code_challenge_method values.'
|
106
|
+
one: 'The code_challenge_method must be %{challenge_methods}.'
|
107
|
+
other: 'The code_challenge_method must be one of %{challenge_methods}.'
|
104
108
|
server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.'
|
105
109
|
temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.'
|
106
110
|
|
data/lib/doorkeeper/errors.rb
CHANGED
@@ -6,6 +6,10 @@ module Doorkeeper
|
|
6
6
|
def type
|
7
7
|
message
|
8
8
|
end
|
9
|
+
|
10
|
+
def self.translate_options
|
11
|
+
{}
|
12
|
+
end
|
9
13
|
end
|
10
14
|
|
11
15
|
class InvalidGrantReuse < DoorkeeperError
|
@@ -45,6 +49,16 @@ module Doorkeeper
|
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
52
|
+
class InvalidCodeChallengeMethod < BaseResponseError
|
53
|
+
def self.translate_options
|
54
|
+
challenge_methods = Doorkeeper.config.pkce_code_challenge_methods_supported
|
55
|
+
{
|
56
|
+
challenge_methods: challenge_methods.join(", "),
|
57
|
+
count: challenge_methods.length
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
48
62
|
UnableToGenerateToken = Class.new(DoorkeeperError)
|
49
63
|
TokenGeneratorNotFound = Class.new(DoorkeeperError)
|
50
64
|
NoOrmCleaner = Class.new(DoorkeeperError)
|
@@ -54,8 +68,6 @@ module Doorkeeper
|
|
54
68
|
InvalidClient = Class.new(BaseResponseError)
|
55
69
|
InvalidScope = Class.new(BaseResponseError)
|
56
70
|
InvalidRedirectUri = Class.new(BaseResponseError)
|
57
|
-
InvalidCodeChallenge = Class.new(BaseResponseError)
|
58
|
-
InvalidCodeChallengeMethod = Class.new(BaseResponseError)
|
59
71
|
InvalidGrant = Class.new(BaseResponseError)
|
60
72
|
|
61
73
|
UnauthorizedClient = Class.new(BaseResponseError)
|
@@ -59,15 +59,11 @@ module Doorkeeper
|
|
59
59
|
Doorkeeper.config.access_grant_model.pkce_supported?
|
60
60
|
end
|
61
61
|
|
62
|
-
def confidential?
|
63
|
-
client&.confidential
|
64
|
-
end
|
65
|
-
|
66
62
|
def validate_params
|
67
63
|
@missing_param =
|
68
64
|
if grant&.uses_pkce? && code_verifier.blank?
|
69
65
|
:code_verifier
|
70
|
-
elsif !confidential
|
66
|
+
elsif client && !client.confidential && Doorkeeper.config.force_pkce? && code_verifier.blank?
|
71
67
|
:code_verifier
|
72
68
|
elsif redirect_uri.blank?
|
73
69
|
:redirect_uri
|
@@ -2,13 +2,14 @@
|
|
2
2
|
|
3
3
|
module Doorkeeper
|
4
4
|
module OAuth
|
5
|
-
Error = Struct.new(:name, :state) do
|
5
|
+
Error = Struct.new(:name, :state, :translate_options) do
|
6
6
|
def description
|
7
|
-
|
8
|
-
name,
|
7
|
+
options = (translate_options || {}).merge(
|
9
8
|
scope: %i[doorkeeper errors messages],
|
10
9
|
default: :server_error,
|
11
10
|
)
|
11
|
+
|
12
|
+
I18n.translate(name, **options)
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|
@@ -12,6 +12,7 @@ module Doorkeeper
|
|
12
12
|
attributes.merge(
|
13
13
|
name: error_name_for(request.error),
|
14
14
|
exception_class: exception_class_for(request.error),
|
15
|
+
translate_options: request.error.try(:translate_options),
|
15
16
|
state: request.try(:state),
|
16
17
|
redirect_uri: request.try(:redirect_uri),
|
17
18
|
),
|
@@ -33,7 +34,7 @@ module Doorkeeper
|
|
33
34
|
delegate :name, :description, :state, to: :@error
|
34
35
|
|
35
36
|
def initialize(attributes = {})
|
36
|
-
@error = OAuth::Error.new(*attributes.values_at(:name, :state))
|
37
|
+
@error = OAuth::Error.new(*attributes.values_at(:name, :state, :translate_options))
|
37
38
|
@exception_class = attributes[:exception_class]
|
38
39
|
@redirect_uri = attributes[:redirect_uri]
|
39
40
|
@response_on_fragment = attributes[:response_on_fragment]
|
@@ -14,12 +14,13 @@ module Doorkeeper
|
|
14
14
|
validate :response_type, error: Errors::UnsupportedResponseType
|
15
15
|
validate :response_mode, error: Errors::UnsupportedResponseMode
|
16
16
|
validate :scopes, error: Errors::InvalidScope
|
17
|
-
validate :code_challenge, error: Errors::
|
17
|
+
validate :code_challenge, error: Errors::InvalidRequest
|
18
18
|
validate :code_challenge_method, error: Errors::InvalidCodeChallengeMethod
|
19
19
|
|
20
20
|
attr_reader :client, :code_challenge, :code_challenge_method, :missing_param,
|
21
21
|
:redirect_uri, :resource_owner, :response_type, :state,
|
22
|
-
:authorization_response_flow, :response_mode, :custom_access_token_attributes
|
22
|
+
:authorization_response_flow, :response_mode, :custom_access_token_attributes,
|
23
|
+
:invalid_request_reason
|
23
24
|
|
24
25
|
def initialize(server, parameters = {}, resource_owner = nil)
|
25
26
|
@server = server
|
@@ -75,7 +76,7 @@ module Doorkeeper
|
|
75
76
|
if client_scopes.blank?
|
76
77
|
server.default_scopes.to_s
|
77
78
|
else
|
78
|
-
|
79
|
+
server.default_scopes.allowed(client_scopes).to_s
|
79
80
|
end
|
80
81
|
end
|
81
82
|
|
@@ -147,7 +148,10 @@ module Doorkeeper
|
|
147
148
|
def validate_code_challenge
|
148
149
|
return true unless Doorkeeper.config.force_pkce?
|
149
150
|
return true if client.confidential
|
150
|
-
code_challenge.present?
|
151
|
+
return true if code_challenge.present?
|
152
|
+
|
153
|
+
@invalid_request_reason = :invalid_code_challenge
|
154
|
+
false
|
151
155
|
end
|
152
156
|
|
153
157
|
def validate_code_challenge_method
|
@@ -70,10 +70,28 @@ module Doorkeeper
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
+
# DEPRECATED: With dynamic scopes, #allowed should be called because
|
74
|
+
# A & B doesn't really make sense with dynamic scopes.
|
75
|
+
#
|
76
|
+
# For example, if A = user:* and B is user:1, A & B = [].
|
77
|
+
# If we modified this method to take dynamic scopes into an account, then order
|
78
|
+
# becomes important, and this would violate the principle that A & B = B & A.
|
73
79
|
def &(other)
|
80
|
+
return allowed(other) if dynamic_scopes_enabled?
|
81
|
+
|
74
82
|
self.class.from_array(all & to_array(other))
|
75
83
|
end
|
76
84
|
|
85
|
+
# Returns a set of scopes that are allowed, taking dynamic
|
86
|
+
# scopes into account. This instance's scopes is taken as the allowed set,
|
87
|
+
# and the passed value is the set to filter.
|
88
|
+
#
|
89
|
+
# @param other The set of scopes to filter
|
90
|
+
def allowed(other)
|
91
|
+
filtered_scopes = other.select { |scope| self.exists?(scope) }
|
92
|
+
self.class.from_array(filtered_scopes)
|
93
|
+
end
|
94
|
+
|
77
95
|
private
|
78
96
|
|
79
97
|
def dynamic_scopes_enabled?
|
@@ -70,7 +70,9 @@ module Doorkeeper
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def doorkeeper_token
|
73
|
-
@doorkeeper_token
|
73
|
+
return @doorkeeper_token if defined?(@doorkeeper_token)
|
74
|
+
|
75
|
+
@doorkeeper_token = OAuth::Token.authenticate(
|
74
76
|
request,
|
75
77
|
*Doorkeeper.config.access_token_methods,
|
76
78
|
)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doorkeeper
|
4
|
+
module RevocableTokens
|
5
|
+
class RevocableAccessToken
|
6
|
+
attr_reader :token
|
7
|
+
|
8
|
+
def initialize(token)
|
9
|
+
@token = token
|
10
|
+
end
|
11
|
+
|
12
|
+
def revocable?
|
13
|
+
token.accessible?
|
14
|
+
end
|
15
|
+
|
16
|
+
def revoke
|
17
|
+
token.revoke
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doorkeeper
|
4
|
+
module RevocableTokens
|
5
|
+
class RevocableRefreshToken
|
6
|
+
attr_reader :token
|
7
|
+
|
8
|
+
def initialize(token)
|
9
|
+
@token = token
|
10
|
+
end
|
11
|
+
|
12
|
+
def revocable?
|
13
|
+
!token.revoked?
|
14
|
+
end
|
15
|
+
|
16
|
+
def revoke
|
17
|
+
token.revoke
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/doorkeeper/version.rb
CHANGED
data/lib/doorkeeper.rb
CHANGED
@@ -34,6 +34,11 @@ module Doorkeeper
|
|
34
34
|
autoload :Token, "doorkeeper/request/token"
|
35
35
|
end
|
36
36
|
|
37
|
+
module RevocableTokens
|
38
|
+
autoload :RevocableAccessToken, "doorkeeper/revocable_tokens/revocable_access_token"
|
39
|
+
autoload :RevocableRefreshToken, "doorkeeper/revocable_tokens/revocable_refresh_token"
|
40
|
+
end
|
41
|
+
|
37
42
|
module OAuth
|
38
43
|
autoload :BaseRequest, "doorkeeper/oauth/base_request"
|
39
44
|
autoload :AuthorizationCodeRequest, "doorkeeper/oauth/authorization_code_request"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: doorkeeper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.8.
|
4
|
+
version: 5.8.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Felipe Elias Philipp
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2025-04-04 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: railties
|
@@ -290,6 +290,8 @@ files:
|
|
290
290
|
- lib/doorkeeper/request/refresh_token.rb
|
291
291
|
- lib/doorkeeper/request/strategy.rb
|
292
292
|
- lib/doorkeeper/request/token.rb
|
293
|
+
- lib/doorkeeper/revocable_tokens/revocable_access_token.rb
|
294
|
+
- lib/doorkeeper/revocable_tokens/revocable_refresh_token.rb
|
293
295
|
- lib/doorkeeper/secret_storing/base.rb
|
294
296
|
- lib/doorkeeper/secret_storing/bcrypt.rb
|
295
297
|
- lib/doorkeeper/secret_storing/plain.rb
|
@@ -326,6 +328,7 @@ metadata:
|
|
326
328
|
source_code_uri: https://github.com/doorkeeper-gem/doorkeeper
|
327
329
|
bug_tracker_uri: https://github.com/doorkeeper-gem/doorkeeper/issues
|
328
330
|
documentation_uri: https://doorkeeper.gitbook.io/guides/
|
331
|
+
funding_uri: https://opencollective.com/doorkeeper-gem
|
329
332
|
post_install_message: "Starting from 5.5.0 RC1 Doorkeeper requires client authentication
|
330
333
|
for Resource Owner Password Grant\nas stated in the OAuth RFC. You have to create
|
331
334
|
a new OAuth client (Doorkeeper::Application) if you didn't\nhave it before and use
|