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
@@ -13,7 +13,9 @@ module Doorkeeper
13
13
  validate :scopes, error: :invalid_scope
14
14
 
15
15
  def initialize(server, request)
16
- @server, @request, @client = server, request, request.client
16
+ @server = server
17
+ @request = request
18
+ @client = request.client
17
19
 
18
20
  validate
19
21
  end
@@ -25,7 +27,7 @@ module Doorkeeper
25
27
  end
26
28
 
27
29
  def validate_scopes
28
- return true unless @request.scopes.present?
30
+ return true if @request.scopes.blank?
29
31
 
30
32
  application_scopes = if @client.present?
31
33
  @client.application.scopes
@@ -1,10 +1,10 @@
1
1
  module Doorkeeper
2
2
  module OAuth
3
- class Error < Struct.new(:name, :state)
3
+ Error = Struct.new(:name, :state) do
4
4
  def description
5
5
  I18n.translate(
6
6
  name,
7
- scope: [:doorkeeper, :errors, :messages],
7
+ scope: %i[doorkeeper errors messages],
8
8
  default: :server_error
9
9
  )
10
10
  end
@@ -4,8 +4,13 @@ module Doorkeeper
4
4
  include OAuth::Helpers
5
5
 
6
6
  def self.from_request(request, attributes = {})
7
- state = request.state if request.respond_to?(:state)
8
- new(attributes.merge(name: request.error, state: state))
7
+ new(
8
+ attributes.merge(
9
+ name: request.error,
10
+ state: request.try(:state),
11
+ redirect_uri: request.try(:redirect_uri)
12
+ )
13
+ )
9
14
  end
10
15
 
11
16
  delegate :name, :description, :state, to: :@error
@@ -42,10 +47,12 @@ module Doorkeeper
42
47
  end
43
48
 
44
49
  def headers
45
- { 'Cache-Control' => 'no-store',
50
+ {
51
+ 'Cache-Control' => 'no-store',
46
52
  'Pragma' => 'no-cache',
47
53
  'Content-Type' => 'application/json; charset=utf-8',
48
- 'WWW-Authenticate' => authenticate_info }
54
+ 'WWW-Authenticate' => authenticate_info
55
+ }
49
56
  end
50
57
 
51
58
  protected
@@ -21,7 +21,7 @@ module Doorkeeper
21
21
  end
22
22
 
23
23
  def description
24
- scope = { scope: [:doorkeeper, :scopes] }
24
+ scope = { scope: %i[doorkeeper scopes] }
25
25
  @description ||= @scopes.map { |r| I18n.translate r, scope }.join('\n')
26
26
  end
27
27
  end
@@ -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
@@ -3,6 +3,7 @@ module Doorkeeper
3
3
  module Helpers
4
4
  module URIChecker
5
5
  def self.valid?(url)
6
+ return true if native_uri?(url)
6
7
  uri = as_uri(url)
7
8
  uri.fragment.nil? && !uri.host.nil? && !uri.scheme.nil?
8
9
  rescue URI::InvalidURIError
@@ -12,6 +13,13 @@ module Doorkeeper
12
13
  def self.matches?(url, client_url)
13
14
  url = as_uri(url)
14
15
  client_url = as_uri(client_url)
16
+
17
+ if client_url.query.present?
18
+ return false unless query_matches?(url.query, client_url.query)
19
+ # Clear out queries so rest of URI can be tested. This allows query
20
+ # params to be in the request but order not mattering.
21
+ client_url.query = nil
22
+ end
15
23
  url.query = nil
16
24
  url == client_url
17
25
  end
@@ -24,6 +32,13 @@ module Doorkeeper
24
32
  URI.parse(url)
25
33
  end
26
34
 
35
+ def self.query_matches?(query, client_query)
36
+ return true if client_query.nil? && query.nil?
37
+ return false if client_query.nil? || query.nil?
38
+ # Will return true independent of query order
39
+ client_query.split('&').sort == query.split('&').sort
40
+ end
41
+
27
42
  def self.native_uri?(url)
