doorkeeper 5.0.3 → 5.1.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/.hound.yml +2 -1
  3. data/.rubocop.yml +37 -4
  4. data/.travis.yml +11 -21
  5. data/Appraisals +29 -7
  6. data/Dangerfile +5 -2
  7. data/Gemfile +19 -5
  8. data/NEWS.md +81 -21
  9. data/README.md +75 -485
  10. data/Rakefile +10 -8
  11. data/app/controllers/doorkeeper/application_controller.rb +1 -2
  12. data/app/controllers/doorkeeper/application_metal_controller.rb +2 -13
  13. data/app/controllers/doorkeeper/applications_controller.rb +20 -8
  14. data/app/controllers/doorkeeper/authorized_applications_controller.rb +1 -1
  15. data/app/controllers/doorkeeper/token_info_controller.rb +2 -2
  16. data/app/controllers/doorkeeper/tokens_controller.rb +11 -11
  17. data/app/helpers/doorkeeper/dashboard_helper.rb +1 -1
  18. data/app/validators/redirect_uri_validator.rb +5 -2
  19. data/app/views/doorkeeper/applications/_form.html.erb +6 -0
  20. data/app/views/doorkeeper/applications/show.html.erb +1 -1
  21. data/app/views/layouts/doorkeeper/admin.html.erb +5 -3
  22. data/bin/console +16 -0
  23. data/config/locales/en.yml +1 -0
  24. data/doorkeeper.gemspec +24 -21
  25. data/gemfiles/rails_5_0.gemfile +9 -4
  26. data/gemfiles/rails_5_1.gemfile +9 -4
  27. data/gemfiles/rails_5_2.gemfile +10 -5
  28. data/gemfiles/rails_6_0.gemfile +17 -0
  29. data/gemfiles/rails_master.gemfile +9 -9
  30. data/lib/doorkeeper/config/option.rb +64 -0
  31. data/lib/doorkeeper/config.rb +191 -75
  32. data/lib/doorkeeper/engine.rb +1 -1
  33. data/lib/doorkeeper/grape/authorization_decorator.rb +4 -4
  34. data/lib/doorkeeper/grape/helpers.rb +3 -3
  35. data/lib/doorkeeper/helpers/controller.rb +3 -2
  36. data/lib/doorkeeper/models/access_grant_mixin.rb +20 -3
  37. data/lib/doorkeeper/models/access_token_mixin.rb +63 -19
  38. data/lib/doorkeeper/models/application_mixin.rb +43 -1
  39. data/lib/doorkeeper/models/concerns/expirable.rb +4 -2
  40. data/lib/doorkeeper/models/concerns/ownership.rb +1 -6
  41. data/lib/doorkeeper/models/concerns/reusable.rb +19 -0
  42. data/lib/doorkeeper/models/concerns/revocable.rb +2 -1
  43. data/lib/doorkeeper/models/concerns/scopes.rb +5 -1
  44. data/lib/doorkeeper/models/concerns/secret_storable.rb +108 -0
  45. data/lib/doorkeeper/oauth/authorization/code.rb +2 -2
  46. data/lib/doorkeeper/oauth/authorization/token.rb +12 -7
  47. data/lib/doorkeeper/oauth/authorization/uri_builder.rb +1 -1
  48. data/lib/doorkeeper/oauth/authorization_code_request.rb +6 -4
  49. data/lib/doorkeeper/oauth/client.rb +1 -1
  50. data/lib/doorkeeper/oauth/client_credentials/validation.rb +5 -4
  51. data/lib/doorkeeper/oauth/client_credentials_request.rb +1 -1
  52. data/lib/doorkeeper/oauth/code_response.rb +2 -2
  53. data/lib/doorkeeper/oauth/error_response.rb +10 -6
  54. data/lib/doorkeeper/oauth/forbidden_token_response.rb +1 -1
  55. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +23 -8
  56. data/lib/doorkeeper/oauth/helpers/unique_token.rb +13 -1
  57. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +37 -1
  58. data/lib/doorkeeper/oauth/invalid_token_response.rb +5 -1
  59. data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -2
  60. data/lib/doorkeeper/oauth/pre_authorization.rb +11 -5
  61. data/lib/doorkeeper/oauth/refresh_token_request.rb +5 -2
  62. data/lib/doorkeeper/oauth/scopes.rb +5 -3
  63. data/lib/doorkeeper/oauth/token.rb +2 -2
  64. data/lib/doorkeeper/oauth/token_introspection.rb +74 -8
  65. data/lib/doorkeeper/oauth/token_response.rb +9 -9
  66. data/lib/doorkeeper/oauth.rb +5 -5
  67. data/lib/doorkeeper/orm/active_record/access_grant.rb +27 -13
  68. data/lib/doorkeeper/orm/active_record/access_token.rb +6 -13
  69. data/lib/doorkeeper/orm/active_record/application.rb +21 -57
  70. data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +10 -3
  71. data/lib/doorkeeper/orm/active_record.rb +6 -6
  72. data/lib/doorkeeper/rails/helpers.rb +1 -1
  73. data/lib/doorkeeper/rails/routes/mapping.rb +7 -7
  74. data/lib/doorkeeper/rails/routes.rb +11 -11
  75. data/lib/doorkeeper/rake/db.rake +13 -13
  76. data/lib/doorkeeper/rake.rb +1 -1
  77. data/lib/doorkeeper/request.rb +1 -1
  78. data/lib/doorkeeper/secret_storing/base.rb +64 -0
  79. data/lib/doorkeeper/secret_storing/bcrypt.rb +60 -0
  80. data/lib/doorkeeper/secret_storing/plain.rb +33 -0
  81. data/lib/doorkeeper/secret_storing/sha256_hash.rb +26 -0
  82. data/lib/doorkeeper/stale_records_cleaner.rb +1 -1
  83. data/lib/doorkeeper/version.rb +3 -3
  84. data/lib/doorkeeper.rb +69 -60
  85. data/lib/generators/doorkeeper/application_owner_generator.rb +10 -9
  86. data/lib/generators/doorkeeper/confidential_applications_generator.rb +10 -9
  87. data/lib/generators/doorkeeper/install_generator.rb +11 -9
  88. data/lib/generators/doorkeeper/migration_generator.rb +9 -9
  89. data/lib/generators/doorkeeper/pkce_generator.rb +10 -9
  90. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +10 -9
  91. data/lib/generators/doorkeeper/templates/initializer.rb +120 -13
  92. data/lib/generators/doorkeeper/templates/migration.rb.erb +15 -7
  93. data/lib/generators/doorkeeper/views_generator.rb +6 -4
  94. data/spec/controllers/application_metal_controller_spec.rb +24 -10
  95. data/spec/controllers/applications_controller_spec.rb +54 -52
  96. data/spec/controllers/authorizations_controller_spec.rb +139 -145
  97. data/spec/controllers/protected_resources_controller_spec.rb +78 -76
  98. data/spec/controllers/token_info_controller_spec.rb +14 -12
  99. data/spec/controllers/tokens_controller_spec.rb +167 -108
  100. data/spec/dummy/Rakefile +3 -1
  101. data/spec/dummy/app/controllers/application_controller.rb +3 -1
  102. data/spec/dummy/app/controllers/custom_authorizations_controller.rb +2 -0
  103. data/spec/dummy/app/controllers/full_protected_resources_controller.rb +4 -2
  104. data/spec/dummy/app/controllers/home_controller.rb +5 -3
  105. data/spec/dummy/app/controllers/metal_controller.rb +2 -0
  106. data/spec/dummy/app/controllers/semi_protected_resources_controller.rb +4 -2
  107. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  108. data/spec/dummy/app/models/user.rb +2 -0
  109. data/spec/dummy/config/application.rb +25 -1
  110. data/spec/dummy/config/environments/development.rb +2 -0
  111. data/spec/dummy/config/environments/production.rb +2 -0
  112. data/spec/dummy/config/environments/test.rb +3 -1
  113. data/spec/dummy/config/initializers/backtrace_silencers.rb +2 -0
  114. data/spec/dummy/config/initializers/doorkeeper.rb +5 -2
  115. data/spec/dummy/config/initializers/secret_token.rb +3 -1
  116. data/spec/dummy/config/initializers/session_store.rb +3 -1
  117. data/spec/dummy/config/initializers/wrap_parameters.rb +2 -0
  118. data/spec/dummy/config.ru +3 -1
  119. data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +17 -10
  120. data/spec/dummy/db/migrate/20170822064514_enable_pkce.rb +2 -0
  121. data/spec/dummy/db/schema.rb +1 -1
  122. data/spec/dummy/script/rails +5 -3
  123. data/spec/factories.rb +8 -6
  124. data/spec/generators/application_owner_generator_spec.rb +13 -26
  125. data/spec/generators/confidential_applications_generator_spec.rb +12 -28
  126. data/spec/generators/install_generator_spec.rb +17 -15
  127. data/spec/generators/migration_generator_spec.rb +13 -26
  128. data/spec/generators/pkce_generator_spec.rb +11 -26
  129. data/spec/generators/previous_refresh_token_generator_spec.rb +16 -29
  130. data/spec/generators/templates/routes.rb +2 -0
  131. data/spec/generators/views_generator_spec.rb +14 -12
  132. data/spec/grape/grape_integration_spec.rb +34 -32
  133. data/spec/helpers/doorkeeper/dashboard_helper_spec.rb +9 -7
  134. data/spec/lib/config_spec.rb +268 -99
  135. data/spec/lib/doorkeeper_spec.rb +3 -1
  136. data/spec/lib/models/expirable_spec.rb +22 -8
  137. data/spec/lib/models/reusable_spec.rb +40 -0
  138. data/spec/lib/models/revocable_spec.rb +8 -6
  139. data/spec/lib/models/scopes_spec.rb +27 -13
  140. data/spec/lib/models/secret_storable_spec.rb +135 -0
  141. data/spec/lib/oauth/authorization/uri_builder_spec.rb +17 -15
  142. data/spec/lib/oauth/authorization_code_request_spec.rb +34 -11
  143. data/spec/lib/oauth/base_request_spec.rb +20 -8
  144. data/spec/lib/oauth/base_response_spec.rb +3 -1
  145. data/spec/lib/oauth/client/credentials_spec.rb +24 -22
  146. data/spec/lib/oauth/client_credentials/creator_spec.rb +56 -10
  147. data/spec/lib/oauth/client_credentials/issuer_spec.rb +27 -18
  148. data/spec/lib/oauth/client_credentials/validation_spec.rb +20 -15
  149. data/spec/lib/oauth/client_credentials_integration_spec.rb +7 -5
  150. data/spec/lib/oauth/client_credentials_request_spec.rb +27 -21
  151. data/spec/lib/oauth/client_spec.rb +15 -13
  152. data/spec/lib/oauth/code_request_spec.rb +8 -6
  153. data/spec/lib/oauth/code_response_spec.rb +9 -7
  154. data/spec/lib/oauth/error_response_spec.rb +19 -11
  155. data/spec/lib/oauth/error_spec.rb +4 -2
  156. data/spec/lib/oauth/forbidden_token_response_spec.rb +7 -5
  157. data/spec/lib/oauth/helpers/scope_checker_spec.rb +68 -31
  158. data/spec/lib/oauth/helpers/unique_token_spec.rb +8 -6
  159. data/spec/lib/oauth/helpers/uri_checker_spec.rb +115 -95
  160. data/spec/lib/oauth/invalid_token_response_spec.rb +3 -1
  161. data/spec/lib/oauth/password_access_token_request_spec.rb +79 -30
  162. data/spec/lib/oauth/pre_authorization_spec.rb +80 -54
  163. data/spec/lib/oauth/refresh_token_request_spec.rb +36 -33
  164. data/spec/lib/oauth/scopes_spec.rb +63 -61
  165. data/spec/lib/oauth/token_request_spec.rb +79 -24
  166. data/spec/lib/oauth/token_response_spec.rb +40 -38
  167. data/spec/lib/oauth/token_spec.rb +60 -44
  168. data/spec/lib/request/strategy_spec.rb +3 -1
  169. data/spec/lib/secret_storing/base_spec.rb +60 -0
  170. data/spec/lib/secret_storing/bcrypt_spec.rb +49 -0
  171. data/spec/lib/secret_storing/plain_spec.rb +44 -0
  172. data/spec/lib/secret_storing/sha256_hash_spec.rb +48 -0
  173. data/spec/lib/server_spec.rb +16 -14
  174. data/spec/lib/stale_records_cleaner_spec.rb +17 -17
  175. data/spec/models/doorkeeper/access_grant_spec.rb +80 -15
  176. data/spec/models/doorkeeper/access_token_spec.rb +198 -73
  177. data/spec/models/doorkeeper/application_spec.rb +280 -288
  178. data/spec/requests/applications/applications_request_spec.rb +98 -66
  179. data/spec/requests/applications/authorized_applications_spec.rb +20 -18
  180. data/spec/requests/endpoints/authorization_spec.rb +25 -23
  181. data/spec/requests/endpoints/token_spec.rb +38 -36
  182. data/spec/requests/flows/authorization_code_errors_spec.rb +26 -24
  183. data/spec/requests/flows/authorization_code_spec.rb +193 -151
  184. data/spec/requests/flows/client_credentials_spec.rb +54 -52
  185. data/spec/requests/flows/implicit_grant_errors_spec.rb +10 -8
  186. data/spec/requests/flows/implicit_grant_spec.rb +27 -25
  187. data/spec/requests/flows/password_spec.rb +59 -55
  188. data/spec/requests/flows/refresh_token_spec.rb +45 -43
  189. data/spec/requests/flows/revoke_token_spec.rb +40 -54
  190. data/spec/requests/flows/skip_authorization_spec.rb +23 -21
  191. data/spec/requests/protected_resources/metal_spec.rb +7 -5
  192. data/spec/requests/protected_resources/private_api_spec.rb +35 -33
  193. data/spec/routing/custom_controller_routes_spec.rb +71 -65
  194. data/spec/routing/default_routes_spec.rb +22 -20
  195. data/spec/routing/scoped_routes_spec.rb +20 -18
  196. data/spec/spec_helper.rb +15 -13
  197. data/spec/spec_helper_integration.rb +3 -1
  198. data/spec/support/dependencies/factory_bot.rb +3 -1
  199. data/spec/support/doorkeeper_rspec.rb +3 -1
  200. data/spec/support/helpers/access_token_request_helper.rb +3 -1
  201. data/spec/support/helpers/authorization_request_helper.rb +4 -2
  202. data/spec/support/helpers/config_helper.rb +2 -0
  203. data/spec/support/helpers/model_helper.rb +3 -1
  204. data/spec/support/helpers/request_spec_helper.rb +5 -3
  205. data/spec/support/helpers/url_helper.rb +9 -7
  206. data/spec/support/http_method_shim.rb +4 -9
  207. data/spec/support/orm/active_record.rb +3 -1
  208. data/spec/support/shared/controllers_shared_context.rb +18 -16
  209. data/spec/support/shared/hashing_shared_context.rb +36 -0
  210. data/spec/support/shared/models_shared_examples.rb +12 -10
  211. data/spec/validators/redirect_uri_validator_spec.rb +74 -45
  212. data/spec/version/version_spec.rb +7 -5
  213. metadata +60 -26
  214. data/gemfiles/rails_4_2.gemfile +0 -13
  215. data/spec/dummy/config/initializers/new_framework_defaults.rb +0 -8
