doorkeeper 4.2.5 → 5.0.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.
Files changed (215) 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/.gitlab-ci.yml +16 -0
  6. data/.hound.yml +2 -13
  7. data/.rubocop.yml +17 -0
  8. data/.travis.yml +19 -4
  9. data/Appraisals +8 -4
  10. data/CODE_OF_CONDUCT.md +46 -0
  11. data/Gemfile +1 -1
  12. data/NEWS.md +115 -0
  13. data/README.md +175 -39
  14. data/RELEASING.md +5 -12
  15. data/Rakefile +6 -0
  16. data/SECURITY.md +15 -0
  17. data/UPGRADE.md +2 -0
  18. data/app/assets/stylesheets/doorkeeper/admin/application.css +2 -2
  19. data/app/controllers/doorkeeper/application_controller.rb +2 -5
  20. data/app/controllers/doorkeeper/application_metal_controller.rb +4 -0
  21. data/app/controllers/doorkeeper/applications_controller.rb +47 -13
  22. data/app/controllers/doorkeeper/authorizations_controller.rb +55 -12
  23. data/app/controllers/doorkeeper/authorized_applications_controller.rb +19 -2
  24. data/app/controllers/doorkeeper/tokens_controller.rb +24 -17
  25. data/app/helpers/doorkeeper/dashboard_helper.rb +8 -6
  26. data/app/validators/redirect_uri_validator.rb +13 -2
  27. data/app/views/doorkeeper/applications/_delete_form.html.erb +3 -1
  28. data/app/views/doorkeeper/applications/_form.html.erb +31 -19
  29. data/app/views/doorkeeper/applications/edit.html.erb +1 -1
  30. data/app/views/doorkeeper/applications/index.html.erb +18 -6
  31. data/app/views/doorkeeper/applications/new.html.erb +1 -1
  32. data/app/views/doorkeeper/applications/show.html.erb +8 -5
  33. data/app/views/doorkeeper/authorizations/error.html.erb +1 -1
  34. data/app/views/doorkeeper/authorizations/new.html.erb +5 -1
  35. data/app/views/doorkeeper/authorized_applications/index.html.erb +0 -1
  36. data/app/views/layouts/doorkeeper/admin.html.erb +15 -15
  37. data/config/locales/en.yml +19 -6
  38. data/doorkeeper.gemspec +20 -20
  39. data/gemfiles/rails_4_2.gemfile +6 -4
  40. data/gemfiles/rails_5_0.gemfile +4 -4
  41. data/gemfiles/rails_5_1.gemfile +6 -7
  42. data/gemfiles/rails_5_2.gemfile +12 -0
  43. data/gemfiles/rails_master.gemfile +17 -0
  44. data/lib/doorkeeper/config.rb +123 -71
  45. data/lib/doorkeeper/engine.rb +7 -3
  46. data/lib/doorkeeper/errors.rb +20 -5
  47. data/lib/doorkeeper/grape/helpers.rb +14 -9
  48. data/lib/doorkeeper/helpers/controller.rb +16 -22
  49. data/lib/doorkeeper/models/access_grant_mixin.rb +67 -23
  50. data/lib/doorkeeper/models/access_token_mixin.rb +54 -55
  51. data/lib/doorkeeper/models/application_mixin.rb +16 -30
  52. data/lib/doorkeeper/models/concerns/expirable.rb +7 -5
  53. data/lib/doorkeeper/models/concerns/orderable.rb +13 -0
  54. data/lib/doorkeeper/models/concerns/scopes.rb +1 -1
  55. data/lib/doorkeeper/oauth/authorization/code.rb +31 -8
  56. data/lib/doorkeeper/oauth/authorization/context.rb +15 -0
  57. data/lib/doorkeeper/oauth/authorization/token.rb +46 -20
  58. data/lib/doorkeeper/oauth/authorization_code_request.rb +33 -3
  59. data/lib/doorkeeper/oauth/base_request.rb +23 -7
  60. data/lib/doorkeeper/oauth/client/credentials.rb +6 -4
  61. data/lib/doorkeeper/oauth/client.rb +2 -4
  62. data/lib/doorkeeper/oauth/client_credentials/creator.rb +2 -1
  63. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +6 -3
  64. data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -6
  65. data/lib/doorkeeper/oauth/client_credentials_request.rb +0 -4
  66. data/lib/doorkeeper/oauth/error.rb +2 -2
  67. data/lib/doorkeeper/oauth/error_response.rb +11 -4
  68. data/lib/doorkeeper/oauth/forbidden_token_response.rb +1 -1
  69. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +0 -8
  70. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +15 -0
  71. data/lib/doorkeeper/oauth/invalid_token_response.rb +3 -4
  72. data/lib/doorkeeper/oauth/password_access_token_request.rb +8 -4
  73. data/lib/doorkeeper/oauth/pre_authorization.rb +45 -13
  74. data/lib/doorkeeper/oauth/refresh_token_request.rb +7 -1
  75. data/lib/doorkeeper/oauth/scopes.rb +19 -9
  76. data/lib/doorkeeper/oauth/token.rb +6 -3
  77. data/lib/doorkeeper/oauth/token_introspection.rb +128 -0
  78. data/lib/doorkeeper/oauth/token_response.rb +4 -2
  79. data/lib/doorkeeper/oauth.rb +13 -0
  80. data/lib/doorkeeper/orm/active_record/access_grant.rb +27 -0
  81. data/lib/doorkeeper/orm/active_record/access_token.rb +21 -20
  82. data/lib/doorkeeper/orm/active_record/application.rb +45 -0
  83. data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +26 -0
  84. data/lib/doorkeeper/orm/active_record.rb +21 -8
  85. data/lib/doorkeeper/rails/helpers.rb +7 -10
  86. data/lib/doorkeeper/rails/routes.rb +21 -7
  87. data/lib/doorkeeper/rake/db.rake +40 -0
  88. data/lib/doorkeeper/rake/setup.rake +6 -0
  89. data/lib/doorkeeper/rake.rb +14 -0
  90. data/lib/doorkeeper/request/authorization_code.rb +0 -2
  91. data/lib/doorkeeper/request/client_credentials.rb +0 -2
  92. data/lib/doorkeeper/request/code.rb +0 -2
  93. data/lib/doorkeeper/request/password.rb +1 -13
  94. data/lib/doorkeeper/request/refresh_token.rb +0 -2
  95. data/lib/doorkeeper/request/token.rb +0 -2
  96. data/lib/doorkeeper/request.rb +29 -30
  97. data/lib/doorkeeper/validations.rb +3 -2
  98. data/lib/doorkeeper/version.rb +14 -1
  99. data/lib/doorkeeper.rb +21 -17
  100. data/lib/generators/doorkeeper/application_owner_generator.rb +26 -12
  101. data/lib/generators/doorkeeper/confidential_applications_generator.rb +32 -0
  102. data/lib/generators/doorkeeper/install_generator.rb +17 -9
  103. data/lib/generators/doorkeeper/migration_generator.rb +26 -9
  104. data/lib/generators/doorkeeper/pkce_generator.rb +32 -0
  105. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +31 -20
  106. data/lib/generators/doorkeeper/templates/add_confidential_to_applications.rb.erb +13 -0
  107. data/lib/generators/doorkeeper/templates/{add_owner_to_application_migration.rb → add_owner_to_application_migration.rb.erb} +1 -1
  108. data/lib/generators/doorkeeper/templates/{add_previous_refresh_token_to_access_tokens.rb → add_previous_refresh_token_to_access_tokens.rb.erb} +1 -1
  109. data/lib/generators/doorkeeper/templates/enable_pkce_migration.rb.erb +6 -0
  110. data/lib/generators/doorkeeper/templates/initializer.rb +104 -12
  111. data/lib/generators/doorkeeper/templates/{migration.rb → migration.rb.erb} +2 -1
  112. data/lib/generators/doorkeeper/views_generator.rb +3 -1
  113. data/spec/controllers/application_metal_controller_spec.rb +50 -0
  114. data/spec/controllers/applications_controller_spec.rb +141 -17
  115. data/spec/controllers/authorizations_controller_spec.rb +304 -27
  116. data/spec/controllers/protected_resources_controller_spec.rb +44 -35
  117. data/spec/controllers/token_info_controller_spec.rb +17 -21
  118. data/spec/controllers/tokens_controller_spec.rb +201 -17
  119. data/spec/dummy/app/assets/config/manifest.js +2 -0
  120. data/spec/dummy/config/environments/test.rb +4 -5
  121. data/spec/dummy/config/initializers/doorkeeper.rb +23 -2
  122. data/spec/dummy/config/initializers/{active_record_belongs_to_required_by_default.rb → new_framework_defaults.rb} +5 -1
  123. data/spec/dummy/config/initializers/secret_token.rb +0 -1
  124. data/spec/dummy/config/routes.rb +3 -42
  125. data/spec/dummy/db/migrate/20111122132257_create_users.rb +3 -1
  126. data/spec/dummy/db/migrate/20120312140401_add_password_to_users.rb +3 -1
  127. data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +3 -1
  128. data/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb +3 -1
  129. data/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb +3 -1
  130. data/spec/dummy/db/migrate/20170822064514_enable_pkce.rb +6 -0
  131. data/spec/dummy/db/migrate/20180210183654_add_confidential_to_applications.rb +13 -0
  132. data/spec/dummy/db/schema.rb +38 -37
  133. data/spec/factories.rb +1 -1
  134. data/spec/generators/application_owner_generator_spec.rb +25 -6
  135. data/spec/generators/confidential_applications_generator_spec.rb +45 -0
  136. data/spec/generators/install_generator_spec.rb +1 -1
  137. data/spec/generators/migration_generator_spec.rb +25 -4
  138. data/spec/generators/pkce_generator_spec.rb +43 -0
  139. data/spec/generators/previous_refresh_token_generator_spec.rb +57 -0
  140. data/spec/generators/views_generator_spec.rb +1 -1
  141. data/spec/grape/grape_integration_spec.rb +135 -0
  142. data/spec/helpers/doorkeeper/dashboard_helper_spec.rb +2 -2
  143. data/spec/lib/config_spec.rb +199 -22
  144. data/spec/lib/doorkeeper_spec.rb +1 -126
  145. data/spec/lib/models/expirable_spec.rb +0 -4
  146. data/spec/lib/models/revocable_spec.rb +2 -4
  147. data/spec/lib/models/scopes_spec.rb +0 -4
  148. data/spec/lib/oauth/authorization/uri_builder_spec.rb +0 -4
  149. data/spec/lib/oauth/authorization_code_request_spec.rb +63 -13
  150. data/spec/lib/oauth/base_request_spec.rb +43 -10
  151. data/spec/lib/oauth/base_response_spec.rb +1 -1
  152. data/spec/lib/oauth/client/credentials_spec.rb +5 -5
  153. data/spec/lib/oauth/client_credentials/creator_spec.rb +6 -2
  154. data/spec/lib/oauth/client_credentials/issuer_spec.rb +26 -7
  155. data/spec/lib/oauth/client_credentials/validation_spec.rb +2 -3
  156. data/spec/lib/oauth/client_credentials_integration_spec.rb +2 -2
  157. data/spec/lib/oauth/client_credentials_request_spec.rb +4 -5
  158. data/spec/lib/oauth/client_spec.rb +0 -3
  159. data/spec/lib/oauth/code_request_spec.rb +5 -5
  160. data/spec/lib/oauth/error_response_spec.rb +0 -3
  161. data/spec/lib/oauth/error_spec.rb +1 -3
  162. data/spec/lib/oauth/forbidden_token_response_spec.rb +1 -4
  163. data/spec/lib/oauth/helpers/scope_checker_spec.rb +0 -3
  164. data/spec/lib/oauth/helpers/unique_token_spec.rb +0 -1
  165. data/spec/lib/oauth/helpers/uri_checker_spec.rb +115 -3
  166. data/spec/lib/oauth/invalid_token_response_spec.rb +2 -5
  167. data/spec/lib/oauth/password_access_token_request_spec.rb +46 -5
  168. data/spec/lib/oauth/pre_authorization_spec.rb +40 -6
  169. data/spec/lib/oauth/refresh_token_request_spec.rb +30 -14
  170. data/spec/lib/oauth/scopes_spec.rb +28 -4
  171. data/spec/lib/oauth/token_request_spec.rb +10 -13
  172. data/spec/lib/oauth/token_response_spec.rb +0 -1
  173. data/spec/lib/oauth/token_spec.rb +37 -14
  174. data/spec/lib/orm/active_record/stale_records_cleaner_spec.rb +79 -0
  175. data/spec/lib/request/strategy_spec.rb +0 -1
  176. data/spec/lib/server_spec.rb +10 -0
  177. data/spec/models/doorkeeper/access_grant_spec.rb +45 -2
  178. data/spec/models/doorkeeper/access_token_spec.rb +132 -64
  179. data/spec/models/doorkeeper/application_spec.rb +114 -23
  180. data/spec/requests/applications/applications_request_spec.rb +139 -6
  181. data/spec/requests/applications/authorized_applications_spec.rb +1 -1
  182. data/spec/requests/endpoints/authorization_spec.rb +1 -1
  183. data/spec/requests/endpoints/token_spec.rb +15 -6
  184. data/spec/requests/flows/authorization_code_errors_spec.rb +12 -2
  185. data/spec/requests/flows/authorization_code_spec.rb +199 -2
  186. data/spec/requests/flows/client_credentials_spec.rb +73 -5
  187. data/spec/requests/flows/implicit_grant_errors_spec.rb +3 -3
  188. data/spec/requests/flows/implicit_grant_spec.rb +38 -11
  189. data/spec/requests/flows/password_spec.rb +160 -24
  190. data/spec/requests/flows/refresh_token_spec.rb +6 -6
  191. data/spec/requests/flows/revoke_token_spec.rb +26 -26
  192. data/spec/requests/flows/skip_authorization_spec.rb +16 -11
  193. data/spec/requests/protected_resources/metal_spec.rb +2 -2
  194. data/spec/requests/protected_resources/private_api_spec.rb +2 -2
  195. data/spec/routing/custom_controller_routes_spec.rb +63 -7
  196. data/spec/routing/default_routes_spec.rb +6 -2
  197. data/spec/routing/scoped_routes_spec.rb +16 -2
  198. data/spec/spec_helper.rb +54 -3
  199. data/spec/spec_helper_integration.rb +2 -64
  200. data/spec/support/dependencies/factory_bot.rb +2 -0
  201. data/spec/support/doorkeeper_rspec.rb +19 -0
  202. data/spec/support/helpers/access_token_request_helper.rb +1 -1
  203. data/spec/support/helpers/authorization_request_helper.rb +4 -4
  204. data/spec/support/helpers/model_helper.rb +9 -4
  205. data/spec/support/helpers/request_spec_helper.rb +18 -6
  206. data/spec/support/helpers/url_helper.rb +15 -10
  207. data/spec/support/http_method_shim.rb +12 -16
  208. data/spec/support/shared/controllers_shared_context.rb +2 -6
  209. data/spec/support/shared/models_shared_examples.rb +4 -4
  210. data/spec/validators/redirect_uri_validator_spec.rb +58 -7
  211. data/spec/version/version_spec.rb +15 -0
  212. data/vendor/assets/stylesheets/doorkeeper/bootstrap.min.css +4 -5
  213. metadata +75 -33
  214. data/spec/controllers/application_metal_controller.rb +0 -10
  215. data/spec/support/dependencies/factory_girl.rb +0 -2