28
43
  url == Doorkeeper.configuration.native_redirect_uri
29
44
  end
@@ -4,10 +4,9 @@ module Doorkeeper
4
4
  attr_reader :reason
5
5
 
6
6
  def self.from_access_token(access_token, attributes = {})
7
- reason = case
8
- when access_token.try(:revoked?)
7
+ reason = if access_token.try(:revoked?)
9
8
  :revoked
10
- when access_token.try(:expired?)
9
+ elsif access_token.try(:expired?)
11
10
  :expired
12
11
  else
13
12
  :unknown
@@ -22,7 +21,7 @@ module Doorkeeper
22
21
  end
23
22
 
24
23
  def description
25
- scope = { scope: [:doorkeeper, :errors, :messages, :invalid_token] }
24
+ scope = { scope: %i[doorkeeper errors messages invalid_token] }
26
25
  @description ||= I18n.translate @reason, scope
27
26
  end
28
27
  end
@@ -16,25 +16,29 @@ module Doorkeeper
16
16
  @client = client
17
17
  @parameters = parameters
18
18
  @original_scopes = parameters[:scope]
19
+ @grant_type = Doorkeeper::OAuth::PASSWORD
19
20
  end
20
21
 
21
22
  private
22
23
 
23
24
  def before_successful_response
24
25
  find_or_create_access_token(client, resource_owner.id, scopes, server)
26
+ super
25
27
  end
26
28
 
27
29
  def validate_scopes
28
- return true unless @original_scopes.present?
29
- ScopeChecker.valid? @original_scopes, server.scopes, client.try(:scopes)
30
+ client_scopes = client.try(:scopes)
31
+ return true if scopes.blank?
32
+
33
+ ScopeChecker.valid?(scopes.to_s, server.scopes, client_scopes)
30
34
  end
31
35
 
32
36
  def validate_resource_owner
33
- !!resource_owner
37
+ !resource_owner.nil?
34
38
  end
35
39
 
36
40
  def validate_client
37
- !parameters[:client_id] || !!client
41
+ !parameters[:client_id] || !client.nil?
38
42
  end
39
43
  end
40
44
  end
@@ -7,17 +7,21 @@ module Doorkeeper
7
7
  validate :client, error: :invalid_client
8
8
  validate :scopes, error: :invalid_scope
9
9
  validate :redirect_uri, error: :invalid_redirect_uri
10
+ validate :code_challenge_method, error: :invalid_code_challenge_method
10
11
 
11
- attr_accessor :server, :client, :response_type, :redirect_uri, :state
12
+ attr_accessor :server, :client, :response_type, :redirect_uri, :state,
13
+ :code_challenge, :code_challenge_method
12
14
  attr_writer :scope
13
15
 
14
16
  def initialize(server, client, attrs = {})
15
- @server = server
16
- @client = client
17
- @response_type = attrs[:response_type]
18
- @redirect_uri = attrs[:redirect_uri]
19
- @scope = attrs[:scope]
20
- @state = attrs[:state]
17
+ @server = server
18
+ @client = client
19
+ @response_type = attrs[:response_type]
20
+ @redirect_uri = attrs[:redirect_uri]
21
+ @scope = attrs[:scope]
22
+ @state = attrs[:state]
23
+ @code_challenge = attrs[:code_challenge]
24
+ @code_challenge_method = attrs[:code_challenge_method]
21
25
  end
22
26
 
23
27
  def authorizable?
@@ -29,15 +33,36 @@ module Doorkeeper
29
33
  end
30
34
 
31
35
  def scope
32
- @scope.presence || server.default_scopes.to_s
36
+ @scope.presence || build_scopes
33
37
  end
34
38
 
35
39
  def error_response
36
40
  OAuth::ErrorResponse.from_request(self)
