doorkeeper 4.2.6 → 5.0.0.rc1

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.
Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +25 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +17 -0
  4. data/.gitignore +2 -1
  5. data/.hound.yml +2 -13
  6. data/.rubocop.yml +17 -0
  7. data/.travis.yml +19 -5
  8. data/Appraisals +8 -4
  9. data/CODE_OF_CONDUCT.md +46 -0
  10. data/Gemfile +1 -1
  11. data/NEWS.md +77 -0
  12. data/README.md +169 -34
  13. data/Rakefile +6 -0
  14. data/SECURITY.md +15 -0
  15. data/app/assets/stylesheets/doorkeeper/admin/application.css +2 -2
  16. data/app/controllers/doorkeeper/application_controller.rb +2 -5
  17. data/app/controllers/doorkeeper/application_metal_controller.rb +4 -0
  18. data/app/controllers/doorkeeper/applications_controller.rb +47 -13
  19. data/app/controllers/doorkeeper/authorizations_controller.rb +55 -12
  20. data/app/controllers/doorkeeper/authorized_applications_controller.rb +15 -1
  21. data/app/controllers/doorkeeper/tokens_controller.rb +15 -7
  22. data/app/helpers/doorkeeper/dashboard_helper.rb +8 -6
  23. data/app/validators/redirect_uri_validator.rb +13 -2
  24. data/app/views/doorkeeper/applications/_delete_form.html.erb +3 -1
  25. data/app/views/doorkeeper/applications/_form.html.erb +31 -19
  26. data/app/views/doorkeeper/applications/edit.html.erb +1 -1
  27. data/app/views/doorkeeper/applications/index.html.erb +18 -6
  28. data/app/views/doorkeeper/applications/new.html.erb +1 -1
  29. data/app/views/doorkeeper/applications/show.html.erb +8 -5
  30. data/app/views/doorkeeper/authorizations/error.html.erb +1 -1
  31. data/app/views/doorkeeper/authorizations/new.html.erb +4 -0
  32. data/app/views/doorkeeper/authorized_applications/index.html.erb +0 -1
  33. data/app/views/layouts/doorkeeper/admin.html.erb +15 -15
  34. data/config/locales/en.yml +18 -6
  35. data/doorkeeper.gemspec +6 -5
  36. data/gemfiles/rails_4_2.gemfile +6 -4
  37. data/gemfiles/rails_5_0.gemfile +4 -4
  38. data/gemfiles/rails_5_1.gemfile +6 -7
  39. data/gemfiles/rails_5_2.gemfile +12 -0
  40. data/gemfiles/rails_master.gemfile +14 -0
  41. data/lib/doorkeeper/config.rb +107 -68
  42. data/lib/doorkeeper/engine.rb +7 -3
  43. data/lib/doorkeeper/errors.rb +2 -5
  44. data/lib/doorkeeper/grape/helpers.rb +14 -9
  45. data/lib/doorkeeper/helpers/controller.rb +15 -6
  46. data/lib/doorkeeper/models/access_grant_mixin.rb +52 -23
  47. data/lib/doorkeeper/models/access_token_mixin.rb +51 -52
  48. data/lib/doorkeeper/models/application_mixin.rb +16 -30
  49. data/lib/doorkeeper/models/concerns/expirable.rb +7 -5
  50. data/lib/doorkeeper/models/concerns/orderable.rb +13 -0
  51. data/lib/doorkeeper/models/concerns/scopes.rb +1 -1
  52. data/lib/doorkeeper/oauth/authorization/code.rb +31 -8
  53. data/lib/doorkeeper/oauth/authorization/context.rb +15 -0
  54. data/lib/doorkeeper/oauth/authorization/token.rb +41 -20
  55. data/lib/doorkeeper/oauth/authorization_code_request.rb +33 -3
  56. data/lib/doorkeeper/oauth/base_request.rb +22 -7
  57. data/lib/doorkeeper/oauth/client/credentials.rb +6 -4
  58. data/lib/doorkeeper/oauth/client.rb +2 -2
  59. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +6 -1
  60. data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -2
  61. data/lib/doorkeeper/oauth/error.rb +2 -2
  62. data/lib/doorkeeper/oauth/error_response.rb +11 -4
  63. data/lib/doorkeeper/oauth/forbidden_token_response.rb +1 -1
  64. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +0 -8
  65. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +15 -0
  66. data/lib/doorkeeper/oauth/invalid_token_response.rb +3 -4
  67. data/lib/doorkeeper/oauth/password_access_token_request.rb +8 -4
  68. data/lib/doorkeeper/oauth/pre_authorization.rb +45 -13
  69. data/lib/doorkeeper/oauth/refresh_token_request.rb +7 -1
  70. data/lib/doorkeeper/oauth/scopes.rb +19 -9
  71. data/lib/doorkeeper/oauth/token.rb +6 -3
  72. data/lib/doorkeeper/oauth/token_introspection.rb +128 -0
  73. data/lib/doorkeeper/oauth/token_response.rb +4 -2
  74. data/lib/doorkeeper/oauth.rb +13 -0
  75. data/lib/doorkeeper/orm/active_record/access_grant.rb +27 -0
  76. data/lib/doorkeeper/orm/active_record/access_token.rb +21 -20
  77. data/lib/doorkeeper/orm/active_record/application.rb +34 -0
  78. data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +26 -0
  79. data/lib/doorkeeper/orm/active_record.rb +21 -8
  80. data/lib/doorkeeper/rails/helpers.rb +7 -10
  81. data/lib/doorkeeper/rails/routes.rb +21 -7
  82. data/lib/doorkeeper/rake/db.rake +40 -0
  83. data/lib/doorkeeper/rake/setup.rake +6 -0
  84. data/lib/doorkeeper/rake.rb +14 -0
  85. data/lib/doorkeeper/request/password.rb +1 -11
  86. data/lib/doorkeeper/request.rb +29 -23
  87. data/lib/doorkeeper/validations.rb +3 -2
  88. data/lib/doorkeeper/version.rb +14 -1
  89. data/lib/doorkeeper.rb +6 -17
  90. data/lib/generators/doorkeeper/application_owner_generator.rb +26 -12
  91. data/lib/generators/doorkeeper/confidential_applications_generator.rb +32 -0
  92. data/lib/generators/doorkeeper/install_generator.rb +17 -9
  93. data/lib/generators/doorkeeper/migration_generator.rb +26 -9
  94. data/lib/generators/doorkeeper/pkce_generator.rb +32 -0
  95. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +31 -20
  96. data/lib/generators/doorkeeper/templates/add_confidential_to_applications.rb.erb +13 -0
  97. data/lib/generators/doorkeeper/templates/{add_owner_to_application_migration.rb → add_owner_to_application_migration.rb.erb} +1 -1
  98. data/lib/generators/doorkeeper/templates/{add_previous_refresh_token_to_access_tokens.rb → add_previous_refresh_token_to_access_tokens.rb.erb} +1 -1
  99. data/lib/generators/doorkeeper/templates/enable_pkce_migration.rb.erb +6 -0
  100. data/lib/generators/doorkeeper/templates/initializer.rb +88 -10
  101. data/lib/generators/doorkeeper/templates/{migration.rb → migration.rb.erb} +2 -1
  102. data/lib/generators/doorkeeper/views_generator.rb +3 -1
  103. data/spec/controllers/application_metal_controller_spec.rb +50 -0
  104. data/spec/controllers/applications_controller_spec.rb +141 -17
  105. data/spec/controllers/authorizations_controller_spec.rb +255 -20
  106. data/spec/controllers/protected_resources_controller_spec.rb +44 -35
  107. data/spec/controllers/token_info_controller_spec.rb +17 -21
  108. data/spec/controllers/tokens_controller_spec.rb +142 -10
  109. data/spec/dummy/app/assets/config/manifest.js +2 -0
  110. data/spec/dummy/config/environments/test.rb +4 -5
  111. data/spec/dummy/config/initializers/doorkeeper.rb +18 -1
  112. data/spec/dummy/config/initializers/{active_record_belongs_to_required_by_default.rb → new_framework_defaults.rb} +5 -1
  113. data/spec/dummy/config/initializers/secret_token.rb +0 -1
  114. data/spec/dummy/config/routes.rb +3 -42
  115. data/spec/dummy/db/migrate/20111122132257_create_users.rb +3 -1
  116. data/spec/dummy/db/migrate/20120312140401_add_password_to_users.rb +3 -1
  117. data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +3 -1
  118. data/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb +3 -1
  119. data/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb +3 -1
  120. data/spec/dummy/db/migrate/20170822064514_enable_pkce.rb +6 -0
  121. data/spec/dummy/db/migrate/20180210183654_add_confidential_to_applications.rb +13 -0
  122. data/spec/dummy/db/schema.rb +38 -37
  123. data/spec/factories.rb +1 -1
  124. data/spec/generators/application_owner_generator_spec.rb +25 -6
  125. data/spec/generators/confidential_applications_generator_spec.rb +45 -0
  126. data/spec/generators/install_generator_spec.rb +1 -1
  127. data/spec/generators/migration_generator_spec.rb +25 -4
  128. data/spec/generators/pkce_generator_spec.rb +43 -0
  129. data/spec/generators/previous_refresh_token_generator_spec.rb +57 -0
  130. data/spec/generators/views_generator_spec.rb +1 -1
  131. data/spec/grape/grape_integration_spec.rb +135 -0
  132. data/spec/helpers/doorkeeper/dashboard_helper_spec.rb +2 -2
  133. data/spec/lib/config_spec.rb +170 -22
  134. data/spec/lib/doorkeeper_spec.rb +1 -126
  135. data/spec/lib/models/expirable_spec.rb +0 -3
  136. data/spec/lib/models/revocable_spec.rb +2 -4
  137. data/spec/lib/models/scopes_spec.rb +0 -4
  138. data/spec/lib/oauth/authorization/uri_builder_spec.rb +0 -4
  139. data/spec/lib/oauth/authorization_code_request_spec.rb +63 -13
  140. data/spec/lib/oauth/base_request_spec.rb +19 -10
  141. data/spec/lib/oauth/base_response_spec.rb +1 -1
  142. data/spec/lib/oauth/client/credentials_spec.rb +5 -5
  143. data/spec/lib/oauth/client_credentials/creator_spec.rb +6 -2
  144. data/spec/lib/oauth/client_credentials/issuer_spec.rb +26 -7
  145. data/spec/lib/oauth/client_credentials/validation_spec.rb +2 -3
  146. data/spec/lib/oauth/client_credentials_integration_spec.rb +2 -2
  147. data/spec/lib/oauth/client_credentials_request_spec.rb +4 -5
  148. data/spec/lib/oauth/client_spec.rb +0 -3
  149. data/spec/lib/oauth/code_request_spec.rb +5 -5
  150. data/spec/lib/oauth/error_response_spec.rb +0 -3
  151. data/spec/lib/oauth/error_spec.rb +1 -3
  152. data/spec/lib/oauth/forbidden_token_response_spec.rb +1 -4
  153. data/spec/lib/oauth/helpers/scope_checker_spec.rb +0 -3
  154. data/spec/lib/oauth/helpers/unique_token_spec.rb +0 -1
  155. data/spec/lib/oauth/helpers/uri_checker_spec.rb +115 -3
  156. data/spec/lib/oauth/invalid_token_response_spec.rb +2 -5
  157. data/spec/lib/oauth/password_access_token_request_spec.rb +46 -5
  158. data/spec/lib/oauth/pre_authorization_spec.rb +40 -6
  159. data/spec/lib/oauth/refresh_token_request_spec.rb +30 -14
  160. data/spec/lib/oauth/scopes_spec.rb +28 -4
  161. data/spec/lib/oauth/token_request_spec.rb +10 -13
  162. data/spec/lib/oauth/token_response_spec.rb +0 -1
  163. data/spec/lib/oauth/token_spec.rb +37 -14
  164. data/spec/lib/orm/active_record/stale_records_cleaner_spec.rb +79 -0
  165. data/spec/lib/request/strategy_spec.rb +0 -1
  166. data/spec/lib/server_spec.rb +10 -0
  167. data/spec/models/doorkeeper/access_grant_spec.rb +2 -2
  168. data/spec/models/doorkeeper/access_token_spec.rb +118 -60
  169. data/spec/models/doorkeeper/application_spec.rb +101 -23
  170. data/spec/requests/applications/applications_request_spec.rb +94 -6
  171. data/spec/requests/applications/authorized_applications_spec.rb +1 -1
  172. data/spec/requests/endpoints/authorization_spec.rb +1 -1
  173. data/spec/requests/endpoints/token_spec.rb +15 -6
  174. data/spec/requests/flows/authorization_code_errors_spec.rb +1 -1
  175. data/spec/requests/flows/authorization_code_spec.rb +198 -1
  176. data/spec/requests/flows/client_credentials_spec.rb +73 -5
  177. data/spec/requests/flows/implicit_grant_errors_spec.rb +3 -3
  178. data/spec/requests/flows/implicit_grant_spec.rb +38 -11
  179. data/spec/requests/flows/password_spec.rb +160 -24
  180. data/spec/requests/flows/refresh_token_spec.rb +6 -6
  181. data/spec/requests/flows/revoke_token_spec.rb +26 -26
  182. data/spec/requests/flows/skip_authorization_spec.rb +16 -11
  183. data/spec/requests/protected_resources/metal_spec.rb +2 -2
  184. data/spec/requests/protected_resources/private_api_spec.rb +2 -2
  185. data/spec/routing/custom_controller_routes_spec.rb +63 -7
  186. data/spec/routing/default_routes_spec.rb +6 -2
  187. data/spec/routing/scoped_routes_spec.rb +16 -2
  188. data/spec/spec_helper.rb +54 -3
  189. data/spec/spec_helper_integration.rb +2 -63
  190. data/spec/support/dependencies/factory_bot.rb +2 -0
  191. data/spec/support/doorkeeper_rspec.rb +19 -0
  192. data/spec/support/helpers/access_token_request_helper.rb +1 -1
  193. data/spec/support/helpers/authorization_request_helper.rb +4 -4
  194. data/spec/support/helpers/model_helper.rb +9 -4
  195. data/spec/support/helpers/request_spec_helper.rb +10 -6
  196. data/spec/support/helpers/url_helper.rb +15 -10
  197. data/spec/support/http_method_shim.rb +12 -16
  198. data/spec/support/shared/controllers_shared_context.rb +2 -6
  199. data/spec/support/shared/models_shared_examples.rb +4 -4
  200. data/spec/validators/redirect_uri_validator_spec.rb +58 -7
  201. data/spec/version/version_spec.rb +15 -0
  202. data/vendor/assets/stylesheets/doorkeeper/bootstrap.min.css +4 -5
  203. metadata +73 -19
  204. data/spec/controllers/application_metal_controller.rb +0 -10
  205. data/spec/support/dependencies/factory_girl.rb +0 -2
