doorkeeper 5.4.0.rc2 → 5.5.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of doorkeeper might be problematic. Click here for more details.

Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -10
  3. data/README.md +4 -4
  4. data/app/controllers/doorkeeper/application_controller.rb +1 -0
  5. data/app/controllers/doorkeeper/authorizations_controller.rb +16 -5
  6. data/app/controllers/doorkeeper/authorized_applications_controller.rb +1 -1
  7. data/app/controllers/doorkeeper/token_info_controller.rb +12 -2
  8. data/app/controllers/doorkeeper/tokens_controller.rb +34 -26
  9. data/app/views/doorkeeper/applications/show.html.erb +16 -12
  10. data/app/views/doorkeeper/authorizations/form_post.html.erb +11 -0
  11. data/config/locales/en.yml +3 -1
  12. data/lib/doorkeeper.rb +5 -0
  13. data/lib/doorkeeper/config.rb +91 -62
  14. data/lib/doorkeeper/config/option.rb +1 -3
  15. data/lib/doorkeeper/config/validations.rb +53 -0
  16. data/lib/doorkeeper/engine.rb +1 -1
  17. data/lib/doorkeeper/grant_flow.rb +45 -0
  18. data/lib/doorkeeper/grant_flow/fallback_flow.rb +15 -0
  19. data/lib/doorkeeper/grant_flow/flow.rb +44 -0
  20. data/lib/doorkeeper/grant_flow/registry.rb +50 -0
  21. data/lib/doorkeeper/helpers/controller.rb +4 -0
  22. data/lib/doorkeeper/models/access_grant_mixin.rb +1 -2
  23. data/lib/doorkeeper/models/access_token_mixin.rb +4 -4
  24. data/lib/doorkeeper/models/concerns/revocable.rb +1 -1
  25. data/lib/doorkeeper/oauth/authorization/code.rb +5 -1
  26. data/lib/doorkeeper/oauth/authorization/context.rb +5 -5
  27. data/lib/doorkeeper/oauth/authorization/token.rb +11 -5
  28. data/lib/doorkeeper/oauth/authorization/uri_builder.rb +1 -1
  29. data/lib/doorkeeper/oauth/authorization_code_request.rb +10 -17
  30. data/lib/doorkeeper/oauth/base_request.rb +1 -1
  31. data/lib/doorkeeper/oauth/client_credentials/creator.rb +2 -1
  32. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +1 -0
  33. data/lib/doorkeeper/oauth/code_request.rb +2 -2
  34. data/lib/doorkeeper/oauth/code_response.rb +17 -11
  35. data/lib/doorkeeper/oauth/error_response.rb +4 -3
  36. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +1 -3
  37. data/lib/doorkeeper/oauth/password_access_token_request.rb +23 -3
  38. data/lib/doorkeeper/oauth/pre_authorization.rb +33 -8
  39. data/lib/doorkeeper/oauth/refresh_token_request.rb +13 -0
  40. data/lib/doorkeeper/oauth/token.rb +3 -3
  41. data/lib/doorkeeper/oauth/token_introspection.rb +1 -5
  42. data/lib/doorkeeper/oauth/token_request.rb +1 -1
  43. data/lib/doorkeeper/orm/active_record.rb +5 -14
  44. data/lib/doorkeeper/orm/active_record/mixins/access_grant.rb +11 -1
  45. data/lib/doorkeeper/orm/active_record/mixins/access_token.rb +9 -1
  46. data/lib/doorkeeper/orm/active_record/mixins/application.rb +26 -15
  47. data/lib/doorkeeper/orm/active_record/redirect_uri_validator.rb +5 -0
  48. data/lib/doorkeeper/rails/routes.rb +1 -3
  49. data/lib/doorkeeper/rake/db.rake +3 -3
  50. data/lib/doorkeeper/rake/setup.rake +5 -0
  51. data/lib/doorkeeper/request.rb +49 -12
  52. data/lib/doorkeeper/request/password.rb +1 -0
  53. data/lib/doorkeeper/server.rb +1 -1
  54. data/lib/doorkeeper/stale_records_cleaner.rb +4 -4
  55. data/lib/doorkeeper/version.rb +3 -7
  56. data/lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb.erb +1 -1
  57. data/lib/generators/doorkeeper/templates/initializer.rb +9 -7
  58. metadata +26 -13
@@ -89,8 +89,7 @@ module Doorkeeper
89
89
  # suitable for PKCE validation
90
90
  #
91
91
  def generate_code_challenge(code_verifier)