37
41
  end
38
42
 
43
+ def as_json(_options)
44
+ {
45
+ client_id: client.uid,
46
+ redirect_uri: redirect_uri,
47
+ state: state,
48
+ response_type: response_type,
49
+ scope: scope,
50
+ client_name: client.name,
51
+ status: I18n.t('doorkeeper.pre_authorization.status')
52
+ }
53
+ end
54
+
39
55
  private
40
56
 
57
+ def build_scopes
58
+ client_scopes = client.application.scopes
59
+ if client_scopes.blank?
60
+ server.default_scopes.to_s
61
+ else
62
+ (server.default_scopes & client_scopes).to_s
63
+ end
64
+ end
65
+
41
66
  def validate_response_type
42
67
  server.authorization_response_types.include? response_type
43
68
  end
@@ -47,7 +72,8 @@ module Doorkeeper
47
72
  end
48
73
 
49
74
  def validate_scopes
50
- return true unless scope.present?
75
+ return true if scope.blank?
76
+
51
77
  Helpers::ScopeChecker.valid?(
52
78
  scope,
53
79
  server.scopes,
@@ -55,11 +81,17 @@ module Doorkeeper
55
81
  )
56
82
  end
57
83
 
58
- # TODO: test uri should be matched against the client's one
59
84
  def validate_redirect_uri
60
- return false unless redirect_uri.present?
61
- Helpers::URIChecker.native_uri?(redirect_uri) ||
62
- Helpers::URIChecker.valid_for_authorization?(redirect_uri, client.redirect_uri)
85
+ return false if redirect_uri.blank?
86
+
87
+ Helpers::URIChecker.valid_for_authorization?(
88
+ redirect_uri,
89
+ client.redirect_uri
90
+ )
91
+ end
92
+
93
+ def validate_code_challenge_method
94
+ !code_challenge.present? || (code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/)
63
95
  end
64
96
  end
65
97
  end
@@ -35,6 +35,7 @@ module Doorkeeper
35
35
  refresh_token.revoke unless refresh_token_revoked_on_use?
36
36
  create_access_token
37
37
  end
38
+ super
38
39
  end
39
40
 
40
41
  def refresh_token_revoked_on_use?
@@ -64,7 +65,12 @@ module Doorkeeper
64
65
  end
65
66
 
66
67
  def access_token_expires_in
67
- Authorization::Token.access_token_expires_in(server, client)
68
+ Authorization::Token.access_token_expires_in(
69
+ server,
70
+ client,
71
+ Doorkeeper::OAuth::REFRESH_TOKEN,
72
+ scopes
73
+ )
68
74
  end
69
75
 
70
76
  def validate_token_presence
@@ -41,24 +41,34 @@ module Doorkeeper
41
41
  end
42
42
 
43
43
  def has_scopes?(scopes)
44
- scopes.all? { |s| exists?(s) }
44
+ scopes.all? { |scope| exists?(scope) }
45
45
  end
46
46
 
47
47
  def +(other)
48
- if other.is_a? Scopes
49
- self.class.from_array(all + other.all)
50
- else
51
- super(other)
52
- end
48
+ self.class.from_array(all + to_array(other))
53
49
  end
54
50
 
55
51
  def <=>(other)
56
- map(&:to_s).sort <=> other.map(&:to_s).sort
52
+ if other.respond_to?(:map)
53
+ map(&:to_s).sort <=> other.map(&:to_s).sort
54
+ else
55
+ super
56
+ end
57
57
  end
58
58
 
59
59
  def &(other)
60
- other_array = other.present? ? other.all : []
61
- self.class.from_array(all & other_array)
60
+ self.class.from_array(all & to_array(other))
61
+ end
62
+
63
+ private
64
+
65
+ def to_array(other)
66
+ case other
67
+ when Scopes
68
+ other.all
69
+ else
70
+ other.to_a
71
+ end
62
72
  end
