doorkeeper 5.7.1 → 5.8.0
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 +10 -0
- data/lib/doorkeeper/config/validations.rb +12 -0
- data/lib/doorkeeper/config.rb +26 -1
- data/lib/doorkeeper/models/access_token_mixin.rb +2 -0
- data/lib/doorkeeper/oauth/pre_authorization.rb +1 -1
- data/lib/doorkeeper/oauth/scopes.rb +37 -1
- data/lib/doorkeeper/oauth/token_introspection.rb +28 -16
- data/lib/doorkeeper/oauth/token_response.rb +1 -0
- data/lib/doorkeeper/orm/active_record/mixins/application.rb +10 -3
- data/lib/doorkeeper/version.rb +2 -2
- data/lib/generators/doorkeeper/remove_applications_secret_not_null_constraint_generator.rb +33 -0
- data/lib/generators/doorkeeper/templates/migration.rb.erb +1 -0
- data/lib/generators/doorkeeper/templates/remove_applications_secret_not_null_constraint.rb.erb +7 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4907be020648eb5c1dbc6e596c31c72c4133740e849ee1345ff337a4ce01c56
|
4
|
+
data.tar.gz: 831e491b48efe921e43065d393c6a785a9988e6057f1ed3e1e4f2e8626555e65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f97a8c5d6749cea33b31737988b26271d1ad03f4bd8f003b3674e8e81159a44e6a54f5611b868a06a9a074995bcc47165f222aadcd961746cc43776ee993c57
|
7
|
+
data.tar.gz: 3d2e13efa8bba0cedc4a63055d6ecb70f0af8d1dc0644047b4b1ecb3bc3bf5affd2a0a20d46989a4999b4361573c39ae72f516398c67ffe708512f0fd6e23011
|
data/CHANGELOG.md
CHANGED
@@ -9,6 +9,16 @@ User-visible changes worth mentioning.
|
|
9
9
|
|
10
10
|
Add your entry here.
|
11
11
|
|
12
|
+
## 5.8.0
|
13
|
+
|
14
|
+
- [#1739] Add support for dynamic scopes
|
15
|
+
- [#1715] Fix token introspection invalid request reason
|
16
|
+
- [#1714] Fix `Doorkeeper::AccessToken.find_or_create_for` with empty scopes which raises NoMethodError
|
17
|
+
- [#1712] Add `Pragma: no-cache` to token response
|
18
|
+
- [#1726] Refactor token introspection class.
|
19
|
+
- [#1727] Allow to set null secret value for Applications if they are public.
|
20
|
+
- [#1735] Add `pkce_code_challenge_methods` config option.
|
21
|
+
|
12
22
|
## 5.7.1
|
13
23
|
|
14
24
|
- [#1705] Add `force_pkce` option that requires non-confidential clients to use PKCE when requesting an access_token using an authorization code
|
@@ -11,6 +11,7 @@ module Doorkeeper
|
|
11
11
|
validate_reuse_access_token_value
|
12
12
|
validate_token_reuse_limit
|
13
13
|
validate_secret_strategies
|
14
|
+
validate_pkce_code_challenge_methods
|
14
15
|
end
|
15
16
|
|
16
17
|
private
|
@@ -48,6 +49,17 @@ module Doorkeeper
|
|
48
49
|
)
|
49
50
|
@token_reuse_limit = 100
|
50
51
|
end
|
52
|
+
|
53
|
+
def validate_pkce_code_challenge_methods
|
54
|
+
return if pkce_code_challenge_methods.all? {|method| method =~ /^plain$|^S256$/ }
|
55
|
+
|
56
|
+
::Rails.logger.warn(
|
57
|
+
"[DOORKEEPER] You have configured an invalid value for pkce_code_challenge_methods option. " \
|
58
|
+
"It will be set to default ['plain', 'S256']",
|
59
|
+
)
|
60
|
+
|
61
|
+
@pkce_code_challenge_methods = ['plain', 'S256']
|
62
|
+
end
|
51
63
|
end
|
52
64
|
end
|
53
65
|
end
|
data/lib/doorkeeper/config.rb
CHANGED
@@ -31,6 +31,16 @@ module Doorkeeper
|
|
31
31
|
@config.instance_variable_set(:@confirm_application_owner, true)
|
32
32
|
end
|
33
33
|
|
34
|
+
# Provide support for dynamic scopes (e.g. user:*) (disabled by default)
|
35
|
+
# Optional parameter delimiter (default ":") if you want to customize
|
36
|
+
# the delimiter separating the scope name and matching value.
|
37
|
+
#
|
38
|
+
# @param opts [Hash] the options to configure dynamic scopes
|
39
|
+
def enable_dynamic_scopes(opts = {})
|
40
|
+
@config.instance_variable_set(:@enable_dynamic_scopes, true)
|
41
|
+
@config.instance_variable_set(:@dynamic_scopes_delimiter, opts[:delimiter] || ':')
|
42
|
+
end
|
43
|
+
|
34
44
|
# Define default access token scopes for your provider
|
35
45
|
#
|
36
46
|
# @param scopes [Array] Default set of access (OAuth::Scopes.new)
|
@@ -243,6 +253,7 @@ module Doorkeeper
|
|
243
253
|
option :orm, default: :active_record
|
244
254
|
option :native_redirect_uri, default: "urn:ietf:wg:oauth:2.0:oob", deprecated: true
|
245
255
|
option :grant_flows, default: %w[authorization_code client_credentials]
|
256
|
+
option :pkce_code_challenge_methods, default: %w[plain S256]
|
246
257
|
option :handle_auth_errors, default: :render
|
247
258
|
option :token_lookup_batch_size, default: 10_000
|
248
259
|
# Sets the token_reuse_limit
|
@@ -418,7 +429,7 @@ module Doorkeeper
|
|
418
429
|
default: (lambda do |token, authorized_client, authorized_token|
|
419
430
|
if authorized_token
|
420
431
|
authorized_token.application == token&.application
|
421
|
-
elsif token
|
432
|
+
elsif token&.application
|
422
433
|
authorized_client == token.application
|
423
434
|
else
|
424
435
|
true
|
@@ -510,6 +521,14 @@ module Doorkeeper
|
|
510
521
|
option_set? :enable_application_owner
|
511
522
|
end
|
512
523
|
|
524
|
+
def enable_dynamic_scopes?
|
525
|
+
option_set? :enable_dynamic_scopes
|
526
|
+
end
|
527
|
+
|
528
|
+
def dynamic_scopes_delimiter
|
529
|
+
@dynamic_scopes_delimiter
|
530
|
+
end
|
531
|
+
|
513
532
|
def polymorphic_resource_owner?
|
514
533
|
option_set? :polymorphic_resource_owner
|
515
534
|
end
|
@@ -554,6 +573,12 @@ module Doorkeeper
|
|
554
573
|
@scopes_by_grant_type ||= {}
|
555
574
|
end
|
556
575
|
|
576
|
+
def pkce_code_challenge_methods_supported
|
577
|
+
return [] unless access_grant_model.pkce_supported?
|
578
|
+
|
579
|
+
pkce_code_challenge_methods
|
580
|
+
end
|
581
|
+
|
557
582
|
def client_credentials_methods
|
558
583
|
@client_credentials_methods ||= %i[from_basic from_params]
|
559
584
|
end
|
@@ -214,6 +214,8 @@ module Doorkeeper
|
|
214
214
|
# @return [Doorkeeper::AccessToken] existing record or a new one
|
215
215
|
#
|
216
216
|
def find_or_create_for(application:, resource_owner:, scopes:, **token_attributes)
|
217
|
+
scopes = Doorkeeper::OAuth::Scopes.from_string(scopes) if scopes.is_a?(String)
|
218
|
+
|
217
219
|
if Doorkeeper.config.reuse_access_token
|
218
220
|
custom_attributes = extract_custom_attributes(token_attributes).presence
|
219
221
|
access_token = matching_token_for(
|
@@ -154,7 +154,7 @@ module Doorkeeper
|
|
154
154
|
return true unless Doorkeeper.config.access_grant_model.pkce_supported?
|
155
155
|
|
156
156
|
code_challenge.blank? ||
|
157
|
-
(code_challenge_method.present? && code_challenge_method
|
157
|
+
(code_challenge_method.present? && Doorkeeper.config.pkce_code_challenge_methods_supported.include?(code_challenge_method))
|
158
158
|
end
|
159
159
|
|
160
160
|
def response_on_fragment?
|
@@ -6,6 +6,8 @@ module Doorkeeper
|
|
6
6
|
include Enumerable
|
7
7
|
include Comparable
|
8
8
|
|
9
|
+
DYNAMIC_SCOPE_WILDCARD = "*"
|
10
|
+
|
9
11
|
def self.from_string(string)
|
10
12
|
string ||= ""
|
11
13
|
new.tap do |scope|
|
@@ -26,7 +28,15 @@ module Doorkeeper
|
|
26
28
|
end
|
27
29
|
|
28
30
|
def exists?(scope)
|
29
|
-
|
31
|
+
scope = scope.to_s
|
32
|
+
|
33
|
+
@scopes.any? do |allowed_scope|
|
34
|
+
if dynamic_scopes_enabled? && dynamic_scopes_present?(allowed_scope, scope)
|
35
|
+
dynamic_scope_match?(allowed_scope, scope)
|
36
|
+
else
|
37
|
+
allowed_scope == scope
|
38
|
+
end
|
39
|
+
end
|
30
40
|
end
|
31
41
|
|
32
42
|
def add(*scopes)
|
@@ -66,6 +76,32 @@ module Doorkeeper
|
|
66
76
|
|
67
77
|
private
|
68
78
|
|
79
|
+
def dynamic_scopes_enabled?
|
80
|
+
Doorkeeper.config.enable_dynamic_scopes?
|
81
|
+
end
|
82
|
+
|
83
|
+
def dynamic_scope_delimiter
|
84
|
+
return unless dynamic_scopes_enabled?
|
85
|
+
|
86
|
+
@dynamic_scope_delimiter ||= Doorkeeper.config.dynamic_scopes_delimiter
|
87
|
+
end
|
88
|
+
|
89
|
+
def dynamic_scopes_present?(allowed, requested)
|
90
|
+
allowed.include?(dynamic_scope_delimiter) && requested.include?(dynamic_scope_delimiter)
|
91
|
+
end
|
92
|
+
|
93
|
+
def dynamic_scope_match?(allowed, requested)
|
94
|
+
allowed_pattern = allowed.split(dynamic_scope_delimiter, 2)
|
95
|
+
request_pattern = requested.split(dynamic_scope_delimiter, 2)
|
96
|
+
|
97
|
+
return false if allowed_pattern[0] != request_pattern[0]
|
98
|
+
return false if allowed_pattern[1].blank?
|
99
|
+
return false if request_pattern[1].blank?
|
100
|
+
return true if allowed_pattern[1] == DYNAMIC_SCOPE_WILDCARD && allowed_pattern[1].present?
|
101
|
+
|
102
|
+
allowed_pattern[1] == request_pattern[1]
|
103
|
+
end
|
104
|
+
|
69
105
|
def to_array(other)
|
70
106
|
case other
|
71
107
|
when Scopes
|
@@ -6,16 +6,15 @@ module Doorkeeper
|
|
6
6
|
#
|
7
7
|
# @see https://datatracker.ietf.org/doc/html/rfc7662
|
8
8
|
class TokenIntrospection
|
9
|
-
attr_reader :error
|
9
|
+
attr_reader :token, :error, :invalid_request_reason
|
10
10
|
|
11
11
|
def initialize(server, token)
|
12
12
|
@server = server
|
13
13
|
@token = token
|
14
|
-
|
15
|
-
authorize!
|
16
14
|
end
|
17
15
|
|
18
16
|
def authorized?
|
17
|
+
authorize!
|
19
18
|
@error.blank?
|
20
19
|
end
|
21
20
|
|
@@ -37,8 +36,7 @@ module Doorkeeper
|
|
37
36
|
|
38
37
|
private
|
39
38
|
|
40
|
-
attr_reader :server
|
41
|
-
attr_reader :invalid_request_reason
|
39
|
+
attr_reader :server
|
42
40
|
|
43
41
|
# If the protected resource uses OAuth 2.0 client credentials to
|
44
42
|
# authenticate to the introspection endpoint and its credentials are
|
@@ -60,24 +58,38 @@ module Doorkeeper
|
|
60
58
|
def authorize!
|
61
59
|
# Requested client authorization
|
62
60
|
if server.credentials
|
63
|
-
|
61
|
+
authorize_using_basic_auth!
|
64
62
|
elsif authorized_token
|
65
|
-
|
66
|
-
#
|
67
|
-
# If the protected resource uses an OAuth 2.0 bearer token to authorize
|
68
|
-
# its call to the introspection endpoint and the token used for
|
69
|
-
# authorization does not contain sufficient privileges or is otherwise
|
70
|
-
# invalid for this request, the authorization server responds with an
|
71
|
-
# HTTP 401 code as described in Section 3 of OAuth 2.0 Bearer Token
|
72
|
-
# Usage [RFC6750].
|
73
|
-
#
|
74
|
-
@error = Errors::InvalidToken unless valid_authorized_token?
|
63
|
+
authorize_using_bearer_token!
|
75
64
|
else
|
76
65
|
@error = Errors::InvalidRequest
|
77
66
|
@invalid_request_reason = :request_not_authorized
|
78
67
|
end
|
79
68
|
end
|
80
69
|
|
70
|
+
def authorize_using_basic_auth!
|
71
|
+
# Note that a properly formed and authorized query for an inactive or
|
72
|
+
# otherwise invalid token (or a token the protected resource is not
|
73
|
+
# allowed to know about) is not considered an error response by this
|
74
|
+
# specification. In these cases, the authorization server MUST instead
|
75
|
+
# respond with an introspection response with the "active" field set to
|
76
|
+
# "false" as described in Section 2.2.
|
77
|
+
@error = Errors::InvalidClient unless authorized_client
|
78
|
+
end
|
79
|
+
|
80
|
+
def authorize_using_bearer_token!
|
81
|
+
# Requested bearer token authorization
|
82
|
+
#
|
83
|
+
# If the protected resource uses an OAuth 2.0 bearer token to authorize
|
84
|
+
# its call to the introspection endpoint and the token used for
|
85
|
+
# authorization does not contain sufficient privileges or is otherwise
|
86
|
+
# invalid for this request, the authorization server responds with an
|
87
|
+
# HTTP 401 code as described in Section 3 of OAuth 2.0 Bearer Token
|
88
|
+
# Usage [RFC6750].
|
89
|
+
#
|
90
|
+
@error = Errors::InvalidToken unless valid_authorized_token?
|
91
|
+
end
|
92
|
+
|
81
93
|
# Client Authentication
|
82
94
|
def authorized_client
|
83
95
|
@authorized_client ||= server.credentials && server.client
|
@@ -20,11 +20,13 @@ module Doorkeeper::Orm::ActiveRecord::Mixins
|
|
20
20
|
dependent: :delete_all,
|
21
21
|
class_name: Doorkeeper.config.access_token_class.to_s
|
22
22
|
|
23
|
-
validates :name, :
|
23
|
+
validates :name, :uid, presence: true
|
24
|
+
validates :secret, presence: true, if: -> { secret_required? }
|
24
25
|
validates :uid, uniqueness: { case_sensitive: true }
|
25
|
-
validates_with Doorkeeper::RedirectUriValidator, attributes: [:redirect_uri]
|
26
26
|
validates :confidential, inclusion: { in: [true, false] }
|
27
27
|
|
28
|
+
validates_with Doorkeeper::RedirectUriValidator, attributes: [:redirect_uri]
|
29
|
+
|
28
30
|
validate :scopes_match_configured, if: :enforce_scopes?
|
29
31
|
|
30
32
|
before_validation :generate_uid, :generate_secret, on: :create
|
@@ -118,7 +120,7 @@ module Doorkeeper::Orm::ActiveRecord::Mixins
|
|
118
120
|
end
|
119
121
|
|
120
122
|
def generate_secret
|
121
|
-
return if secret.present?
|
123
|
+
return if secret.present? || !secret_required?
|
122
124
|
|
123
125
|
renew_secret
|
124
126
|
end
|
@@ -136,6 +138,11 @@ module Doorkeeper::Orm::ActiveRecord::Mixins
|
|
136
138
|
Doorkeeper.config.enforce_configured_scopes?
|
137
139
|
end
|
138
140
|
|
141
|
+
def secret_required?
|
142
|
+
confidential? ||
|
143
|
+
!self.class.columns.detect { |column| column.name == "secret" }&.null
|
144
|
+
end
|
145
|
+
|
139
146
|
# Helper method to extract collection of serializable attribute names
|
140
147
|
# considering serialization options (like `only`, `except` and so on).
|
141
148
|
#
|
data/lib/doorkeeper/version.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
require "rails/generators/active_record"
|
5
|
+
|
6
|
+
module Doorkeeper
|
7
|
+
# Generates migration with which drops NOT NULL constraint and allows not
|
8
|
+
# to bloat the database with redundant secret value.
|
9
|
+
#
|
10
|
+
class RemoveApplicationSecretNotNullConstraint < ::Rails::Generators::Base
|
11
|
+
include ::Rails::Generators::Migration
|
12
|
+
source_root File.expand_path("templates", __dir__)
|
13
|
+
desc "Removes NOT NULL constraint for OAuth2 applications."
|
14
|
+
|
15
|
+
def enable_polymorphic_resource_owner
|
16
|
+
migration_template(
|
17
|
+
"remove_applications_secret_not_null_constraint.rb.erb",
|
18
|
+
"db/migrate/remove_applications_secret_not_null_constraint.rb",
|
19
|
+
migration_version: migration_version,
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.next_migration_number(dirname)
|
24
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def migration_version
|
30
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -5,6 +5,7 @@ class CreateDoorkeeperTables < ActiveRecord::Migration<%= migration_version %>
|
|
5
5
|
create_table :oauth_applications do |t|
|
6
6
|
t.string :name, null: false
|
7
7
|
t.string :uid, null: false
|
8
|
+
# Remove `null: false` or use conditional constraint if you are planning to use public clients.
|
8
9
|
t.string :secret, null: false
|
9
10
|
|
10
11
|
# Remove `null: false` if you are planning to use grant flows
|
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.
|
4
|
+
version: 5.8.0
|
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: 2024-
|
14
|
+
date: 2024-10-31 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: railties
|
@@ -305,6 +305,7 @@ files:
|
|
305
305
|
- lib/generators/doorkeeper/migration_generator.rb
|
306
306
|
- lib/generators/doorkeeper/pkce_generator.rb
|
307
307
|
- lib/generators/doorkeeper/previous_refresh_token_generator.rb
|
308
|
+
- lib/generators/doorkeeper/remove_applications_secret_not_null_constraint_generator.rb
|
308
309
|
- lib/generators/doorkeeper/templates/README
|
309
310
|
- lib/generators/doorkeeper/templates/add_confidential_to_applications.rb.erb
|
310
311
|
- lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb.erb
|
@@ -313,6 +314,7 @@ files:
|
|
313
314
|
- lib/generators/doorkeeper/templates/enable_polymorphic_resource_owner_migration.rb.erb
|
314
315
|
- lib/generators/doorkeeper/templates/initializer.rb
|
315
316
|
- lib/generators/doorkeeper/templates/migration.rb.erb
|
317
|
+
- lib/generators/doorkeeper/templates/remove_applications_secret_not_null_constraint.rb.erb
|
316
318
|
- lib/generators/doorkeeper/views_generator.rb
|
317
319
|
- vendor/assets/stylesheets/doorkeeper/bootstrap.min.css
|
318
320
|
homepage: https://github.com/doorkeeper-gem/doorkeeper
|
@@ -346,7 +348,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
346
348
|
- !ruby/object:Gem::Version
|
347
349
|
version: '0'
|
348
350
|
requirements: []
|
349
|
-
rubygems_version: 3.
|
351
|
+
rubygems_version: 3.5.15
|
350
352
|
signing_key:
|
351
353
|
specification_version: 4
|
352
354
|
summary: OAuth 2 provider for Rails and Grape
|