@@ -7,31 +7,46 @@ module Doorkeeper
7
7
  class Validator
8
8
  attr_reader :parsed_scopes, :scope_str
9
9
 
10
- def initialize(scope_str, server_scopes, application_scopes)
10
+ def initialize(scope_str, server_scopes, app_scopes, grant_type)
11
11
  @parsed_scopes = OAuth::Scopes.from_string(scope_str)
12
12
  @scope_str = scope_str
13
- @valid_scopes = valid_scopes(server_scopes, application_scopes)
13
+ @valid_scopes = valid_scopes(server_scopes, app_scopes)
14
+
15
+ if grant_type
16
+ @scopes_by_grant_type = Doorkeeper.configuration.scopes_by_grant_type[grant_type.to_sym]
17
+ end
14
18
  end
15
19
 
16
20
  def valid?
17
21
  scope_str.present? &&
18
22
  scope_str !~ /[\n\r\t]/ &&
19
- @valid_scopes.has_scopes?(parsed_scopes)
23
+ @valid_scopes.has_scopes?(parsed_scopes) &&
24
+ permitted_to_grant_type?
20
25
  end
21
26
 
22
27
  private
23
28
 
24
- def valid_scopes(server_scopes, application_scopes)
25
- if application_scopes.present?
26
- application_scopes
29
+ def valid_scopes(server_scopes, app_scopes)
30
+ if app_scopes.present?
31
+ app_scopes
27
32
  else