63
73
  end
64
74
  end
@@ -3,7 +3,7 @@ module Doorkeeper
3
3
  class Token
4
4
  class << self
5
5
  def from_request(request, *methods)
6
- methods.inject(nil) do |credentials, method|
6
+ methods.inject(nil) do |_, method|
7
7
  method = self.method(method) if method.is_a?(Symbol)
8
8
  credentials = method.call(request)
9
9
  break credentials unless credentials.blank?
@@ -11,9 +11,12 @@ module Doorkeeper
11
11
  end
12
12
 
13
13
  def authenticate(request, *methods)
14
- if token = from_request(request, *methods)
14
+ if (token = from_request(request, *methods))
15
15
  access_token = AccessToken.by_token(token)
16
- access_token.revoke_previous_refresh_token! if access_token
16
+ refresh_token_enabled = Doorkeeper.configuration.refresh_token_enabled?
17
+ if access_token.present? && refresh_token_enabled
18
+ access_token.revoke_previous_refresh_token!
19
+ end
17
20
  access_token
18
21
  end
19
22
  end
@@ -0,0 +1,128 @@
1
+ module Doorkeeper
2
+ module OAuth
3
+ # RFC7662 OAuth 2.0 Token Introspection
4
+ #
5
+ # @see https://tools.ietf.org/html/rfc7662
6
+ class TokenIntrospection
7
+ attr_reader :server, :token
8
+ attr_reader :error
9
+
10
+ def initialize(server, token)
11
+ @server = server
12
+ @token = token
13
+
14
+ authorize!
15
+ end
16
+
17
+ def authorized?
18
+ @error.blank?
19
+ end
20
+
21
+ def to_json
22
+ active? ? success_response : failure_response
23
+ end
24
+
25
+ private
26
+
27
+ # If the protected resource uses OAuth 2.0 client credentials to
28
+ # authenticate to the introspection endpoint and its credentials are
29
+ # invalid, the authorization server responds with an HTTP 401
30
+ # (Unauthorized) as described in Section 5.2 of OAuth 2.0 [RFC6749].
31
+ #
32
+ # Endpoint must first validate the authentication.
33
+ # If the authentication is invalid, the endpoint should respond with
34
+ # an HTTP 401 status code and an invalid_client response.
35
+ #
36
+ # @see https://www.oauth.com/oauth2-servers/token-introspection-endpoint/
37
+ #
38
+ def authorize!
39
+ # Requested client authorization
40
+ if server.credentials
41
+ @error = :invalid_client unless authorized_client
42
+ else
43
+ # Requested bearer token authorization
44
+ @error = :invalid_request unless authorized_token
45
+ end
46
+ end
47
+
48
+ # Client Authentication
49
+ def authorized_client
50
+ @authorized_client ||= server.credentials && server.client
51
+ end
52
+
53
+ # Bearer Token Authentication
54
+ def authorized_token
55
+ @authorized_token ||=
56
+ OAuth::Token.authenticate(server.context.request, :from_bearer_authorization)
57
+ end
58
+
59
+ # 2.2. Introspection Response
60
+ def success_response
61
+ {
62
+ active: true,
63
+ scope: @token.scopes_string,
64
+ client_id: @token.try(:application).try(:uid),
65
+ token_type: @token.token_type,
66
+ exp: @token.expires_at.to_i,
67
+ iat: @token.created_at.to_i
68
+ }
69
+ end
70
+
71
+ # If the introspection call is properly authorized but the token is not
72
+ # active, does not exist on this server, or the protected resource is
73
+ # not allowed to introspect this particular token, then the
74
+ # authorization server MUST return an introspection response with the
75
+ # "active" field set to "false". Note that to avoid disclosing too
76
+ # much of the authorization server's state to a third party, the
77
+ # authorization server SHOULD NOT include any additional information
78
+ # about an inactive token, including why the token is inactive.
79
+ #
80
+ # @see https://tools.ietf.org/html/rfc7662 2.2. Introspection Response
81
+ #
82
+ def failure_response
83
+ {
84
+ active: false
85
+ }
86
+ end
87
+
88
+ # Boolean indicator of whether or not the presented token
89
+ # is currently active. The specifics of a token's "active" state
90
+ # will vary depending on the implementation of the authorization
91
+ # server and the information it keeps about its tokens, but a "true"
92
+ # value return for the "active" property will generally indicate
93
+ # that a given token has been issued by this authorization server,
94
+ # has not been revoked by the resource owner, and is within its
95
+ # given time window of validity (e.g., after its issuance time and
96
+ # before its expiration time).
97
+ #
98
+ # Any other error is considered an "inactive" token.
99
+ #
100
+ # * The token requested does not exist or is invalid
101
+ # * The token expired
102
+ # * The token was issued to a different client than is making this request
103
+ #
104
+ def active?
105
+ if authorized_client
106
+ valid_token? && authorized_for_client?
107
+ else
108
+ valid_token?
109
+ end
110
+ end
111
+
112
+ # Token can be valid only if it is not expired or revoked.
113
+ def valid_token?
114
+ @token.present? && @token.accessible?
115
+ end
116
+
117
+ # If token doesn't belong to some client, then it is public.
118
+ # Otherwise in it required for token to be connected to the same client.
119
+ def authorized_for_client?
120
+ if @token.application.present?
121
+ @token.application == authorized_client.application
122
+ else
123
+ true
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -23,9 +23,11 @@ module Doorkeeper
23
23
  end