92
- padded_result = Base64.urlsafe_encode64(Digest::SHA256.digest(code_verifier))
93
- padded_result.split("=")[0] # Remove any trailing '='
92
+ Base64.urlsafe_encode64(Digest::SHA256.digest(code_verifier), padding: false)
94
93
  end
95
94
 
96
95
  def pkce_supported?
@@ -94,8 +94,8 @@ module Doorkeeper
94
94
  # Interface to enumerate access token records in batches in order not
95
95
  # to bloat the memory. Could be overloaded in any ORM extension.
96
96
  #
97
- def find_access_token_in_batches(relation, *args, &block)
98
- relation.find_in_batches(*args, &block)
97
+ def find_access_token_in_batches(relation, **args, &block)
98
+ relation.find_in_batches(**args, &block)
99
99
  end
100
100
 
101
101
  # Enumerates AccessToken records in batches to find a matching token.
@@ -374,10 +374,10 @@ module Doorkeeper
374
374
  # and clears `:previous_refresh_token` attribute.
375
375
  #
376
376
  def revoke_previous_refresh_token!
377
- return unless self.class.refresh_token_revoked_on_use?
377
+ return if !self.class.refresh_token_revoked_on_use? || previous_refresh_token.blank?
378
378
 
379
379
  old_refresh_token&.revoke
380
- update_column(:previous_refresh_token, "")
380
+ update_attribute(:previous_refresh_token, "")
381
381
  end
382
382
 
383
383
  private
@@ -9,7 +9,7 @@ module Doorkeeper
9
9
  # @param clock [Time] time object
10
10
  #
11
11
  def revoke(clock = Time)
12
- update_column(:revoked_at, clock.now.utc)
12
+ update_attribute(:revoked_at, clock.now.utc)
13
13
  end
14
14
 
15
15
  # Indicates whether the object has been revoked.
@@ -11,7 +11,7 @@ module Doorkeeper
11
11
  @resource_owner = resource_owner
12
12
  end
13
13
 
14
- def issue_token
14
+ def issue_token!
15
15
  return @token if defined?(@token)
16
16
 
17
17
  @token = Doorkeeper.config.access_grant_model.create!(access_grant_attributes)
@@ -21,6 +21,10 @@ module Doorkeeper
21
21
  { action: :show, code: token.plaintext_token }
22
22
  end
23
23
 
24
+ def access_grant?
25
+ true
26
+ end
27
+
24
28
  private
25
29
 
26
30
  def authorization_code_expires_in
@@ -4,12 +4,12 @@ module Doorkeeper
4
4
  module OAuth
5
5
  module Authorization
6
6
  class Context
7
- attr_reader :client, :grant_type, :scopes
7
+ attr_reader :client, :grant_type, :resource_owner, :scopes
8
8
 
9
- def initialize(client, grant_type, scopes)
10
- @client = client
11
- @grant_type = grant_type
12
- @scopes = scopes
9
+ def initialize(**attributes)
10
+ attributes.each do |name, value|
11
+ instance_variable_set(:"@#{name}", value) if respond_to?(name)
12
+ end
13
13
  end
14
14
  end
15
15
  end
@@ -7,7 +7,7 @@ module Doorkeeper
7
7
  attr_reader :pre_auth, :resource_owner, :token
8
8
 
9
9
  class << self
10
- def build_context(pre_auth_or_oauth_client, grant_type, scopes)
10
+ def build_context(pre_auth_or_oauth_client, grant_type, scopes, resource_owner)
11
11
  oauth_client = if pre_auth_or_oauth_client.respond_to?(:application)
12
12
  pre_auth_or_oauth_client.application
13
13
  elsif pre_auth_or_oauth_client.respond_to?(:client)
@@ -17,9 +17,10 @@ module Doorkeeper
17
17
  end
18
18
 
19
19
  Doorkeeper::OAuth::Authorization::Context.new(
20
- oauth_client,
21
- grant_type,
22
- scopes,
20
+ client: oauth_client,
21
+ grant_type: grant_type,
22
+ scopes: scopes,
23
+ resource_owner: resource_owner,
23
24
  )
24
25
  end
25
26
 
@@ -48,13 +49,14 @@ module Doorkeeper
48
49
  @resource_owner = resource_owner
49
50
  end
50
51
 
51
- def issue_token
52
+ def issue_token!
52
53
  return @token if defined?(@token)
53
54
 