@@ -6,32 +6,8 @@ module Doorkeeper
6
6
  include Models::Expirable
7
7
  include Models::Revocable
8
8
  include Models::Accessible
9
+ include Models::Orderable
9
10
  include Models::Scopes
10
- include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
11
-
12
- included do
13
- belongs_to_options = {
14
- class_name: 'Doorkeeper::Application',
15
- inverse_of: :access_tokens
16
- }
17
- if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
18
- belongs_to_options[:optional] = true
19
- end
20
-
21
- belongs_to :application, belongs_to_options
22
-
23
- validates :token, presence: true, uniqueness: true
24
- validates :refresh_token, uniqueness: true, if: :use_refresh_token?
25
-
26
- # @attr_writer [Boolean, nil] use_refresh_token
27
- # indicates the possibility of using refresh token
28
- attr_writer :use_refresh_token
29
-
30
- before_validation :generate_token, on: :create
31
- before_validation :generate_refresh_token,
32
- on: :create,
33
- if: :use_refresh_token?
34
- end
35
11
 
36
12
  module ClassMethods
37
13
  # Returns an instance of the Doorkeeper::AccessToken with
@@ -68,11 +44,11 @@ module Doorkeeper
68
44
  # @param resource_owner [ActiveRecord::Base]
69
45
  # instance of the Resource Owner model