24
24
 
25
25
  def headers
26
- { 'Cache-Control' => 'no-store',
26
+ {
27
+ 'Cache-Control' => 'no-store',
27
28
  'Pragma' => 'no-cache',
28
- 'Content-Type' => 'application/json; charset=utf-8' }
29
+ 'Content-Type' => 'application/json; charset=utf-8'
30
+ }
29
31
  end
30
32
  end
31
33
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ module OAuth
5
+ GRANT_TYPES = [
6
+ AUTHORIZATION_CODE = 'authorization_code'.freeze,
7
+ IMPLICIT = 'implicit'.freeze,
8
+ PASSWORD = 'password'.freeze,
9
+ CLIENT_CREDENTIALS = 'client_credentials'.freeze,
10
+ REFRESH_TOKEN = 'refresh_token'.freeze
11
+ ].freeze
12
+ end
13
+ end
@@ -3,5 +3,32 @@ module Doorkeeper
3
3
  self.table_name = "#{table_name_prefix}oauth_access_grants#{table_name_suffix}".to_sym
4
4
 
5
5
  include AccessGrantMixin
6
+ include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
7
+
8
+ belongs_to_options = {
9
+ class_name: 'Doorkeeper::Application',
10
+ inverse_of: :access_grants
11
+ }
12
+
13
+ if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
14
+ belongs_to_options[:optional] = true
15
+ end
16
+
17
+ belongs_to :application, belongs_to_options
18
+
19
+ validates :resource_owner_id, :application_id, :token, :expires_in, :redirect_uri, presence: true
20
+ validates :token, uniqueness: true
21
+
22
+ before_validation :generate_token, on: :create
23
+
24
+ private
25
+
26
+ # Generates token value with UniqueToken class.
27
+ #
28
+ # @return [String] token value
29
+ #
30
+ def generate_token
31
+ self.token = UniqueToken.generate
32
+ end
6
33
  end
7
34
  end
@@ -3,18 +3,29 @@ module Doorkeeper
3
3
  self.table_name = "#{table_name_prefix}oauth_access_tokens#{table_name_suffix}".to_sym
4
4
 
5
5
  include AccessTokenMixin
6
+ include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
6
7
 