54
55
  context = self.class.build_context(
55
56
  pre_auth.client,
56
57
  Doorkeeper::OAuth::IMPLICIT,
57
58
  pre_auth.scopes,
59
+ resource_owner,
58
60
  )
59
61
 
60
62
  @token = Doorkeeper.config.access_token_model.find_or_create_for(
@@ -74,6 +76,10 @@ module Doorkeeper
74
76
  }
75
77
  end
76
78
 
79
+ def access_token?
80
+ true
81
+ end
82
+
77
83
  private
78
84
 
79
85
  def controller
@@ -23,7 +23,7 @@ module Doorkeeper
23
23
  private
24
24
 
25
25
  def build_query(parameters = {})
26
- parameters = parameters.reject { |_, value| value.blank? }
26
+ parameters.reject! { |_, value| value.blank? }
27
27
  Rack::Utils.build_query(parameters)
28
28
  end
29
29
  end
@@ -3,7 +3,6 @@
3
3
  module Doorkeeper
4
4
  module OAuth
5
5
  class AuthorizationCodeRequest < BaseRequest
6
- validate :pkce_support, error: :invalid_request
7
6
  validate :params, error: :invalid_request
8
7
  validate :client, error: :invalid_client
9
8
  validate :grant, error: :invalid_grant
@@ -32,12 +31,6 @@ module Doorkeeper
32
31
 
33
32
  grant.revoke
34
33
 