@@ -6,24 +6,16 @@ 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_grants
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
11
 
23
- validates :resource_owner_id, :application_id, :token, :expires_in, :redirect_uri, presence: true
24
- validates :token, uniqueness: true
12
+ # never uses pkce, if pkce migrations were not generated
13
+ def uses_pkce?
14
+ pkce_supported? && code_challenge.present?
15
+ end
25
16
 
26
- before_validation :generate_token, on: :create
17
+ def pkce_supported?
18
+ respond_to? :code_challenge
27
19
  end
28
20
 
29
21
  module ClassMethods
@@ -38,16 +30,68 @@ module Doorkeeper
38
30
  def by_token(token)
39
31
  find_by(token: token.to_s)
40
32
  end
41
- end
42
33
 
43
- private
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
44
91
 
45
- # Generates token value with UniqueToken class.
46
- #
47
- # @return [String] token value
48
- #
49
- def generate_token
50
- self.token = UniqueToken.generate
92
+ def pkce_supported?
93
+ new.pkce_supported?
94
+ end
51
95
  end
52
96
  end
53
97
  end
@@ -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
 
@@ -180,7 +173,7 @@ module Doorkeeper
180
173
  # The OAuth 2.0 Authorization Framework: Bearer Token Usage
181
174
  #
182
175
  def token_type