70
46
  #
71
- def revoke_all_for(application_id, resource_owner)
47
+ def revoke_all_for(application_id, resource_owner, clock = Time)
72
48
  where(application_id: application_id,
73
49
  resource_owner_id: resource_owner.id,
74
50
  revoked_at: nil).
75
- each(&:revoke)
51
+ update_all(revoked_at: clock.now.utc)
76
52
  end
77
53
 
78
54
  # Looking for not expired Access Token with a matching set of scopes
@@ -94,30 +70,34 @@ module Doorkeeper
94
70
  else
95
71
  resource_owner_or_id
96
72
  end
97
- token = last_authorized_token_for(application.try(:id), resource_owner_id)
98
- if token && scopes_match?(token.scopes, scopes, application.try(:scopes))
99
- 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))
100
77
  end
101
78
  end
102
79
 
103
- # Checks whether the token scopes match the scopes from the parameters or
104
- # Application scopes (if present).
80
+ # Checks whether the token scopes match the scopes from the parameters
105
81
  #
106
82
  # @param token_scopes [#to_s]
107
83
  # set of scopes (any object that responds to `#to_s`)
108
- # @param param_scopes [String]
84
+ # @param param_scopes [Doorkeeper::OAuth::Scopes]
109
85
  # scopes from params
110
- # @param app_scopes [String]
86
+ # @param app_scopes [Doorkeeper::OAuth::Scopes]
111
87
  # Application scopes