28
33
  server_scopes
29
34
  end
30
35
  end
36
+
37
+ def permitted_to_grant_type?
38
+ return true unless @scopes_by_grant_type
39
+
40
+ OAuth::Scopes.from_array(@scopes_by_grant_type)
41
+ .has_scopes?(parsed_scopes)
42
+ end
31
43
  end
32
44
 
33
- def self.valid?(scope_str, server_scopes, application_scopes = nil)
34
- Validator.new(scope_str, server_scopes, application_scopes).valid?
45
+ def self.valid?(scope_str:, server_scopes:, app_scopes: nil, grant_type: nil)
46
+ Validator.new(scope_str,
47
+ server_scopes,
48
+ app_scopes,
49
+ grant_type).valid?
35
50
  end
36
51
  end
37
52
  end
@@ -5,10 +5,22 @@ module Doorkeeper
5
5
  module Helpers
6
6
  module UniqueToken
7
7
  def self.generate(options = {})
8
- generator_method = options.delete(:generator) || SecureRandom.method(:hex)
8
+ # Access Token value must be 1*VSCHAR or
9
+ # 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="
10
+ #
11
+ # @see https://tools.ietf.org/html/rfc6749#appendix-A.12
12
+ # @see https://tools.ietf.org/html/rfc6750#section-2.1
13
+ #
14
+ generator_method = options.delete(:generator) || SecureRandom.method(self.generator_method)
9
15
  token_size = options.delete(:size) || 32
