doorkeeper 4.4.3 → 5.0.0

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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.gitlab-ci.yml +16 -0
  4. data/.travis.yml +2 -0
  5. data/Appraisals +2 -2
  6. data/Gemfile +1 -1
  7. data/NEWS.md +61 -8
  8. data/README.md +92 -9
  9. data/Rakefile +6 -0
  10. data/UPGRADE.md +2 -0
  11. data/app/assets/stylesheets/doorkeeper/admin/application.css +2 -2
  12. data/app/controllers/doorkeeper/application_controller.rb +4 -3
  13. data/app/controllers/doorkeeper/application_metal_controller.rb +4 -0
  14. data/app/controllers/doorkeeper/applications_controller.rb +42 -22
  15. data/app/controllers/doorkeeper/authorizations_controller.rb +55 -12
  16. data/app/controllers/doorkeeper/authorized_applications_controller.rb +19 -2
  17. data/app/controllers/doorkeeper/tokens_controller.rb +2 -6
  18. data/app/helpers/doorkeeper/dashboard_helper.rb +7 -7
  19. data/app/validators/redirect_uri_validator.rb +3 -2
  20. data/app/views/doorkeeper/applications/_delete_form.html.erb +3 -1
  21. data/app/views/doorkeeper/applications/_form.html.erb +25 -24
  22. data/app/views/doorkeeper/applications/edit.html.erb +1 -1
  23. data/app/views/doorkeeper/applications/index.html.erb +17 -7
  24. data/app/views/doorkeeper/applications/new.html.erb +1 -1
  25. data/app/views/doorkeeper/applications/show.html.erb +6 -6
  26. data/app/views/doorkeeper/authorizations/error.html.erb +1 -1
  27. data/app/views/doorkeeper/authorizations/new.html.erb +4 -0
  28. data/app/views/layouts/doorkeeper/admin.html.erb +15 -15
  29. data/config/locales/en.yml +10 -1
  30. data/doorkeeper.gemspec +18 -20
  31. data/gemfiles/rails_5_2.gemfile +1 -1
  32. data/gemfiles/rails_master.gemfile +4 -1
  33. data/lib/doorkeeper/config.rb +75 -39
  34. data/lib/doorkeeper/engine.rb +4 -0
  35. data/lib/doorkeeper/errors.rb +2 -5
  36. data/lib/doorkeeper/grape/helpers.rb +1 -1
  37. data/lib/doorkeeper/helpers/controller.rb +7 -2
  38. data/lib/doorkeeper/models/access_grant_mixin.rb +71 -0
  39. data/lib/doorkeeper/models/access_token_mixin.rb +39 -22
  40. data/lib/doorkeeper/models/concerns/scopes.rb +1 -1
  41. data/lib/doorkeeper/oauth/authorization/code.rb +31 -8
  42. data/lib/doorkeeper/oauth/authorization/context.rb +15 -0
  43. data/lib/doorkeeper/oauth/authorization/token.rb +36 -14
  44. data/lib/doorkeeper/oauth/authorization_code_request.rb +27 -2
  45. data/lib/doorkeeper/oauth/base_request.rb +20 -9
  46. data/lib/doorkeeper/oauth/client/credentials.rb +1 -1
  47. data/lib/doorkeeper/oauth/client.rb +0 -2
  48. data/lib/doorkeeper/oauth/client_credentials/creator.rb +2 -1
  49. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +6 -3
  50. data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -6
  51. data/lib/doorkeeper/oauth/client_credentials_request.rb +0 -4
  52. data/lib/doorkeeper/oauth/error_response.rb +11 -3
  53. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +0 -8
  54. data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -4
  55. data/lib/doorkeeper/oauth/pre_authorization.rb +41 -11
  56. data/lib/doorkeeper/oauth/refresh_token_request.rb +6 -1
  57. data/lib/doorkeeper/oauth/scopes.rb +1 -1
  58. data/lib/doorkeeper/oauth/token.rb +5 -2
  59. data/lib/doorkeeper/oauth/token_introspection.rb +2 -2
  60. data/lib/doorkeeper/oauth/token_response.rb +4 -2
  61. data/lib/doorkeeper/oauth.rb +13 -0
  62. data/lib/doorkeeper/orm/active_record/application.rb +22 -14
  63. data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +26 -0
  64. data/lib/doorkeeper/orm/active_record.rb +2 -0
  65. data/lib/doorkeeper/rails/helpers.rb +2 -4
  66. data/lib/doorkeeper/rails/routes.rb +14 -6
  67. data/lib/doorkeeper/rake/db.rake +40 -0
  68. data/lib/doorkeeper/rake/setup.rake +6 -0
  69. data/lib/doorkeeper/rake.rb +14 -0
  70. data/lib/doorkeeper/request/authorization_code.rb +0 -2
  71. data/lib/doorkeeper/request/client_credentials.rb +0 -2
  72. data/lib/doorkeeper/request/code.rb +0 -2
  73. data/lib/doorkeeper/request/password.rb +0 -2
  74. data/lib/doorkeeper/request/refresh_token.rb +0 -2
  75. data/lib/doorkeeper/request/token.rb +0 -2
  76. data/lib/doorkeeper/request.rb +28 -35
  77. data/lib/doorkeeper/version.rb +5 -25
  78. data/lib/doorkeeper.rb +19 -17
  79. data/lib/generators/doorkeeper/application_owner_generator.rb +23 -18
  80. data/lib/generators/doorkeeper/confidential_applications_generator.rb +32 -0
  81. data/lib/generators/doorkeeper/install_generator.rb +17 -9
  82. data/lib/generators/doorkeeper/migration_generator.rb +23 -18
  83. data/lib/generators/doorkeeper/pkce_generator.rb +32 -0
  84. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +29 -24
  85. data/lib/generators/doorkeeper/templates/add_confidential_to_applications.rb.erb +13 -0
  86. data/lib/generators/doorkeeper/templates/enable_pkce_migration.rb.erb +6 -0
  87. data/lib/generators/doorkeeper/templates/initializer.rb +76 -11
  88. data/lib/generators/doorkeeper/views_generator.rb +3 -1
  89. data/spec/controllers/application_metal_controller_spec.rb +50 -0
  90. data/spec/controllers/applications_controller_spec.rb +126 -13
  91. data/spec/controllers/authorizations_controller_spec.rb +277 -47
  92. data/spec/controllers/protected_resources_controller_spec.rb +16 -16
  93. data/spec/controllers/token_info_controller_spec.rb +4 -12
  94. data/spec/controllers/tokens_controller_spec.rb +13 -15
  95. data/spec/dummy/app/assets/config/manifest.js +2 -0
  96. data/spec/dummy/config/environments/test.rb +4 -5
  97. data/spec/dummy/config/initializers/doorkeeper.rb +10 -5
  98. data/spec/dummy/config/initializers/new_framework_defaults.rb +4 -0
  99. data/spec/dummy/config/routes.rb +3 -42
  100. data/spec/dummy/db/migrate/20170822064514_enable_pkce.rb +6 -0
  101. data/spec/dummy/db/migrate/{20180210183654_add_confidential_to_application.rb → 20180210183654_add_confidential_to_applications.rb} +1 -1
  102. data/spec/dummy/db/schema.rb +36 -36
  103. data/spec/generators/application_owner_generator_spec.rb +1 -1
  104. data/spec/generators/confidential_applications_generator_spec.rb +45 -0
  105. data/spec/generators/install_generator_spec.rb +1 -1
  106. data/spec/generators/migration_generator_spec.rb +1 -1
  107. data/spec/generators/pkce_generator_spec.rb +43 -0
  108. data/spec/generators/previous_refresh_token_generator_spec.rb +1 -1
  109. data/spec/generators/views_generator_spec.rb +1 -1
  110. data/spec/grape/grape_integration_spec.rb +1 -1
  111. data/spec/helpers/doorkeeper/dashboard_helper_spec.rb +1 -1
  112. data/spec/lib/config_spec.rb +80 -31
  113. data/spec/lib/doorkeeper_spec.rb +1 -126
  114. data/spec/lib/models/expirable_spec.rb +0 -3
  115. data/spec/lib/models/revocable_spec.rb +0 -2
  116. data/spec/lib/models/scopes_spec.rb +0 -4
  117. data/spec/lib/oauth/authorization/uri_builder_spec.rb +0 -4
  118. data/spec/lib/oauth/authorization_code_request_spec.rb +9 -2
  119. data/spec/lib/oauth/base_request_spec.rb +40 -2
  120. data/spec/lib/oauth/base_response_spec.rb +1 -1
  121. data/spec/lib/oauth/client/credentials_spec.rb +1 -3
  122. data/spec/lib/oauth/client_credentials/creator_spec.rb +5 -1
  123. data/spec/lib/oauth/client_credentials/issuer_spec.rb +26 -7
  124. data/spec/lib/oauth/client_credentials/validation_spec.rb +2 -3
  125. data/spec/lib/oauth/client_credentials_integration_spec.rb +1 -1
  126. data/spec/lib/oauth/client_credentials_request_spec.rb +3 -5
  127. data/spec/lib/oauth/client_spec.rb +0 -3
  128. data/spec/lib/oauth/code_request_spec.rb +4 -2
  129. data/spec/lib/oauth/error_response_spec.rb +0 -3
  130. data/spec/lib/oauth/error_spec.rb +0 -2
  131. data/spec/lib/oauth/forbidden_token_response_spec.rb +1 -4
  132. data/spec/lib/oauth/helpers/scope_checker_spec.rb +0 -3
  133. data/spec/lib/oauth/helpers/unique_token_spec.rb +0 -1
  134. data/spec/lib/oauth/helpers/uri_checker_spec.rb +5 -7
  135. data/spec/lib/oauth/invalid_token_response_spec.rb +1 -4
  136. data/spec/lib/oauth/password_access_token_request_spec.rb +37 -2
  137. data/spec/lib/oauth/pre_authorization_spec.rb +33 -4
  138. data/spec/lib/oauth/refresh_token_request_spec.rb +11 -7
  139. data/spec/lib/oauth/scopes_spec.rb +0 -3
  140. data/spec/lib/oauth/token_request_spec.rb +4 -5
  141. data/spec/lib/oauth/token_response_spec.rb +0 -1
  142. data/spec/lib/oauth/token_spec.rb +37 -14
  143. data/spec/lib/orm/active_record/stale_records_cleaner_spec.rb +79 -0
  144. data/spec/lib/request/strategy_spec.rb +0 -1
  145. data/spec/lib/server_spec.rb +1 -1
  146. data/spec/models/doorkeeper/access_grant_spec.rb +44 -1
  147. data/spec/models/doorkeeper/access_token_spec.rb +66 -22
  148. data/spec/models/doorkeeper/application_spec.rb +14 -47
  149. data/spec/requests/applications/applications_request_spec.rb +134 -1
  150. data/spec/requests/applications/authorized_applications_spec.rb +1 -1
  151. data/spec/requests/endpoints/authorization_spec.rb +1 -1
  152. data/spec/requests/endpoints/token_spec.rb +7 -5
  153. data/spec/requests/flows/authorization_code_errors_spec.rb +1 -1
  154. data/spec/requests/flows/authorization_code_spec.rb +197 -1
  155. data/spec/requests/flows/client_credentials_spec.rb +46 -6
  156. data/spec/requests/flows/implicit_grant_errors_spec.rb +1 -1
  157. data/spec/requests/flows/implicit_grant_spec.rb +38 -11
  158. data/spec/requests/flows/password_spec.rb +56 -2
  159. data/spec/requests/flows/refresh_token_spec.rb +2 -2
  160. data/spec/requests/flows/revoke_token_spec.rb +11 -11
  161. data/spec/requests/flows/skip_authorization_spec.rb +16 -11
  162. data/spec/requests/protected_resources/metal_spec.rb +1 -1
  163. data/spec/requests/protected_resources/private_api_spec.rb +1 -1
  164. data/spec/routing/custom_controller_routes_spec.rb +59 -7
  165. data/spec/routing/default_routes_spec.rb +2 -2
  166. data/spec/routing/scoped_routes_spec.rb +16 -2
  167. data/spec/spec_helper.rb +54 -3
  168. data/spec/spec_helper_integration.rb +2 -74
  169. data/spec/support/dependencies/{factory_girl.rb → factory_bot.rb} +0 -0
  170. data/spec/support/doorkeeper_rspec.rb +19 -0
  171. data/spec/support/helpers/authorization_request_helper.rb +4 -4
  172. data/spec/support/helpers/request_spec_helper.rb +10 -2
  173. data/spec/support/helpers/url_helper.rb +7 -3
  174. data/spec/support/http_method_shim.rb +12 -16
  175. data/spec/validators/redirect_uri_validator_spec.rb +7 -1
  176. data/spec/version/version_spec.rb +3 -3
  177. data/vendor/assets/stylesheets/doorkeeper/bootstrap.min.css +4 -5
  178. metadata +37 -33
  179. data/lib/generators/doorkeeper/add_client_confidentiality_generator.rb +0 -31
  180. data/lib/generators/doorkeeper/templates/add_confidential_to_application_migration.rb.erb +0 -11
  181. data/spec/controllers/application_metal_controller.rb +0 -10