7
- # Deletes all the Access Tokens created for the specific
8
- # Application and Resource Owner.
9
- #
10
- # @param application_id [Integer] Application ID
11
- # @param resource_owner [ActiveRecord::Base] Resource Owner model instance
12
- #
13
- def self.delete_all_for(application_id, resource_owner)
14
- where(application_id: application_id,
15
- resource_owner_id: resource_owner.id).delete_all
8
+ belongs_to_options = {
9
+ class_name: 'Doorkeeper::Application',
10
+ inverse_of: :access_tokens
11
+ }
12
+
13
+ if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
14
+ belongs_to_options[:optional] = true
16
15
  end
17
- private_class_method :delete_all_for
16
+
17
+ belongs_to :application, belongs_to_options
18
+
19
+ validates :token, presence: true, uniqueness: true
20
+ validates :refresh_token, uniqueness: true, if: :use_refresh_token?
21
+
22
+ # @attr_writer [Boolean, nil] use_refresh_token
23
+ # indicates the possibility of using refresh token
24
+ attr_writer :use_refresh_token
25
+
26
+ before_validation :generate_token, on: :create
27
+ before_validation :generate_refresh_token,
28
+ on: :create, if: :use_refresh_token?
18
29
 
19
30
  # Searches for not revoked Access Tokens associated with the
20
31
  # specific Resource Owner.
@@ -29,18 +40,8 @@ module Doorkeeper
29
40
  where(resource_owner_id: resource_owner.id, revoked_at: nil)
30
41
  end
31
42
 
32
- # ORM-specific order method.
33
- def self.order_method
34
- :order
35
- end
36
-
37
43
  def self.refresh_token_revoked_on_use?
38
44
  column_names.include?('previous_refresh_token')
39
45
  end
40
-
41
- # ORM-specific DESC order for `:created_at` column.
42
- def self.created_at_desc
43
- 'created_at desc'
44
- end
45
46
  end
46
47
  end
@@ -3,6 +3,19 @@ module Doorkeeper
3
3
  self.table_name = "#{table_name_prefix}oauth_applications#{table_name_suffix}".to_sym
4
4
 
5
5
  include ApplicationMixin
6
+ include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
7
+
8
+ has_many :access_grants, dependent: :delete_all, class_name: 'Doorkeeper::AccessGrant'
9
+ has_many :access_tokens, dependent: :delete_all, class_name: 'Doorkeeper::AccessToken'
10
+
11
+ validates :name, :secret, :uid, presence: true
12
+ validates :uid, uniqueness: true
13
+ validates :redirect_uri, redirect_uri: true
14
+ validates :confidential, inclusion: { in: [true, false] }
15
+
16
+ validate :scopes_match_configured, if: :enforce_scopes?
17
+
18
+ before_validation :generate_uid, :generate_secret, on: :create
6
19
 
7
20
  has_many :authorized_tokens, -> { where(revoked_at: nil) }, class_name: 'AccessToken'
8
21
  has_many :authorized_applications, through: :authorized_tokens, source: :application
@@ -20,5 +33,26 @@ module Doorkeeper
20
33
  resource_access_tokens = AccessToken.active_for(resource_owner)
21
34
  where(id: resource_access_tokens.select(:application_id).distinct)
22
35
  end
36
+
37
+ private
38
+
39
+ def generate_uid
40
+ self.uid = UniqueToken.generate if uid.blank?
41
+ end
42
+
43
+ def generate_secret
44
+ self.secret = UniqueToken.generate if secret.blank?
45
+ end
46
+
47
+ def scopes_match_configured
48
+ if scopes.present? &&
49
+ !ScopeChecker.valid?(scopes.to_s, Doorkeeper.configuration.scopes)
50
+ errors.add(:scopes, :not_match_configured)
51
+ end
52
+ end
53
+
54
+ def enforce_scopes?
55
+ Doorkeeper.configuration.enforce_configured_scopes?
56
+ end
23
57
  end
24
58
  end