10
16
  generator_method.call(token_size)
11
17
  end
18
+
19
+ # Generator method for default generator class (SecureRandom)
20
+ #
21
+ def self.generator_method
22
+ Doorkeeper.configuration.default_generator_method
23
+ end
12
24
  end
13
25
  end
14
26
  end
@@ -1,11 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ipaddr"
4
+
3
5
  module Doorkeeper
6
+ module IPAddrLoopback
7
+ def loopback?
8
+ case @family
9
+ when Socket::AF_INET
10
+ @addr & 0xff000000 == 0x7f000000
11
+ when Socket::AF_INET6
12
+ @addr == 1
13
+ else
14
+ raise AddressFamilyError, "unsupported address family"
15
+ end
16
+ end
17
+ end
18
+
19
+ # For backward compatibility with old rubies
20
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.5.0")
21
+ IPAddr.send(:include, Doorkeeper::IPAddrLoopback)
22
+ end
23
+
4
24
  module OAuth
5
25
  module Helpers
6
26
  module URIChecker
7
27
  def self.valid?(url)
8
28
  return true if native_uri?(url)
29
+
9
30
  uri = as_uri(url)
10
31
  uri.fragment.nil? && !uri.host.nil? && !uri.scheme.nil?
11
32
  rescue URI::InvalidURIError