183
- 'bearer'
176
+ 'Bearer'
184
177
  end
185
178
 
186
179
  def use_refresh_token?
@@ -194,8 +187,8 @@ module Doorkeeper
194
187
  def as_json(_options = {})
195
188
  {
196
189
  resource_owner_id: resource_owner_id,
197
- scopes: scopes,
198
- expires_in_seconds: expires_in_seconds,
190
+ scope: scopes,
191
+ expires_in: expires_in_seconds,
199
192
  application: { uid: application.try(:uid) },
200
193
  created_at: created_at.to_i
201
194
  }
@@ -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,61 @@ module Doorkeeper
4
4
  class Token
5
5
  attr_accessor :pre_auth, :resource_owner, :token
6
6
 
7
+ class << self
8
+ def build_context(pre_auth_or_oauth_client, grant_type, scopes)
9
+ oauth_client = if pre_auth_or_oauth_client.respond_to?(:client)
10
+ pre_auth_or_oauth_client.client
11
+ else
12
+ pre_auth_or_oauth_client
13
+ end
14
+
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
36
+ end
37
+ end
38
+
7
39
  def initialize(pre_auth, resource_owner)
8
40
  @pre_auth = pre_auth
9
41
  @resource_owner = resource_owner
10
42
  end
11
43
 
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
44
  def issue_token
45
+ context = self.class.build_context(
46
+ pre_auth.client,
47
+ Doorkeeper::OAuth::IMPLICIT,
48
+ pre_auth.scopes
49
+ )
21
50
  @token ||= AccessToken.find_or_create_for(
22
51
  pre_auth.client,
23
52
  resource_owner.id,
24
53
  pre_auth.scopes,
25
- self.class.access_token_expires_in(configuration, pre_auth),
54
+ self.class.access_token_expires_in(configuration, context),
26
55
  false
27
56
  )
28
57
  end
29
58
 
30
59
  def native_redirect
31
60
  {
32
- controller: 'doorkeeper/token_info',
61
+ controller: controller,
33
62
  action: :show,
34
63
  access_token: token.token
35
64
  }
@@ -37,19 +66,16 @@ module Doorkeeper
37
66
 
38
67
  private
39
68
 
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
69
  def configuration
51
70
  Doorkeeper.configuration
52
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
53
79
  end
54
80
  end
55
81
  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