112
88
  #
113
- # @return [Boolean] true if all scopes and 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),
114
92
  # and false in other cases
115
93
  #
116
94
  def scopes_match?(token_scopes, param_scopes, app_scopes)
117
- (!token_scopes.present? && !param_scopes.present?) ||
118
- Doorkeeper::OAuth::Helpers::ScopeChecker.match?(
119
- token_scopes.to_s,
120
- 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,
121
101
  app_scopes
122
102
  )
123
103
  end
@@ -142,9 +122,8 @@ module Doorkeeper
142
122
  def find_or_create_for(application, resource_owner_id, scopes, expires_in, use_refresh_token)
143
123
  if Doorkeeper.configuration.reuse_access_token
144
124
  access_token = matching_token_for(application, resource_owner_id, scopes)
145
- if access_token && !access_token.expired?
146
- return access_token
147
- end
125
+
126
+ return access_token if access_token && !access_token.expired?
148
127
  end
149
128
 
150
129
  create!(
@@ -156,7 +135,7 @@ module Doorkeeper
156
135
  )
157
136
  end
158
137
 
159
- # Looking for not revoked Access Token record that belongs to specific
138
+ # Looking for not revoked Access Token records that belongs to specific
160
139
  # Application and Resource Owner.
161
140
  #
162
141
  # @param application_id [Integer]