@@ -18,15 +39,29 @@ module Doorkeeper
18
39
 
19
40
  unless client_url.query.nil?
20
41
  return false unless query_matches?(url.query, client_url.query)
42
+
21
43
  # Clear out queries so rest of URI can be tested. This allows query
22
44
  # params to be in the request but order not mattering.
23
45
  client_url.query = nil
24
46
  end
25
47
 
48
+ # RFC8252, Paragraph 7.3
49
+ # @see https://tools.ietf.org/html/rfc8252#section-7.3
50
+ if loopback_uri?(url) && loopback_uri?(client_url)
51
+ url.port = nil
52
+ client_url.port = nil
53
+ end
54
+
26
55
  url.query = nil
27
56
  url == client_url
28
57
  end
29
58
 
59
+ def self.loopback_uri?(uri)
60
+ IPAddr.new(uri.host).loopback?
61
+ rescue IPAddr::Error
62
+ false
63
+ end
64
+
30
65
  def self.valid_for_authorization?(url, client_url)
31
66
  valid?(url) && client_url.split.any? { |other_url| matches?(url, other_url) }
32
67
  end
@@ -38,8 +73,9 @@ module Doorkeeper
38
73
  def self.query_matches?(query, client_query)
39
74
  return true if client_query.blank? && query.blank?
40
75
  return false if client_query.nil? || query.nil?
76
+
41
77
  # Will return true independent of query order
42
- client_query.split('&').sort == query.split('&').sort
78
+ client_query.split("&").sort == query.split("&").sort
43
79
  end
44
80
 
45
81
  def self.native_uri?(url)
@@ -22,6 +22,10 @@ module Doorkeeper
22
22
  @reason = attributes[:reason] || :unknown
23
23
  end
24
24
 
25
+ def status
26
+ :unauthorized
27
+ end
28
+
25
29
  def description
26
30
  scope = { scope: %i[doorkeeper errors messages invalid_token] }
27
31
  @description ||= I18n.translate @reason, scope
@@ -39,7 +43,7 @@ module Doorkeeper
39
43
  {
40
44
  expired: Doorkeeper::Errors::TokenExpired,
41
45
  revoked: Doorkeeper::Errors::TokenRevoked,
42
- unknown: Doorkeeper::Errors::TokenUnknown
46
+ unknown: Doorkeeper::Errors::TokenUnknown,
43
47
  }
44
48
  end
45
49
  end
@@ -32,7 +32,12 @@ module Doorkeeper
32
32
  client_scopes = client.try(:scopes)
33
33
  return true if scopes.blank?
34
34
 
35
- ScopeChecker.valid?(scopes.to_s, server.scopes, client_scopes)
35
+ ScopeChecker.valid?(
36
+ scope_str: scopes.to_s,
37
+ server_scopes: server.scopes,
38
+ app_scopes: client_scopes,
39
+ grant_type: grant_type
40
+ )
36
41
  end
37
42
 
38
43
  def validate_resource_owner
@@ -40,7 +45,7 @@ module Doorkeeper
40
45
  end
41
46
 
42
47
  def validate_client