35
- resource_owner = if Doorkeeper.config.polymorphic_resource_owner?
36
- grant.resource_owner
37
- else
38
- grant.resource_owner_id
39
- end
40
-
41
34
  find_or_create_access_token(
42
35
  grant.application,
43
36
  resource_owner,
@@ -49,16 +42,16 @@ module Doorkeeper
49
42
  super
50
43
  end
51
44
 
52
- def pkce_supported?
53
- Doorkeeper.config.access_grant_model.pkce_supported?
45
+ def resource_owner
46
+ if Doorkeeper.config.polymorphic_resource_owner?
47
+ grant.resource_owner
48
+ else
49
+ grant.resource_owner_id
50
+ end
54
51
  end
55
52
 
56
- def validate_pkce_support
57
- @invalid_request_reason = :not_support_pkce if grant &&
58
- !pkce_supported? &&
59
- code_verifier.present?
60
-
61
- @invalid_request_reason.nil?
53
+ def pkce_supported?
54
+ Doorkeeper.config.access_grant_model.pkce_supported?
62
55
  end
63
56
 
64
57
  def validate_params
@@ -91,8 +84,8 @@ module Doorkeeper
91
84
  # if either side (server or client) request PKCE, check the verifier
92
85
  # against the DB - if PKCE is supported
93
86
  def validate_code_verifier
94
- return true unless grant.uses_pkce? || code_verifier
95
- return false unless pkce_supported?
87
+ return true unless pkce_supported?
88
+ return grant.code_challenge.blank? if code_verifier.blank?
96
89
 
97
90
  if grant.code_challenge_method == "S256"
98
91
  grant.code_challenge == generate_code_challenge(code_verifier)
@@ -27,7 +27,7 @@ module Doorkeeper
27
27
  end
28
28
 
29
29
  def find_or_create_access_token(client, resource_owner, scopes, server)
30
- context = Authorization::Token.build_context(client, grant_type, scopes)
30
+ context = Authorization::Token.build_context(client, grant_type, scopes, resource_owner)
31
31
  @access_token = server_config.access_token_model.find_or_create_for(
32
32
  application: client,
33
33
  resource_owner: resource_owner,
@@ -39,7 +39,8 @@ module Doorkeeper
39
39
  end
40
40
 
41
41
  def lookup_existing_token?
42
- server_config.reuse_access_token || server_config.revoke_previous_client_credentials_token?
42
+ server_config.reuse_access_token ||
43
+ server_config.revoke_previous_client_credentials_token?
43
44
  end
44
45
 
45
46
  def find_existing_token_for(client, scopes)
@@ -30,6 +30,7 @@ module Doorkeeper
30
30
  client,
31
31
  Doorkeeper::OAuth::CLIENT_CREDENTIALS,
32
32
  scopes,
33
+ nil,
33
34
  )
34
35
  ttl = Authorization::Token.access_token_expires_in(@server, context)
35
36
 
@@ -6,13 +6,13 @@ module Doorkeeper
6
6
  attr_reader :pre_auth, :resource_owner
7
7
 
8
8
  def initialize(pre_auth, resource_owner)
9
- @pre_auth = pre_auth
9
+ @pre_auth = pre_auth
10
10
  @resource_owner = resource_owner
11
11
  end
12
12
 
13
13
  def authorize
14
14
  auth = Authorization::Code.new(pre_auth, resource_owner)
15
- auth.issue_token
15
+ auth.issue_token!
16
16
  CodeResponse.new(pre_auth, auth)
17
17
  end
18
18
 
@@ -21,23 +21,29 @@ module Doorkeeper
21
21
  auth.token
22
22
  end
23
23
 
24
- def redirect_uri
25
- if URIChecker.oob_uri?(pre_auth.redirect_uri)
26
- auth.oob_redirect
27
- elsif response_on_fragment
28
- Authorization::URIBuilder.uri_with_fragment(
29
- pre_auth.redirect_uri,
24
+ def body
25
+ if auth.try(:access_token?)
26
+ {
30
27
  access_token: auth.token.plaintext_token,
31
28
  token_type: auth.token.token_type,
32
29
  expires_in: auth.token.expires_in_seconds,
33
30
  state: pre_auth.state,
34
- )
35
- else
36
- Authorization::URIBuilder.uri_with_query(
37
- pre_auth.redirect_uri,
31
+ }
32
+ elsif auth.try(:access_grant?)
33
+ {
38
34
  code: auth.token.plaintext_token,
39
35
  state: pre_auth.state,
40
- )
36
+ }
37
+ end
38
+ end
39
+
40
+ def redirect_uri
41
+ if URIChecker.oob_uri?(pre_auth.redirect_uri)
42
+ auth.oob_redirect
43
+ elsif response_on_fragment
44
+ Authorization::URIBuilder.uri_with_fragment(pre_auth.redirect_uri, body)
45
+ else
46
+ Authorization::URIBuilder.uri_with_query(pre_auth.redirect_uri, body)
41
47
  end
42
48
  end
43
49
  end
@@ -5,6 +5,8 @@ module Doorkeeper
5
5
  class ErrorResponse < BaseResponse
6
6
  include OAuth::Helpers
7
7
 
8
+ NON_REDIRECTABLE_STATES = %i[invalid_redirect_uri invalid_client unauthorized_client].freeze
9
+
8
10
  def self.from_request(request, attributes = {})
9
11
  new(
10
12
  attributes.merge(
@@ -32,7 +34,7 @@ module Doorkeeper
32
34
  end
33
35
 
34
36
  def status
35
- if name == :invalid_client
37
+ if name == :invalid_client || name == :unauthorized_client
36
38
  :unauthorized
37
39
  else
38
40
  :bad_request
@@ -40,8 +42,7 @@ module Doorkeeper
40
42
  end
41
43
 
42
44
  def redirectable?
43
- name != :invalid_redirect_uri && name != :invalid_client &&
44
- !URIChecker.oob_uri?(@redirect_uri)
45
+ !NON_REDIRECTABLE_STATES.include?(name) && !URIChecker.oob_uri?(@redirect_uri)
45
46
  end
46
47
 
47
48
  def redirect_uri
@@ -12,9 +12,7 @@ module Doorkeeper
12
12
  @scope_str = scope_str
13
13
  @valid_scopes = valid_scopes(server_scopes, app_scopes)
14
14
 
15
- if grant_type
16
- @scopes_by_grant_type = Doorkeeper.config.scopes_by_grant_type[grant_type.to_sym]
17
- end
15
+ @scopes_by_grant_type = Doorkeeper.config.scopes_by_grant_type[grant_type.to_sym] if grant_type
18
16
  end
19
17
 
20
18
  def valid?
@@ -10,12 +10,13 @@ module Doorkeeper
10
10
  validate :resource_owner, error: :invalid_grant
11
11
  validate :scopes, error: :invalid_scope
12
12
 
13
- attr_reader :client, :resource_owner, :parameters, :access_token
13
+ attr_reader :client, :credentials, :resource_owner, :parameters, :access_token
14
14
 
15
- def initialize(server, client, resource_owner, parameters = {})
15
+ def initialize(server, client, credentials, resource_owner, parameters = {})
16
16
  @server = server
17
17
  @resource_owner = resource_owner
18
18
  @client = client
19
+ @credentials = credentials
19
20
  @parameters = parameters
20
21
  @original_scopes = parameters[:scope]
21
22
  @grant_type = Doorkeeper::OAuth::PASSWORD
@@ -43,8 +44,27 @@ module Doorkeeper
43
44
  resource_owner.present?
44
45
  end
45
46
 
47
+ # Section 4.3.2. Access Token Request for Resource Owner Password Credentials Grant:
48
+ #
49
+ # If the client type is confidential or the client was issued client credentials (or assigned
50
+ # other authentication requirements), the client MUST authenticate with the authorization
51
+ # server as described in Section 3.2.1.
52
+ #
53
+ # The authorization server MUST:
54
+ #
55
+ # o require client authentication for confidential clients or for any client that was
56
+ # issued client credentials (or with other authentication requirements)
57
+ #
58
+ # o authenticate the client if client authentication is included,
59
+ #
60
+ # @see https://tools.ietf.org/html/rfc6749#section-4.3
61
+ #
46
62
  def validate_client
47
- !parameters[:client_id] || client.present?
63
+ if Doorkeeper.config.skip_client_authentication_for_password_grant
64
+ client.present? || (!parameters[:client_id] && credentials.blank?)
65
+ else
66
+ client.present?
67
+ end
48
68
  end
49
69
 
50
70
  def validate_client_supports_grant_flow
@@ -12,16 +12,19 @@ module Doorkeeper
12
12
  validate :redirect_uri, error: :invalid_redirect_uri
13
13
  validate :params, error: :invalid_request
14
14
  validate :response_type, error: :unsupported_response_type
15
+ validate :response_mode, error: :unsupported_response_mode
15
16
  validate :scopes, error: :invalid_scope
16
17
  validate :code_challenge_method, error: :invalid_code_challenge_method
17
18
 
18
19
  attr_reader :client, :code_challenge, :code_challenge_method, :missing_param,
19
- :redirect_uri, :resource_owner, :response_type, :state
20
+ :redirect_uri, :resource_owner, :response_type, :state,
21
+ :authorization_response_flow, :response_mode
20
22
 
21
23
  def initialize(server, parameters = {}, resource_owner = nil)
22
24
  @server = server
23
25
  @client_id = parameters[:client_id]
24
26
  @response_type = parameters[:response_type]
27
+ @response_mode = parameters[:response_mode]
25
28
  @redirect_uri = parameters[:redirect_uri]
26
29
  @scope = parameters[:scope]
27
30
  @state = parameters[:state]
@@ -57,6 +60,10 @@ module Doorkeeper
57
60
  pre_auth_hash
58
61
  end
59
62
 
63
+ def form_post_response?
64
+ response_mode == "form_post"
65
+ end
66
+
60
67
  private
61
68
 
62
69
  attr_reader :client_id, :server
@@ -84,6 +91,11 @@ module Doorkeeper
84
91
  Doorkeeper.config.allow_grant_flow_for_client?(grant_type, client.application)
85
92
  end
86
93
 
94
+ def validate_resource_owner_authorize_for_client
95
+ # The `authorize_resource_owner_for_client` config option is used for this validation
96
+ client.application.authorized_for_resource_owner?(@resource_owner)
97
+ end
98
+
87
99
  def validate_redirect_uri
88
100
  return false if redirect_uri.blank?
89
101
 
@@ -104,7 +116,21 @@ module Doorkeeper
104
116
  end
105
117
 
106
118
  def validate_response_type
107
- server.authorization_response_types.include?(response_type)
119
+ server.authorization_response_flows.any? do |flow|
120
+ if flow.matches_response_type?(response_type)
121
+ @authorization_response_flow = flow
122
+ true
123
+ end
124
+ end
125
+ end
126
+
127
+ def validate_response_mode
128
+ if response_mode.blank?
129
+ @response_mode = authorization_response_flow.default_response_mode
130
+ return true
131
+ end
132
+
133
+ authorization_response_flow.matches_response_mode?(response_mode)
108
134
  end
109
135
 
110
136
  def validate_scopes
@@ -117,17 +143,16 @@ module Doorkeeper
117
143
  end
118
144
 
119
145
  def validate_code_challenge_method
146
+ return true unless Doorkeeper.config.access_grant_model.pkce_supported?
147
+
120
148
  code_challenge.blank? ||
121
149
  (code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/)
122
150
  end
123
151
 
124
- def validate_resource_owner_authorize_for_client
125
- # The `authorize_resource_owner_for_client` config option is used for this validation
126
- client.application.authorized_for_resource_owner?(@resource_owner)
127
- end
128
-
129
152
  def response_on_fragment?
130
- response_type == "token"
153
+ return response_type == "token" if response_mode.nil?
154
+
155
+ response_mode == "fragment"
131
156
  end
132
157
 
133
158
  def grant_type