@@ -164,14 +143,28 @@ module Doorkeeper
164
143
  # @param resource_owner_id [Integer]
165
144
  # ID of the Resource Owner model instance
166
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
+ #
167
163
  # @return [Doorkeeper::AccessToken, nil] matching AccessToken object or
168
164
  # nil if nothing was found
169
165
  #
170
166
  def last_authorized_token_for(application_id, resource_owner_id)
171
- send(order_method, created_at_desc).
172
- find_by(application_id: application_id,
173
- resource_owner_id: resource_owner_id,
174
- revoked_at: nil)
167
+ authorized_tokens_for(application_id, resource_owner_id).first
175
168
  end
176
169
  end
177
170
 
@@ -231,7 +224,7 @@ module Doorkeeper
231
224
  # @return [String] refresh token value
232
225
  #
233
226
  def generate_refresh_token
234
- write_attribute :refresh_token, UniqueToken.generate
227
+ self.refresh_token = UniqueToken.generate
235
228
  end
236
229
 
237
230
  # Generates and sets the token value with the
@@ -247,18 +240,24 @@ module Doorkeeper
247
240
  def generate_token
248
241
  self.created_at ||= Time.now.utc
249
242
 
250
- generator = Doorkeeper.configuration.access_token_generator.constantize
251
- self.token = generator.generate(
243
+ self.token = token_generator.generate(
252
244
  resource_owner_id: resource_owner_id,
253
245
  scopes: scopes,
254
246
  application: application,
255
247
  expires_in: expires_in,
256
248
  created_at: created_at
257
249
  )
258
- rescue NoMethodError
250
+ end
251
+
252
+ def token_generator
253
+ generator_name = Doorkeeper.configuration.access_token_generator
254
+ generator = generator_name.constantize
255
+
256
+ return generator if generator.respond_to?(:generate)
257
+
259
258
  raise Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
260
259
  rescue NameError
261
- raise Errors::TokenGeneratorNotFound, "#{generator} not found"
260
+ raise Errors::TokenGeneratorNotFound, "#{generator_name} not found"
262
261
  end
263
262
  end
264
263
  end
@@ -3,24 +3,16 @@ module Doorkeeper
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  include OAuth::Helpers
6
+ include Models::Orderable
6
7
  include Models::Scopes
7
- include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
8
-
9
- included do
10
- has_many :access_grants, dependent: :delete_all, class_name: 'Doorkeeper::AccessGrant'
11
- has_many :access_tokens, dependent: :delete_all, class_name: 'Doorkeeper::AccessToken'
12
-
13
- validates :name, :secret, :uid, presence: true
14
- validates :uid, uniqueness: true
15
- validates :redirect_uri, redirect_uri: true
16
-
17
- before_validation :generate_uid, :generate_secret, on: :create
18
- end
19
8
 
20
9
  module ClassMethods
21
10
  # Returns an instance of the Doorkeeper::Application with
22
11
  # specific UID and secret.
23
12
  #
13
+ # Public/Non-confidential applications will only find by uid if secret is
14
+ # blank.
15
+ #
24
16
  # @param uid [#to_s] UID (any object that responds to `#to_s`)
25
17
  # @param secret [#to_s] secret (any object that responds to `#to_s`)
26
18
  #
@@ -28,7 +20,11 @@ module Doorkeeper
28
20
  # if there is no record with such credentials
29
21
  #
30
22
  def by_uid_and_secret(uid, secret)
31
- find_by(uid: uid.to_s, secret: secret.to_s)
23
+ app = by_uid(uid)
24
+ return unless app
25
+ return app if secret.blank? && !app.confidential?
26
+ return unless app.secret == secret
27
+ app
32
28
  end
33
29
 
34
30
  # Returns an instance of the Doorkeeper::Application with specific UID.
@@ -43,23 +39,13 @@ module Doorkeeper
43
39
  end
44
40
  end
45
41
 
46
- private
47
-
48
- def has_scopes?
49
- Doorkeeper.configuration.orm != :active_record ||
50
- Doorkeeper::Application.column_names.include?("scopes")
51
- end
52
-
53
- def generate_uid
54
- if uid.blank?
55
- self.uid = UniqueToken.generate
56
- end
57
- end
58
-
59
- def generate_secret
60
- if secret.blank?
61
- self.secret = UniqueToken.generate
62
- end
42
+ # Set an application's valid redirect URIs.
43
+ #
44
+ # @param uris [String, Array] Newline-separated string or array the URI(s)
45
+ #
46
+ # @return [String] The redirect URI(s) seperated by newlines.
47
+ def redirect_uri=(uris)
48
+ super(uris.is_a?(Array) ? uris.join("\n") : uris)
63
49
  end
64
50
  end
65
51
  end
@@ -6,7 +6,7 @@ module Doorkeeper
6
6
  #
7
7
  # @return [Boolean] true if object expired and false in other case
8
8
  def expired?
9
- expires_in && Time.now.utc > expired_time
9
+ expires_in && Time.now.utc > expires_at
10
10
  end
11
11
 
12
12
  # Calculates expiration time in seconds.
@@ -15,14 +15,16 @@ module Doorkeeper
15
15
  # or nil if object never expires.
16
16
  def expires_in_seconds
17
17
  return nil if expires_in.nil?
18
- expires = (created_at + expires_in.seconds) - Time.now.utc
18
+ expires = expires_at - Time.now.utc
19
19
  expires_sec = expires.seconds.round(0)
20
20
  expires_sec > 0 ? expires_sec : 0
21
21
  end
22
22
 
23
- private
24
-
25
- def expired_time
23
+ # Expiration time (date time of creation + TTL).
24
+ #
25
+ # @return [Time] expiration time in UTC
26
+ #
27
+ def expires_at
26
28
  created_at + expires_in.seconds
27
29
  end
28
30
  end
@@ -0,0 +1,13 @@
1
+ module Doorkeeper
2
+ module Models
3
+ module Orderable
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def ordered_by(attribute, direction = :asc)
8
+ order(attribute => direction)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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
@@ -4,32 +4,56 @@ module Doorkeeper
4
4
  class Token
5
5
  attr_accessor :pre_auth, :resource_owner, :token
6
6
 
7
+ class << self
8
+ def access_token_expires_in(server, pre_auth_or_oauth_client, grant_type, scopes)
9
+ if (expiration = custom_expiration(server, pre_auth_or_oauth_client, grant_type, scopes))
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, grant_type, scopes)
19
+ oauth_client = if pre_auth_or_oauth_client.respond_to?(:client)
20
+ pre_auth_or_oauth_client.client
21
+ else
22
+ pre_auth_or_oauth_client
23
+ end
24
+ context = Doorkeeper::OAuth::Authorization::Context.new(
25
+ oauth_client,
26
+ grant_type,
27
+ scopes
28
+ )
29
+
30
+ server.custom_access_token_expires_in.call(context)
31
+ end
32
+ end
33
+
7
34
  def initialize(pre_auth, resource_owner)