43
- !parameters[:client_id] || !client.nil?
48
+ !parameters[:client_id] || client.present?
44
49
  end
45
50
  end
46
51
  end
@@ -50,7 +50,7 @@ module Doorkeeper
50
50
  response_type: response_type,
51
51
  scope: scope,
52
52
  client_name: client.name,
53
- status: I18n.t('doorkeeper.pre_authorization.status')
53
+ status: I18n.t("doorkeeper.pre_authorization.status"),
54
54
  }
55
55
  end
56
56
 
@@ -77,12 +77,17 @@ module Doorkeeper
77
77
  return true if scope.blank?
78
78
 
79
79
  Helpers::ScopeChecker.valid?(
80
- scope,
81
- server.scopes,
82
- client.application.scopes
80
+ scope_str: scope,
81
+ server_scopes: server.scopes,
82
+ app_scopes: client.application.scopes,
83
+ grant_type: grant_type
83
84
  )
84
85
  end
85
86
 
87
+ def grant_type
88
+ response_type == "code" ? AUTHORIZATION_CODE : IMPLICIT
89
+ end
90
+
86
91
  def validate_redirect_uri
87
92
  return false if redirect_uri.blank?
88
93
 
@@ -93,7 +98,8 @@ module Doorkeeper
93
98
  end
94
99
 
95
100
  def validate_code_challenge_method
96
- !code_challenge.present? || (code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/)
101
+ code_challenge.blank? ||
102
+ (code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/)
97
103
  end
98
104
  end
99
105
  end
@@ -58,7 +58,7 @@ module Doorkeeper
58
58
  resource_owner_id: refresh_token.resource_owner_id,
59
59
  scopes: scopes.to_s,
60
60
  expires_in: access_token_expires_in,
61
- use_refresh_token: true
61
+ use_refresh_token: true,
62
62
  }.tap do |attributes|
63
63
  if refresh_token_revoked_on_use?
64
64
  attributes[:previous_refresh_token] = refresh_token.refresh_token
@@ -99,7 +99,10 @@ module Doorkeeper
99
99
 
100
100
  def validate_scope
101
101
  if @original_scopes.present?
102
- ScopeChecker.valid?(@original_scopes, refresh_token.scopes)
102
+ ScopeChecker.valid?(
103
+ scope_str: @original_scopes,
104
+ server_scopes: refresh_token.scopes
105
+ )
103
106
  else
104
107
  true
105
108
  end
@@ -7,7 +7,7 @@ module Doorkeeper
7
7
  include Comparable
8
8
 
9
9
  def self.from_string(string)
10
- string ||= ''
10
+ string ||= ""
11
11
  new.tap do |scope|
12
12
  scope.add(*string.split)
13
13
  end
@@ -39,13 +39,15 @@ module Doorkeeper
39
39
  end
40
40
 
41
41
  def to_s
42
- @scopes.join(' ')
42
+ @scopes.join(" ")
43
43
  end
44
44
 
45
- def has_scopes?(scopes)
45
+ def scopes?(scopes)
46
46
  scopes.all? { |scope| exists?(scope) }
47
47
  end
48
48
 
49
+ alias has_scopes? scopes?
50
+
49
51
  def +(other)
50
52
  self.class.from_array(all + to_array(other))
51
53
  end
@@ -55,11 +55,11 @@ module Doorkeeper
55
55
  end
56
56
 
57
57
  def token_from_header(header, pattern)
58
- header.gsub pattern, ''
58
+ header.gsub pattern, ""
59
59
  end
60
60
 
61
61
  def match?(header, pattern)
62
- header && header.match(pattern)
62
+ header&.match(pattern)
63
63
  end
64
64
  end
65
65
  end
@@ -20,7 +20,17 @@ module Doorkeeper
20
20
  @error.blank?
21
21
  end
22
22
 
23
- def to_json
23
+ def error_response
24
+ return if @error.blank?
25
+
26
+ if @error == :invalid_token
27
+ OAuth::InvalidTokenResponse.from_access_token(authorized_token)
28
+ else
29
+ OAuth::ErrorResponse.new(name: @error)
30
+ end
31
+ end
32
+
33
+ def to_json(*)
24
34
  active? ? success_response : failure_response
25
35
  end
26
36
 
@@ -37,13 +47,29 @@ module Doorkeeper
37
47
  #
38
48
  # @see https://www.oauth.com/oauth2-servers/token-introspection-endpoint/
39
49
  #
50
+ # To prevent token scanning attacks, the endpoint MUST also require
51
+ # some form of authorization to access this endpoint, such as client
52
+ # authentication as described in OAuth 2.0 [RFC6749] or a separate
53
+ # OAuth 2.0 access token such as the bearer token described in OAuth
54
+ # 2.0 Bearer Token Usage [RFC6750].
55
+ #
40
56
  def authorize!