@@ -36,10 +36,7 @@ module Doorkeeper
36
36
  end
37
37
  end
38
38
 
39
- class UnableToGenerateToken < DoorkeeperError
40
- end
41
-
42
- class TokenGeneratorNotFound < DoorkeeperError
43
- end
39
+ UnableToGenerateToken = Class.new(DoorkeeperError)
40
+ TokenGeneratorNotFound = Class.new(DoorkeeperError)
44
41
  end
45
42
  end
@@ -31,7 +31,7 @@ module Doorkeeper
31
31
  end
32
32
 
33
33
  def doorkeeper_token
34
- @_doorkeeper_token ||= OAuth::Token.authenticate(
34
+ @doorkeeper_token ||= OAuth::Token.authenticate(
35
35
  decorated_request,
36
36
  *Doorkeeper.configuration.access_token_methods
37
37
  )
@@ -30,11 +30,11 @@ module Doorkeeper
30
30
 
31
31
  # :doc:
32
32
  def doorkeeper_token
33
- @token ||= OAuth::Token.authenticate request, *config_methods
33
+ @doorkeeper_token ||= OAuth::Token.authenticate request, *config_methods
34
34
  end
35
35
 
36
36
  def config_methods
37
- @methods ||= Doorkeeper.configuration.access_token_methods
37
+ @config_methods ||= Doorkeeper.configuration.access_token_methods
38
38
  end
39
39
 
40
40
  def get_error_response_from_exception(exception)
@@ -51,6 +51,11 @@ module Doorkeeper
51
51
  def skip_authorization?
52
52
  !!instance_exec([@server.current_resource_owner, @pre_auth.client], &Doorkeeper.configuration.skip_authorization)
53
53
  end
54
+
55
+ def enforce_content_type
56
+ return if request.content_type == 'application/x-www-form-urlencoded'
57
+ render json: {}, status: :unsupported_media_type
58
+ end
54
59
  end
55
60
  end
56
61
  end
@@ -9,6 +9,15 @@ module Doorkeeper
9
9
  include Models::Orderable
10
10
  include Models::Scopes
11
11
 
12
+ # never uses pkce, if pkce migrations were not generated
13
+ def uses_pkce?
14
+ pkce_supported? && code_challenge.present?
15
+ end
16
+
17
+ def pkce_supported?
18
+ respond_to? :code_challenge
19
+ end
20
+
12
21
  module ClassMethods
13
22
  # Searches for Doorkeeper::AccessGrant record with the
14
23
  # specific token value.
@@ -21,6 +30,68 @@ module Doorkeeper
21
30
  def by_token(token)
22
31
  find_by(token: token.to_s)
23
32
  end
33
+
34
+ # Revokes AccessGrant records that have not been revoked and associated
35
+ # with the specific Application and Resource Owner.
36
+ #
37
+ # @param application_id [Integer]
38
+ # ID of the Application
39
+ # @param resource_owner [ActiveRecord::Base]
40
+ # instance of the Resource Owner model
41
+ #
42
+ def revoke_all_for(application_id, resource_owner, clock = Time)
43
+ where(application_id: application_id,
44
+ resource_owner_id: resource_owner.id,
45
+ revoked_at: nil).
46
+ update_all(revoked_at: clock.now.utc)
47
+ end
48
+
49
+ # Implements PKCE code_challenge encoding without base64 padding as described in the spec.
50
+ # https://tools.ietf.org/html/rfc7636#appendix-A
51
+ # Appendix A. Notes on Implementing Base64url Encoding without Padding
52
+ #
53
+ # This appendix describes how to implement a base64url-encoding
54
+ # function without padding, based upon the standard base64-encoding
55
+ # function that uses padding.
56
+ #
57
+ # To be concrete, example C# code implementing these functions is shown
58
+ # below. Similar code could be used in other languages.
59
+ #
60
+ # static string base64urlencode(byte [] arg)
61
+ # {
62
+ # string s = Convert.ToBase64String(arg); // Regular base64 encoder
63
+ # s = s.Split('=')[0]; // Remove any trailing '='s
64
+ # s = s.Replace('+', '-'); // 62nd char of encoding
65
+ # s = s.Replace('/', '_'); // 63rd char of encoding
66
+ # return s;
67
+ # }
68
+ #
69
+ # An example correspondence between unencoded and encoded values
70
+ # follows. The octet sequence below encodes into the string below,
71
+ # which when decoded, reproduces the octet sequence.
72
+ #
73
+ # 3 236 255 224 193
74
+ #
75
+ # A-z_4ME
76
+ #
77
+ # https://ruby-doc.org/stdlib-2.1.3/libdoc/base64/rdoc/Base64.html#method-i-urlsafe_encode64
78
+ #
79
+ # urlsafe_encode64(bin)
80
+ # Returns the Base64-encoded version of bin. This method complies with
81
+ # “Base 64 Encoding with URL and Filename Safe Alphabet” in RFC 4648.
82
+ # The alphabet uses '-' instead of '+' and '_' instead of '/'.
83
+
84
+ # @param code_verifier [#to_s] a one time use value (any object that responds to `#to_s`)
85
+ #
86
+ # @return [#to_s] An encoded code challenge based on the provided verifier suitable for PKCE validation
87
+ def generate_code_challenge(code_verifier)
88
+ padded_result = Base64.urlsafe_encode64(Digest::SHA256.digest(code_verifier))
89
+ padded_result.split('=')[0] # Remove any trailing '='
90
+ end
91
+
92
+ def pkce_supported?
93
+ new.pkce_supported?
94
+ end
24
95
  end
25
96
  end
26
97
  end
@@ -70,30 +70,34 @@ module Doorkeeper
70
70
  else
71
71
  resource_owner_or_id
72
72
  end
73
- token = last_authorized_token_for(application.try(:id), resource_owner_id)
74
- if token && scopes_match?(token.scopes, scopes, application.try(:scopes))
75
- token
73
+
74
+ tokens = authorized_tokens_for(application.try(:id), resource_owner_id)
75
+ tokens.detect do |token|
76
+ scopes_match?(token.scopes, scopes, application.try(:scopes))
76
77
  end
77
78
  end
78
79
 
79
- # Checks whether the token scopes match the scopes from the parameters or
80
- # Application scopes (if present).
80
+ # Checks whether the token scopes match the scopes from the parameters
81
81
  #
82
82
  # @param token_scopes [#to_s]
83
83
  # set of scopes (any object that responds to `#to_s`)
84
- # @param param_scopes [String]
84
+ # @param param_scopes [Doorkeeper::OAuth::Scopes]
85
85
  # scopes from params
86
- # @param app_scopes [String]
86
+ # @param app_scopes [Doorkeeper::OAuth::Scopes]
87
87
  # Application scopes
88
88
  #
89
- # @return [Boolean] true if all scopes are blank or matches
89
+ # @return [Boolean] true if the param scopes match the token scopes,
90
+ # and all the param scopes are defined in the application (or in the
91
+ # server configuration if the application doesn't define any scopes),
90
92
  # and false in other cases
91
93
  #
92
94
  def scopes_match?(token_scopes, param_scopes, app_scopes)
93
- (!token_scopes.present? && !param_scopes.present?) ||
94
- Doorkeeper::OAuth::Helpers::ScopeChecker.match?(
95
- token_scopes.to_s,
96
- param_scopes,
95
+ return true if token_scopes.empty? && param_scopes.empty?
96
+
97
+ (token_scopes.sort == param_scopes.sort) &&
98
+ Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(
99
+ param_scopes.to_s,
100
+ Doorkeeper.configuration.scopes,
97
101
  app_scopes
98
102
  )
99
103
  end
@@ -118,9 +122,8 @@ module Doorkeeper
118
122
  def find_or_create_for(application, resource_owner_id, scopes, expires_in, use_refresh_token)
119
123
  if Doorkeeper.configuration.reuse_access_token
120
124
  access_token = matching_token_for(application, resource_owner_id, scopes)
121
- if access_token && !access_token.expired?
122
- return access_token
123
- end
125
+
126
+ return access_token if access_token && !access_token.expired?
124
127
  end
125
128
 
126
129
  create!(
@@ -132,7 +135,7 @@ module Doorkeeper
132
135
  )
133
136
  end
134
137
 
135
- # Looking for not revoked Access Token record that belongs to specific
138
+ # Looking for not revoked Access Token records that belongs to specific
136
139
  # Application and Resource Owner.
137
140
  #
138
141
  # @param application_id [Integer]
@@ -140,14 +143,28 @@ module Doorkeeper
140
143
  # @param resource_owner_id [Integer]
141
144
  # ID of the Resource Owner model instance
142
145
  #
146
+ # @return [Doorkeeper::AccessToken] array of matching AccessToken objects
147
+ #
148
+ def authorized_tokens_for(application_id, resource_owner_id)
149
+ ordered_by(:created_at, :desc)
150
+ .where(application_id: application_id,
151
+ resource_owner_id: resource_owner_id,
152
+ revoked_at: nil)
153
+ end
154
+
155
+ # Convenience method for backwards-compatibility, return the last
156
+ # matching token for the given Application and Resource Owner.
157
+ #
158
+ # @param application_id [Integer]
159
+ # ID of the Application model instance
160
+ # @param resource_owner_id [Integer]
161
+ # ID of the Resource Owner model instance
162
+ #
143
163
  # @return [Doorkeeper::AccessToken, nil] matching AccessToken object or
144
164
  # nil if nothing was found
145
165
  #
146
166
  def last_authorized_token_for(application_id, resource_owner_id)
147
- ordered_by(:created_at, :desc).
148
- find_by(application_id: application_id,
149
- resource_owner_id: resource_owner_id,
150
- revoked_at: nil)
167
+ authorized_tokens_for(application_id, resource_owner_id).first
151
168
  end
152
169
  end
153
170
 
@@ -170,8 +187,8 @@ module Doorkeeper
170
187
  def as_json(_options = {})
171
188
  {
172
189
  resource_owner_id: resource_owner_id,
173
- scopes: scopes,
174
- expires_in_seconds: expires_in_seconds,
190
+ scope: scopes,
191
+ expires_in: expires_in_seconds,
175
192
  application: { uid: application.try(:uid) },
176
193
  created_at: created_at.to_i
177
194
  }
@@ -10,7 +10,7 @@ module Doorkeeper
10
10
  end
11
11
 
12
12
  def includes_scope?(*required_scopes)
13
- required_scopes.blank? || required_scopes.any? { |s| scopes.exists?(s.to_s) }
13
+ required_scopes.blank? || required_scopes.any? { |scope| scopes.exists?(scope.to_s) }
14
14
  end
15
15
  end
16
16
  end
@@ -5,18 +5,12 @@ module Doorkeeper
5
5
  attr_accessor :pre_auth, :resource_owner, :token
6
6
 
7
7
  def initialize(pre_auth, resource_owner)
8
- @pre_auth = pre_auth
8
+ @pre_auth = pre_auth
9
9
  @resource_owner = resource_owner
10
10
  end
11
11
 
12
12
  def issue_token
13
- @token ||= AccessGrant.create!(
14
- application_id: pre_auth.client.id,
15
- resource_owner_id: resource_owner.id,
16
- expires_in: configuration.authorization_code_expires_in,
17
- redirect_uri: pre_auth.redirect_uri,
18
- scopes: pre_auth.scopes.to_s
19
- )
13
+ @token ||= AccessGrant.create! access_grant_attributes
20
14
  end
21
15
 
22
16
  def native_redirect
@@ -26,6 +20,35 @@ module Doorkeeper
26
20
  def configuration
27
21
  Doorkeeper.configuration
28
22
  end
23
+
24
+ private
25
+
26
+ def authorization_code_expires_in
27
+ configuration.authorization_code_expires_in
28
+ end
29
+
30
+ def access_grant_attributes
31
+ pkce_attributes.merge application_id: pre_auth.client.id,
32
+ resource_owner_id: resource_owner.id,
33
+ expires_in: authorization_code_expires_in,
34
+ redirect_uri: pre_auth.redirect_uri,
35
+ scopes: pre_auth.scopes.to_s
36
+ end
37
+
38
+ def pkce_attributes
39
+ return {} unless pkce_supported?
40
+
41
+ {
42
+ code_challenge: pre_auth.code_challenge,
43
+ code_challenge_method: pre_auth.code_challenge_method
44
+ }
45
+ end
46
+
47
+ # ensures firstly, if migration with additional pcke columns was
48
+ # generated and migrated
49
+ def pkce_supported?
50
+ Doorkeeper::AccessGrant.pkce_supported?
51
+ end
29
52
  end
30
53
  end
31
54
  end
@@ -0,0 +1,15 @@
1
+ module Doorkeeper
2
+ module OAuth
3
+ module Authorization
4
+ class Context
5
+ attr_reader :client, :grant_type, :scopes
6
+
7
+ def initialize(client, grant_type, scopes)
8
+ @client = client
9
+ @grant_type = grant_type
10
+ @scopes = scopes
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -5,24 +5,34 @@ module Doorkeeper
5
5
  attr_accessor :pre_auth, :resource_owner, :token
6
6
 
7
7
  class << self
8
- def access_token_expires_in(server, pre_auth_or_oauth_client)
9
- if (expiration = custom_expiration(server, pre_auth_or_oauth_client))
10
- expiration
11
- else
12
- server.access_token_expires_in
13
- end
14
- end
15
-
16
- private
17
-
18
- def custom_expiration(server, pre_auth_or_oauth_client)
8
+ def build_context(pre_auth_or_oauth_client, grant_type, scopes)
19
9
  oauth_client = if pre_auth_or_oauth_client.respond_to?(:client)
20
10
  pre_auth_or_oauth_client.client
21
11
  else
22
12
  pre_auth_or_oauth_client
23
13
  end
24
14
 
25
- server.custom_access_token_expires_in.call(oauth_client)
15
+ Doorkeeper::OAuth::Authorization::Context.new(
16
+ oauth_client,
17
+ grant_type,
18
+ scopes
19
+ )
20
+ end
21
+
22
+ def access_token_expires_in(server, context)
23
+ if (expiration = server.custom_access_token_expires_in.call(context))
24
+ expiration
25
+ else
26
+ server.access_token_expires_in
27
+ end
28
+ end
29
+
30
+ def refresh_token_enabled?(server, context)
31
+ if server.refresh_token_enabled?.respond_to? :call
32
+ server.refresh_token_enabled?.call(context)
33
+ else
34
+ !!server.refresh_token_enabled?
35
+ end
26
36
  end
27
37
  end
28
38
 
@@ -32,18 +42,23 @@ module Doorkeeper
32
42
  end
33
43
 
34
44
  def issue_token
45
+ context = self.class.build_context(
46
+ pre_auth.client,
47
+ Doorkeeper::OAuth::IMPLICIT,
48
+ pre_auth.scopes
49
+ )
35
50
  @token ||= AccessToken.find_or_create_for(
36
51
  pre_auth.client,
37
52
  resource_owner.id,
38
53
  pre_auth.scopes,
39
- self.class.access_token_expires_in(configuration, pre_auth),
54
+ self.class.access_token_expires_in(configuration, context),
40
55
  false
41
56
  )
42
57
  end
43
58
 
44
59
  def native_redirect
45
60
  {
46
- controller: 'doorkeeper/token_info',
61
+ controller: controller,
47
62
  action: :show,
48
63
  access_token: token.token
49
64
  }
@@ -54,6 +69,13 @@ module Doorkeeper
54
69
  def configuration
55
70
  Doorkeeper.configuration
56
71
  end
72
+
73
+ def controller
74
+ @controller ||= begin
75
+ mapping = Doorkeeper::Rails::Routes.mapping[:token_info] || {}
76
+ mapping[:controllers] || 'doorkeeper/token_info'
77
+ end
78
+ end
57
79
  end
58
80
  end
59
81
  end
@@ -6,18 +6,26 @@ module Doorkeeper
6
6
  validate :grant, error: :invalid_grant
7
7
  # @see https://tools.ietf.org/html/rfc6749#section-5.2
8
8
  validate :redirect_uri, error: :invalid_grant
9
+ validate :code_verifier, error: :invalid_grant
9
10
 
10
- attr_accessor :server, :grant, :client, :redirect_uri, :access_token
11
+ attr_accessor :server, :grant, :client, :redirect_uri, :access_token,
12
+ :code_verifier
11
13
 
12
14
  def initialize(server, grant, client, parameters = {})
13
15
  @server = server
14
16
  @client = client
15
17
  @grant = grant
18
+ @grant_type = Doorkeeper::OAuth::AUTHORIZATION_CODE
16
19
  @redirect_uri = parameters[:redirect_uri]
20
+ @code_verifier = parameters[:code_verifier]
17
21
  end
18
22
 
19
23
  private
20
24
 
25
+ def client_by_uid(parameters)
26
+ Doorkeeper::Application.by_uid(parameters[:client_id])
27
+ end
28
+
21
29
  def before_successful_response
22
30
  grant.transaction do
23
31
  grant.lock!
@@ -33,11 +41,13 @@ module Doorkeeper
33
41
  end
34
42
 
35
43
  def validate_attributes
44
+ return false if grant && grant.uses_pkce? && code_verifier.blank?
45
+ return false if grant && !grant.pkce_supported? && !code_verifier.blank?
36
46
  redirect_uri.present?
37
47
  end
38
48
 
39
49
  def validate_client
40
- !!client
50
+ !client.nil?
41
51
  end
42
52
 
43
53
  def validate_grant
@@ -51,6 +61,21 @@ module Doorkeeper
51
61
  grant.redirect_uri
52
62
  )
53
63
  end
64
+
65
+ # if either side (server or client) request pkce, check the verifier
66
+ # against the DB - if pkce is supported
67
+ def validate_code_verifier
68
+ return true unless grant.uses_pkce? || code_verifier
69
+ return false unless grant.pkce_supported?
70
+
71
+ if grant.code_challenge_method == 'S256'
72
+ grant.code_challenge == AccessGrant.generate_code_challenge(code_verifier)
73
+ elsif grant.code_challenge_method == 'plain'
74
+ grant.code_challenge == code_verifier
75
+ else
76
+ false
77
+ end
78
+ end
54
79
  end
55
80
  end
56
81
  end
@@ -3,6 +3,8 @@ module Doorkeeper
3
3
  class BaseRequest
4
4
  include Validations
5
5
 
6
+ attr_reader :grant_type
7
+
6
8
  def authorize
7
9
  validate
8
10
 
@@ -17,11 +19,7 @@ module Doorkeeper
17
19
  end
18
20
 
19
21
  def scopes
20
- @scopes ||= if @original_scopes.present?
21
- OAuth::Scopes.from_string(@original_scopes)
22
- else
23
- default_scopes
24
- end
22
+ @scopes ||= build_scopes
25
23
  end
26
24
 
27
25
  def default_scopes
@@ -33,12 +31,13 @@ module Doorkeeper
33
31
  end
34
32
 
35
33
  def find_or_create_access_token(client, resource_owner_id, scopes, server)
34
+ context = Authorization::Token.build_context(client, grant_type, scopes)
36
35
  @access_token = AccessToken.find_or_create_for(
37
36
  client,
38
37
  resource_owner_id,
39
38
  scopes,
40
- Authorization::Token.access_token_expires_in(server, client),
41
- server.refresh_token_enabled?
39
+ Authorization::Token.access_token_expires_in(server, context),
40
+ Authorization::Token.refresh_token_enabled?(server, context)
42
41
  )
43
42
  end
44
43
 
@@ -47,8 +46,20 @@ module Doorkeeper
47
46
  end
48
47
 
49
48
  def after_successful_response
50
- Doorkeeper.configuration.after_successful_strategy_response.
51
- call(self, @response)
49
+ Doorkeeper.configuration.after_successful_strategy_response.call(self, @response)
50
+ end
51
+
52
+ private
53
+
54
+ def build_scopes
55
+ if @original_scopes.present?
56
+ OAuth::Scopes.from_string(@original_scopes)
57
+ else
58
+ client_scopes = @client.try(:scopes)
59
+ return default_scopes if client_scopes.blank?
60
+
61
+ default_scopes & @client.scopes
62
+ end
52
63
  end
53
64
  end
54
65
  end
@@ -4,7 +4,7 @@ module Doorkeeper
4
4
  Credentials = Struct.new(:uid, :secret) do
5
5
  class << self
6
6
  def from_request(request, *credentials_methods)
7
- credentials_methods.inject(nil) do |credentials, method|
7
+ credentials_methods.inject(nil) do |_, method|
8
8
  method = self.method(method) if method.is_a?(Symbol)
9
9
  credentials = Credentials.new(*method.call(request))
10
10
  break credentials unless credentials.blank?
@@ -1,5 +1,3 @@
1
- require 'doorkeeper/oauth/client/credentials'
2
-
3
1
  module Doorkeeper
4
2
  module OAuth
5
3
  class Client
@@ -5,7 +5,8 @@ module Doorkeeper
5
5
  def call(client, scopes, attributes = {})
6
6
  AccessToken.find_or_create_for(
7
7
  client, nil, scopes, attributes[:expires_in],
8
- attributes[:use_refresh_token])
8
+ attributes[:use_refresh_token]
9
+ )
9
10
  end