8
35
  @pre_auth = pre_auth
9
36
  @resource_owner = resource_owner
10
37
  end
11
38
 
12
- def self.access_token_expires_in(server, pre_auth_or_oauth_client)
13
- if expiration = custom_expiration(server, pre_auth_or_oauth_client)
14
- expiration
15
- else
16
- server.access_token_expires_in
17
- end
18
- end
19
-
20
39
  def issue_token
21
40
  @token ||= AccessToken.find_or_create_for(
22
41
  pre_auth.client,
23
42
  resource_owner.id,
24
43
  pre_auth.scopes,
25
- self.class.access_token_expires_in(configuration, pre_auth),
44
+ self.class.access_token_expires_in(
45
+ configuration,
46
+ pre_auth,
47
+ Doorkeeper::OAuth::IMPLICIT,
48
+ pre_auth.scopes
49
+ ),
26
50
  false
27
51
  )
28
52
  end
29
53
 
30
54
  def native_redirect
31
55
  {
32
- controller: 'doorkeeper/token_info',
56
+ controller: controller,
33
57
  action: :show,
34
58
  access_token: token.token
35
59
  }
@@ -37,19 +61,16 @@ module Doorkeeper
37
61
 
38
62
  private
39
63
 
40
- def self.custom_expiration(server, pre_auth_or_oauth_client)
41
- oauth_client = if pre_auth_or_oauth_client.respond_to?(:client)
42
- pre_auth_or_oauth_client.client
43
- else
44
- pre_auth_or_oauth_client
45
- end
46
-
47
- server.custom_access_token_expires_in.call(oauth_client)
48
- end
49
-
50
64
  def configuration