41
57
  # Requested client authorization
42
58
  if server.credentials
43
59
  @error = :invalid_client unless authorized_client
44
- else
60
+ elsif authorized_token
45
61
  # Requested bearer token authorization
46
- @error = :invalid_request unless authorized_token
62
+ #
63
+ # If the protected resource uses an OAuth 2.0 bearer token to authorize
64
+ # its call to the introspection endpoint and the token used for
65
+ # authorization does not contain sufficient privileges or is otherwise
66
+ # invalid for this request, the authorization server responds with an
67
+ # HTTP 401 code as described in Section 3 of OAuth 2.0 Bearer Token
68
+ # Usage [RFC6750].
69
+ #
70
+ @error = :invalid_token if authorized_token_matches_introspected? || !authorized_token.accessible?
71
+ else
72
+ @error = :invalid_request
47
73
  end
48
74
  end
49
75
 
@@ -60,14 +86,14 @@ module Doorkeeper
60
86
 
61
87
  # 2.2. Introspection Response
62
88
  def success_response
63
- {
89
+ customize_response(
64
90
  active: true,
65
91
  scope: @token.scopes_string,
66
92
  client_id: @token.try(:application).try(:uid),
67
93
  token_type: @token.token_type,
68
94
  exp: @token.expires_at.to_i,
69
95
  iat: @token.created_at.to_i
70
- }
96
+ )
71
97
  end
72
98
 
73
99
  # If the introspection call is properly authorized but the token is not
@@ -83,7 +109,7 @@ module Doorkeeper
83
109
  #
84
110
  def failure_response
85
111
  {
86
- active: false
112
+ active: false,
87
113
  }
88
114
  end
89
115
 
@@ -103,6 +129,25 @@ module Doorkeeper
103
129
  # * The token expired
104
130
  # * The token was issued to a different client than is making this request
105
131
  #
132
+ # Since resource servers using token introspection rely on the
133
+ # authorization server to determine the state of a token, the
134
+ # authorization server MUST perform all applicable checks against a
135
+ # token's state. For instance, these tests include the following:
136
+ #
137
+ # o If the token can expire, the authorization server MUST determine
138
+ # whether or not the token has expired.
139
+ # o If the token can be issued before it is able to be used, the
140
+ # authorization server MUST determine whether or not a token's valid
141
+ # period has started yet.
142
+ # o If the token can be revoked after it was issued, the authorization
143
+ # server MUST determine whether or not such a revocation has taken
144
+ # place.
145
+ # o If the token has been signed, the authorization server MUST
146
+ # validate the signature.
147
+ # o If the token can be used only at certain resource servers, the
148
+ # authorization server MUST determine whether or not the token can
149
+ # be used at the resource server making the introspection call.
150
+ #
106
151
  def active?
107
152
  if authorized_client
108
153
  valid_token? && authorized_for_client?
@@ -113,18 +158,39 @@ module Doorkeeper
113
158
 
114
159
  # Token can be valid only if it is not expired or revoked.
115
160
  def valid_token?
116
- @token.present? && @token.accessible?
161
+ @token&.accessible?
162
+ end
163
+
164
+ # RFC7662 Section 2.1
165
+ def authorized_token_matches_introspected?
166
+ authorized_token.token == @token&.token
117
167
  end
118
168
 
119
169
  # If token doesn't belong to some client, then it is public.
120
170
  # Otherwise in it required for token to be connected to the same client.
121
171
  def authorized_for_client?
122
- if @token.application.present?
172
+ if @token.application
123
173
  @token.application == authorized_client.application
124
174
  else
125
175
  true
126
176
  end
127
177
  end
178
+
179
+ # Allows to customize introspection response.
180
+ # Provides context (controller) and token for generating developer-specific
181
+ # response.
182
+ #
183
+ # @see https://tools.ietf.org/html/rfc7662#section-2.2
184
+ #
185
+ def customize_response(response)
186
+ customized_response = Doorkeeper.configuration.custom_introspection_response.call(
187
+ token,
188
+ server.context
189
+ )
190
+ return response if customized_response.blank?
191
+
192
+ response.merge(customized_response)
193
+ end
128
194
  end
129
195
  end
130
196
  end
@@ -11,12 +11,12 @@ module Doorkeeper
11
11
 
12
12
  def body