10
11
  end
11
12
  end
@@ -1,5 +1,3 @@
1
- require 'doorkeeper/oauth/client_credentials/validation'
2
-
3
1
  module Doorkeeper
4
2
  module OAuth
5
3
  class ClientCredentialsRequest < BaseRequest
@@ -25,7 +23,12 @@ module Doorkeeper
25
23
  private
26
24
 
27
25
  def create_token(client, scopes, creator)
28
- ttl = Authorization::Token.access_token_expires_in(@server, client)
26
+ context = Authorization::Token.build_context(
27
+ client,
28
+ Doorkeeper::OAuth::CLIENT_CREDENTIALS,
29
+ scopes
30
+ )
31
+ ttl = Authorization::Token.access_token_expires_in(@server, context)
29
32
 
30
33
  creator.call(
31
34
  client,
@@ -1,7 +1,3 @@
1
- require 'doorkeeper/validations'
2
- require 'doorkeeper/oauth/scopes'
3
- require 'doorkeeper/oauth/helpers/scope_checker'
4
-
5
1
  module Doorkeeper
6
2
  module OAuth
7
3
  class ClientCredentialsRequest < BaseRequest
@@ -13,7 +9,9 @@ module Doorkeeper
13
9
  validate :scopes, error: :invalid_scope
14
10
 
15
11
  def initialize(server, request)
16
- @server, @request, @client = server, request, request.client
12
+ @server = server
13
+ @request = request
14
+ @client = request.client
17
15
 
18
16
  validate
19
17
  end
@@ -25,7 +23,7 @@ module Doorkeeper
25
23
  end
26
24
 
27
25
  def validate_scopes
28
- return true unless @request.scopes.present?
26
+ return true if @request.scopes.blank?
29
27
 
30
28
  application_scopes = if @client.present?
31
29
  @client.application.scopes
@@ -1,7 +1,3 @@
1
- require 'doorkeeper/oauth/client_credentials/creator'
2
- require 'doorkeeper/oauth/client_credentials/issuer'
3
- require 'doorkeeper/oauth/client_credentials/validation'
4
-
5
1
  module Doorkeeper
6
2
  module OAuth
7
3
  class ClientCredentialsRequest < BaseRequest
@@ -4,7 +4,13 @@ module Doorkeeper
4
4
  include OAuth::Helpers
5
5
 
6
6
  def self.from_request(request, attributes = {})
7
- new(attributes.merge(name: request.error, state: request.try(:state)))
7
+ new(
8
+ attributes.merge(
9
+ name: request.error,
10
+ state: request.try(:state),
11
+ redirect_uri: request.try(:redirect_uri)
12
+ )
13
+ )
8
14
  end
9
15
 
10
16
  delegate :name, :description, :state, to: :@error
@@ -41,10 +47,12 @@ module Doorkeeper
41
47
  end
42
48
 
43
49
  def headers
44
- { 'Cache-Control' => 'no-store',
50
+ {
51
+ 'Cache-Control' => 'no-store',
45
52
  'Pragma' => 'no-cache',
46
53
  'Content-Type' => 'application/json; charset=utf-8',
47
- 'WWW-Authenticate' => authenticate_info }
54
+ 'WWW-Authenticate' => authenticate_info
55
+ }
48
56
  end
49
57
 
50
58
  protected
@@ -17,10 +17,6 @@ module Doorkeeper
17
17
  @valid_scopes.has_scopes?(parsed_scopes)
18
18
  end
19
19
 
20
- def match?
21
- valid? && parsed_scopes.has_scopes?(@valid_scopes)
22
- end
23
-
24
20
  private
25
21
 
26
22
  def valid_scopes(server_scopes, application_scopes)
@@ -35,10 +31,6 @@ module Doorkeeper
35
31
  def self.valid?(scope_str, server_scopes, application_scopes = nil)
36
32
  Validator.new(scope_str, server_scopes, application_scopes).valid?
37
33
  end
38
-
39
- def self.match?(scope_str, server_scopes, application_scopes = nil)
40
- Validator.new(scope_str, server_scopes, application_scopes).match?
41
- end
42
34
  end
43
35
  end
44
36
  end