51
65
  Doorkeeper.configuration
52
66
  end
67
+
68
+ def controller
69
+ @controller ||= begin
70
+ mapping = Doorkeeper::Rails::Routes.mapping[:token_info] || {}
71
+ mapping[:controllers] || 'doorkeeper/token_info'
72
+ end
73
+ end
53
74
  end
54
75
  end
55
76
  end
@@ -4,19 +4,28 @@ module Doorkeeper
4
4
  validate :attributes, error: :invalid_request
5
5
  validate :client, error: :invalid_client
6
6
  validate :grant, error: :invalid_grant
7
+ # @see https://tools.ietf.org/html/rfc6749#section-5.2
7
8
  validate :redirect_uri, error: :invalid_grant
9
+ validate :code_verifier, error: :invalid_grant
8
10
 
9
- attr_accessor :server, :grant, :client, :redirect_uri, :access_token
11
+ attr_accessor :server, :grant, :client, :redirect_uri, :access_token,
12
+ :code_verifier
10
13
 
11
14
  def initialize(server, grant, client, parameters = {})
12
15
  @server = server
13
16
  @client = client
14
17
  @grant = grant
18
+ @grant_type = Doorkeeper::OAuth::AUTHORIZATION_CODE
15
19
  @redirect_uri = parameters[:redirect_uri]
20
+ @code_verifier = parameters[:code_verifier]
16
21
  end
17
22
 
18
23
  private
19
24
 
25
+ def client_by_uid(parameters)
26
+ Doorkeeper::Application.by_uid(parameters[:client_id])
27
+ end
28
+
20
29
  def before_successful_response
21
30
  grant.transaction do
22
31
  grant.lock!
@@ -28,14 +37,17 @@ module Doorkeeper
28
37
  grant.scopes,
29
38
  server)
30
39
  end
40
+ super
31
41
  end
32
42
 
33
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?
34
46
  redirect_uri.present?
35
47
  end