13
13
  {
14
- 'access_token' => token.token,
15
- 'token_type' => token.token_type,
16
- 'expires_in' => token.expires_in_seconds,
17
- 'refresh_token' => token.refresh_token,
18
- 'scope' => token.scopes_string,
19
- 'created_at' => token.created_at.to_i
14
+ "access_token" => token.plaintext_token,
15
+ "token_type" => token.token_type,
16
+ "expires_in" => token.expires_in_seconds,
17
+ "refresh_token" => token.plaintext_refresh_token,
18
+ "scope" => token.scopes_string,
19
+ "created_at" => token.created_at.to_i,
20
20
  }.reject { |_, value| value.blank? }
21
21
  end
22
22
 
@@ -26,9 +26,9 @@ module Doorkeeper
26
26
 
27
27
  def headers
28
28
  {
29
- 'Cache-Control' => 'no-store',
30
- 'Pragma' => 'no-cache',
31
- 'Content-Type' => 'application/json; charset=utf-8'
29
+ "Cache-Control" => "no-store",
30
+ "Pragma" => "no-cache",
31
+ "Content-Type" => "application/json; charset=utf-8",
32
32
  }
33
33
  end
34
34
  end
@@ -3,11 +3,11 @@
3
3
  module Doorkeeper
4
4
  module OAuth
5
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
6
+ AUTHORIZATION_CODE = "authorization_code",
7
+ IMPLICIT = "implicit",
8
+ PASSWORD = "password",
9
+ CLIENT_CREDENTIALS = "client_credentials",
10
+ REFRESH_TOKEN = "refresh_token",
11
11
  ].freeze
12
12
  end
13
13
  end
@@ -1,26 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  class AccessGrant < ActiveRecord::Base
3
- self.table_name = "#{table_name_prefix}oauth_access_grants#{table_name_suffix}".to_sym
5
+ self.table_name = "#{table_name_prefix}oauth_access_grants#{table_name_suffix}"
4
6
 
5
7
  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
8
 
13
- if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
14
- belongs_to_options[:optional] = true
15
- end
9
+ belongs_to :application, class_name: "Doorkeeper::Application",
10
+ optional: true, inverse_of: :access_grants
16
11
 
17
- belongs_to :application, belongs_to_options
12
+ validates :resource_owner_id,
13
+ :application_id,
14
+ :token,
15
+ :expires_in,
16
+ :redirect_uri,
17
+ presence: true
18
18
 
19
- validates :resource_owner_id, :application_id, :token, :expires_in, :redirect_uri, presence: true
20
19
  validates :token, uniqueness: true
21
20
 
22
21
  before_validation :generate_token, on: :create
23
22
 
23
+ # We keep a volatile copy of the raw token for initial communication
24
+ # The stored refresh_token may be mapped and not available in cleartext.
25
+ #
26
+ # Some strategies allow restoring stored secrets (e.g. symmetric encryption)
27
+ # while hashing strategies do not, so you cannot rely on this value
28
+ # returning a present value for persisted tokens.
29
+ def plaintext_token
30
+ if secret_strategy.allows_restoring_secrets?
31
+ secret_strategy.restore_secret(self, :token)
32
+ else
33
+ @raw_token
34
+ end
35
+ end
36
+
24
37
  private
25
38
 
26
39
  # Generates token value with UniqueToken class.
@@ -28,7 +41,8 @@ module Doorkeeper
28
41
  # @return [String] token value
29
42
  #
30
43
  def generate_token
31
- self.token = UniqueToken.generate
44
+ @raw_token = UniqueToken.generate
45
+ secret_strategy.store_secret(self, :token, @raw_token)
32
46
  end
33
47
  end
34
48
  end
@@ -1,20 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  class AccessToken < ActiveRecord::Base
3
- self.table_name = "#{table_name_prefix}oauth_access_tokens#{table_name_suffix}".to_sym
5
+ self.table_name = "#{table_name_prefix}oauth_access_tokens#{table_name_suffix}"
4
6
 
5
7
  include AccessTokenMixin
6
- include ActiveModel::MassAssignmentSecurity if defined?(::ProtectedAttributes)
7
-
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
15
- end
16
8
 
17
- belongs_to :application, belongs_to_options
9
+ belongs_to :application, class_name: "Doorkeeper::Application",
10
+ inverse_of: :access_tokens, optional: true
18
11
 
19
12
  validates :token, presence: true, uniqueness: true
20
13
  validates :refresh_token, uniqueness: true, if: :use_refresh_token?
@@ -41,7 +34,7 @@ module Doorkeeper
41
34
  end
42
35
 
43
36
  def self.refresh_token_revoked_on_use?
44
- column_names.include?('previous_refresh_token')
37
+ column_names.include?("previous_refresh_token")
45
38
  end
46
39
  end
47
40
  end