36
48
 
37
49
  def validate_client
38
- !!client
50
+ !client.nil?
39
51
  end
40
52
 
41
53
  def validate_grant
@@ -44,7 +56,25 @@ module Doorkeeper
44
56
  end
45
57
 
46
58
  def validate_redirect_uri
47
- grant.redirect_uri == redirect_uri
59
+ Helpers::URIChecker.valid_for_authorization?(
60
+ redirect_uri,
61
+ grant.redirect_uri
62
+ )
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
48
78
  end
49
79
  end
50
80
  end
@@ -3,8 +3,11 @@ 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
10
+
8
11
  if valid?
9
12
  before_successful_response
10
13
  @response = TokenResponse.new(access_token)
@@ -16,11 +19,7 @@ module Doorkeeper
16
19
  end
17
20
 
18
21
  def scopes
19
- @scopes ||= if @original_scopes.present?
20
- OAuth::Scopes.from_string(@original_scopes)
21
- else
22
- default_scopes
23
- end
22
+ @scopes ||= build_scopes
24
23
  end
25
24
 
26
25
  def default_scopes
@@ -36,14 +35,30 @@ module Doorkeeper
36
35
  client,
37
36
  resource_owner_id,
38
37
  scopes,
39
- Authorization::Token.access_token_expires_in(server, client),
40
- server.refresh_token_enabled?)
38
+ Authorization::Token.access_token_expires_in(server, client, grant_type, scopes),
39
+ server.refresh_token_enabled?
40
+ )
41
41
  end
42
42
 
43
43
  def before_successful_response
44
+ Doorkeeper.configuration.before_successful_strategy_response.call(self)
44
45
  end
45
46
 
46
47
  def after_successful_response
48
+ Doorkeeper.configuration.after_successful_strategy_response.call(self, @response)
49
+ end
50
+
51
+ private
52
+
53
+ def build_scopes
54
+ if @original_scopes.present?
55
+ OAuth::Scopes.from_string(@original_scopes)
56
+ else
57
+ client_scopes = @client.try(:scopes)
58
+ return default_scopes if client_scopes.blank?
59
+
60
+ default_scopes & @client.scopes
61
+ end
47
62
  end
48
63
  end
49
64
  end
@@ -1,10 +1,10 @@
1
1
  module Doorkeeper
2
2
  module OAuth
3
3
  class Client
4
- class Credentials < Struct.new(:uid, :secret)
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?
@@ -18,13 +18,15 @@ module Doorkeeper
18
18
  def from_basic(request)
19
19
  authorization = request.authorization
20
20
  if authorization.present? && authorization =~ /^Basic (.*)/m
21
- Base64.decode64($1).split(/:/, 2)
21
+ Base64.decode64(Regexp.last_match(1)).split(/:/, 2)
22
22
  end
23
23
  end
24
24
  end
25
25
 
26
+ # Public clients may have their secret blank, but "credentials" are
27
+ # still present
26
28
  def blank?
27
- uid.blank? || secret.blank?
29
+ uid.blank?
28
30
  end
29
31
  end
30
32
  end
@@ -12,7 +12,7 @@ module Doorkeeper
12
12
  end
13
13
 
14
14
  def self.find(uid, method = Application.method(:by_uid))
15
- if application = method.call(uid)
15
+ if (application = method.call(uid))
16
16
  new(application)
17
17
  end
18
18
  end
@@ -20,7 +20,7 @@ module Doorkeeper
20
20
  def self.authenticate(credentials, method = Application.method(:by_uid_and_secret))
21
21
  return false if credentials.blank?
22
22
 
23
- if application = method.call(credentials.uid, credentials.secret)
23
+ if (application = method.call(credentials.uid, credentials.secret))
24
24
  new(application)
25
25
  end
26
26
  end
@@ -25,7 +25,12 @@ module Doorkeeper
25
25
  private
26
26
 
27
27
  def create_token(client, scopes, creator)
28
- ttl = Authorization::Token.access_token_expires_in(@server, client)
28
+ ttl = Authorization::Token.access_token_expires_in(
29
+ @server,
30
+ client,
31
+ Doorkeeper::OAuth::CLIENT_CREDENTIALS,
32
+ scopes
33
+ )
29
34
 
30
35
  creator.call(
31
36
  client,