doorkeeper 5.1.2 → 5.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of doorkeeper might be problematic. Click here for more details.

Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +812 -0
  3. data/CONTRIBUTING.md +4 -9
  4. data/Dangerfile +1 -1
  5. data/Gemfile +2 -1
  6. data/NEWS.md +1 -819
  7. data/README.md +2 -2
  8. data/RELEASING.md +6 -5
  9. data/app/controllers/doorkeeper/applications_controller.rb +5 -3
  10. data/app/controllers/doorkeeper/authorized_applications_controller.rb +1 -1
  11. data/app/controllers/doorkeeper/tokens_controller.rb +18 -8
  12. data/app/validators/redirect_uri_validator.rb +19 -9
  13. data/app/views/doorkeeper/applications/_form.html.erb +0 -6
  14. data/app/views/doorkeeper/applications/show.html.erb +1 -1
  15. data/config/locales/en.yml +3 -1
  16. data/doorkeeper.gemspec +1 -1
  17. data/gemfiles/rails_5_0.gemfile +1 -0
  18. data/gemfiles/rails_5_1.gemfile +1 -0
  19. data/gemfiles/rails_5_2.gemfile +1 -0
  20. data/gemfiles/rails_6_0.gemfile +2 -1
  21. data/gemfiles/rails_master.gemfile +1 -0
  22. data/lib/doorkeeper.rb +3 -0
  23. data/lib/doorkeeper/config.rb +30 -3
  24. data/lib/doorkeeper/config/option.rb +13 -7
  25. data/lib/doorkeeper/grape/helpers.rb +5 -1
  26. data/lib/doorkeeper/helpers/controller.rb +16 -3
  27. data/lib/doorkeeper/oauth/authorization/code.rb +10 -8
  28. data/lib/doorkeeper/oauth/authorization/token.rb +1 -1
  29. data/lib/doorkeeper/oauth/code_response.rb +2 -2
  30. data/lib/doorkeeper/oauth/error_response.rb +1 -1
  31. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +18 -4
  32. data/lib/doorkeeper/oauth/nonstandard.rb +39 -0
  33. data/lib/doorkeeper/oauth/refresh_token_request.rb +8 -8
  34. data/lib/doorkeeper/oauth/token_introspection.rb +13 -12
  35. data/lib/doorkeeper/orm/active_record.rb +17 -1
  36. data/lib/doorkeeper/orm/active_record/access_grant.rb +1 -1
  37. data/lib/doorkeeper/orm/active_record/access_token.rb +2 -2
  38. data/lib/doorkeeper/orm/active_record/application.rb +5 -65
  39. data/lib/doorkeeper/stale_records_cleaner.rb +6 -2
  40. data/lib/doorkeeper/version.rb +3 -3
  41. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +6 -6
  42. data/lib/generators/doorkeeper/templates/initializer.rb +41 -9
  43. data/lib/generators/doorkeeper/templates/migration.rb.erb +3 -0
  44. data/spec/controllers/applications_controller_spec.rb +93 -0
  45. data/spec/controllers/protected_resources_controller_spec.rb +3 -3
  46. data/spec/controllers/tokens_controller_spec.rb +71 -3
  47. data/spec/dummy/config/application.rb +3 -1
  48. data/spec/dummy/config/initializers/doorkeeper.rb +27 -9
  49. data/spec/lib/config_spec.rb +11 -0
  50. data/spec/lib/oauth/helpers/uri_checker_spec.rb +17 -2
  51. data/spec/lib/oauth/pre_authorization_spec.rb +0 -15
  52. data/spec/models/doorkeeper/application_spec.rb +268 -373
  53. data/spec/requests/flows/authorization_code_spec.rb +16 -4
  54. data/spec/requests/flows/revoke_token_spec.rb +19 -11
  55. data/spec/support/doorkeeper_rspec.rb +1 -1
  56. data/spec/validators/redirect_uri_validator_spec.rb +39 -14
  57. metadata +7 -15
  58. data/.coveralls.yml +0 -1
  59. data/.github/ISSUE_TEMPLATE.md +0 -25
  60. data/.github/PULL_REQUEST_TEMPLATE.md +0 -17
  61. data/.gitignore +0 -20
  62. data/.gitlab-ci.yml +0 -16
  63. data/.hound.yml +0 -3
  64. data/.rspec +0 -1
  65. data/.rubocop.yml +0 -50
  66. data/.travis.yml +0 -35
@@ -12,10 +12,10 @@ module Doorkeeper
12
12
  end
13
13
 
14
14
  def issue_token
15
- @token ||= AccessGrant.create! access_grant_attributes
15
+ @token ||= AccessGrant.create!(access_grant_attributes)
16
16
  end
17
17
 
18
- def native_redirect
18
+ def oob_redirect
19
19
  { action: :show, code: token.plaintext_token }
20
20
  end
21
21
 
@@ -30,11 +30,13 @@ module Doorkeeper
30
30
  end
31
31
 
32
32
  def access_grant_attributes
33
- pkce_attributes.merge application_id: pre_auth.client.id,
34
- resource_owner_id: resource_owner.id,
35
- expires_in: authorization_code_expires_in,
36
- redirect_uri: pre_auth.redirect_uri,
37
- scopes: pre_auth.scopes.to_s
33
+ pkce_attributes.merge(
34
+ application_id: pre_auth.client.id,
35
+ resource_owner_id: resource_owner.id,
36
+ expires_in: authorization_code_expires_in,
37
+ redirect_uri: pre_auth.redirect_uri,
38
+ scopes: pre_auth.scopes.to_s
39
+ )
38
40
  end
39
41
 
40
42
  def pkce_attributes
@@ -46,7 +48,7 @@ module Doorkeeper
46
48
  }
47
49
  end
48
50
 
49
- # ensures firstly, if migration with additional pcke columns was
51
+ # Ensures firstly, if migration with additional PKCE columns was
50
52
  # generated and migrated
51
53
  def pkce_supported?
52
54
  Doorkeeper::AccessGrant.pkce_supported?
@@ -63,7 +63,7 @@ module Doorkeeper
63
63
  )
64
64
  end
65
65
 
66
- def native_redirect
66
+ def oob_redirect
67
67
  {
68
68
  controller: controller,
69
69
  action: :show,
@@ -18,8 +18,8 @@ module Doorkeeper
18
18
  end
19
19
 
20
20
  def redirect_uri
21
- if URIChecker.native_uri? pre_auth.redirect_uri
22
- auth.native_redirect
21
+ if URIChecker.oob_uri? pre_auth.redirect_uri
22
+ auth.oob_redirect
23
23
  elsif response_on_fragment
24
24
  Authorization::URIBuilder.uri_with_fragment(
25
25
  pre_auth.redirect_uri,
@@ -41,7 +41,7 @@ module Doorkeeper
41
41
 
42
42
  def redirectable?
43
43
  name != :invalid_redirect_uri && name != :invalid_client &&
44
- !URIChecker.native_uri?(@redirect_uri)
44
+ !URIChecker.oob_uri?(@redirect_uri)
45
45
  end
46
46
 
47
47
  def redirect_uri
@@ -25,10 +25,10 @@ module Doorkeeper
25
25
  module Helpers
26
26
  module URIChecker
27
27
  def self.valid?(url)
28
- return true if native_uri?(url)
28
+ return true if oob_uri?(url)
29
29
 
30
30
  uri = as_uri(url)
31
- uri.fragment.nil? && !uri.host.nil? && !uri.scheme.nil?
31
+ valid_scheme?(uri) && iff_host?(uri) && uri.fragment.nil? && uri.opaque.nil?
32
32
  rescue URI::InvalidURIError
33
33
  false
34
34
  end
@@ -78,8 +78,22 @@ module Doorkeeper
78
78
  client_query.split("&").sort == query.split("&").sort
79
79
  end
80
80
 
81
- def self.native_uri?(url)
82
- url == Doorkeeper.configuration.native_redirect_uri
81
+ def self.valid_scheme?(uri)
82
+ return false if uri.scheme.nil?
83
+
84
+ %w[localhost].include?(uri.scheme) == false
85
+ end
86
+
87
+ def self.hypertext_scheme?(uri)
88
+ %w[http https].include?(uri.scheme)
89
+ end
90
+
91
+ def self.iff_host?(uri)
92
+ !(hypertext_scheme?(uri) && uri.host.nil?)
93
+ end
94
+
95
+ def self.oob_uri?(uri)
96
+ NonStandard::IETF_WG_OAUTH2_OOB_METHODS.include?(uri)
83
97
  end
84
98
  end
85
99
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ module OAuth
5
+ class NonStandard
6
+ # These are not part of the OAuth 2 specification but are still in use by Google
7
+ # and in some other implementations. Native applications should use one of the
8
+ # approaches discussed in RFC8252. OOB is 'Out of Band'
9
+
10
+ # This value signals to the Google Authorization Server that the authorization
11
+ # code should be returned in the title bar of the browser, with the page text
12
+ # prompting the user to copy the code and paste it in the application.
13
+ # This is useful when the client (such as a Windows application) cannot listen
14
+ # on an HTTP port without significant client configuration.
15
+
16
+ # When you use this value, your application can then detect that the page has loaded, and can
17
+ # read the title of the HTML page to obtain the authorization code. It is then up to your
18
+ # application to close the browser window if you want to ensure that the user never sees the
19
+ # page that contains the authorization code. The mechanism for doing this varies from platform
20
+ # to platform.
21
+ #
22
+ # If your platform doesn't allow you to detect that the page has loaded or read the title of
23
+ # the page, you can have the user paste the code back to your application, as prompted by the
24
+ # text in the confirmation page that the OAuth 2.0 server generates.
25
+ IETF_WG_OAUTH2_OOB = "urn:ietf:wg:oauth:2.0:oob"
26
+
27
+ # This is identical to urn:ietf:wg:oauth:2.0:oob, but the text in the confirmation page that
28
+ # the OAuth 2.0 server generates won't instruct the user to copy the authorization code, but
29
+ # instead will simply ask the user to close the window.
30
+ #
31
+ # This is useful when your application reads the title of the HTML page (by checking window
32
+ # titles on the desktop, for example) to obtain the authorization code, but can't close the
33
+ # page on its own.
34
+ IETF_WG_OAUTH2_OOB_AUTO = "urn:ietf:wg:oauth:2.0:oob:auto"
35
+
36
+ IETF_WG_OAUTH2_OOB_METHODS = [IETF_WG_OAUTH2_OOB, IETF_WG_OAUTH2_OOB_AUTO].freeze
37
+ end
38
+ end
39
+ end
@@ -15,20 +15,20 @@ module Doorkeeper
15
15
  :server
16
16
 
17
17
  def initialize(server, refresh_token, credentials, parameters = {})
18
- @server = server
19
- @refresh_token = refresh_token
20
- @credentials = credentials
18
+ @server = server
19
+ @refresh_token = refresh_token
20
+ @credentials = credentials
21
21
  @original_scopes = parameters[:scope] || parameters[:scopes]
22
22
  @refresh_token_parameter = parameters[:refresh_token]
23
-
24
- if credentials
25
- @client = Application.by_uid_and_secret credentials.uid,
26
- credentials.secret
27
- end
23
+ @client = load_client(credentials) if credentials
28
24
  end
29
25
 
30
26
  private
31
27
 
28
+ def load_client(credentials)
29
+ Application.by_uid_and_secret(credentials.uid, credentials.secret)
30
+ end
31
+
32
32
  def before_successful_response
33
33
  refresh_token.transaction do
34
34
  refresh_token.lock!
@@ -80,8 +80,7 @@ module Doorkeeper
80
80
 
81
81
  # Bearer Token Authentication
82
82
  def authorized_token
83
- @authorized_token ||=
84
- OAuth::Token.authenticate(server.context.request, :from_bearer_authorization)
83
+ @authorized_token ||= Doorkeeper.authenticate(server.context.request)
85
84
  end
86
85
 
87
86
  # 2.2. Introspection Response
@@ -150,9 +149,9 @@ module Doorkeeper
150
149
  #
151
150
  def active?
152
151
  if authorized_client
153
- valid_token? && authorized_for_client?
152
+ valid_token? && token_introspection_allowed?(authorized_client.application)
154
153
  else
155
- valid_token?
154
+ valid_token? && token_introspection_allowed?(authorized_token&.application)
156
155
  end
157
156
  end
158
157
 
@@ -166,14 +165,16 @@ module Doorkeeper
166
165
  authorized_token.token == @token&.token
167
166
  end
168
167
 
169
- # If token doesn't belong to some client, then it is public.
170
- # Otherwise in it required for token to be connected to the same client.
171
- def authorized_for_client?
172
- if @token.application
173
- @token.application == authorized_client.application
174
- else
175
- true
176
- end
168
+ # config constraints for introspection in Doorkeeper.configuration.allow_token_introspection
169
+ def token_introspection_allowed?(client)
170
+ allow_introspection = Doorkeeper.configuration.allow_token_introspection
171
+ return allow_introspection unless allow_introspection.respond_to?(:call)
172
+
173
+ allow_introspection.call(
174
+ @token,
175
+ client,
176
+ authorized_token
177
+ )
177
178
  end
178
179
 
179
180
  # Allows to customize introspection response.
@@ -6,6 +6,14 @@ require "doorkeeper/orm/active_record/stale_records_cleaner"
6
6
 
7
7
  module Doorkeeper
8
8
  module Orm
9
+ # ActiveRecord ORM for Doorkeeper entity models.
10
+ # Consists of three main OAuth entities:
11
+ # * Access Token
12
+ # * Access Grant
13
+ # * Application (client)
14
+ #
15
+ # Do a lazy loading of all the required and configured stuff.
16
+ #
9
17
  module ActiveRecord
10
18
  def self.initialize_models!
11
19
  lazy_load do
@@ -14,7 +22,7 @@ module Doorkeeper
14
22
  require "doorkeeper/orm/active_record/application"
15
23
 
16
24
  if Doorkeeper.configuration.active_record_options[:establish_connection]
17
- [Doorkeeper::AccessGrant, Doorkeeper::AccessToken, Doorkeeper::Application].each do |model|
25
+ Doorkeeper::Orm::ActiveRecord.models.each do |model|
18
26
  options = Doorkeeper.configuration.active_record_options[:establish_connection]
19
27
  model.establish_connection(options)
20
28
  end
@@ -33,6 +41,14 @@ module Doorkeeper
33
41
  def self.lazy_load(&block)
34
42
  ActiveSupport.on_load(:active_record, {}, &block)
35
43
  end
44
+
45
+ def self.models
46
+ [
47
+ Doorkeeper::AccessGrant,
48
+ Doorkeeper::AccessToken,
49
+ Doorkeeper::Application,
50
+ ]
51
+ end
36
52
  end
37
53
  end
38
54
  end
@@ -16,7 +16,7 @@ module Doorkeeper
16
16
  :redirect_uri,
17
17
  presence: true
18
18
 
19
- validates :token, uniqueness: true
19
+ validates :token, uniqueness: { case_sensitive: true }
20
20
 
21
21
  before_validation :generate_token, on: :create
22
22
 
@@ -9,8 +9,8 @@ module Doorkeeper
9
9
  belongs_to :application, class_name: "Doorkeeper::Application",
10
10
  inverse_of: :access_tokens, optional: true
11
11
 
12
- validates :token, presence: true, uniqueness: true
13
- validates :refresh_token, uniqueness: true, if: :use_refresh_token?
12
+ validates :token, presence: true, uniqueness: { case_sensitive: true }
13
+ validates :refresh_token, uniqueness: { case_sensitive: true }, if: :use_refresh_token?
14
14
 
15
15
  # @attr_writer [Boolean, nil] use_refresh_token
16
16
  # indicates the possibility of using refresh token
@@ -10,7 +10,7 @@ module Doorkeeper
10
10
  has_many :access_tokens, dependent: :delete_all, class_name: "Doorkeeper::AccessToken"
11
11
 
12
12
  validates :name, :secret, :uid, presence: true
13
- validates :uid, uniqueness: true
13
+ validates :uid, uniqueness: { case_sensitive: true }
14
14
  validates :redirect_uri, redirect_uri: true
15
15
  validates :confidential, inclusion: { in: [true, false] }
16
16
 
@@ -60,38 +60,10 @@ module Doorkeeper
60
60
  end
61
61
  end
62
62
 
63
- # Represents client as set of it's attributes in JSON format.
64
- # This is the right way how we want to override ActiveRecord #to_json.
65
- #
66
- # Respects privacy settings and serializes minimum set of attributes
67
- # for public/private clients and full set for authorized owners.
68
- #
69
- # @return [Hash] entity attributes for JSON
70
- #
71
- def as_json(options = {})
72
- # if application belongs to some owner we need to check if it's the same as
73
- # the one passed in the options or check if we render the client as an owner
74
- if (respond_to?(:owner) && owner && owner == options[:current_resource_owner]) ||
75
- options[:as_owner]
76
- # Owners can see all the client attributes, fallback to ActiveModel serialization
77
- super
78
- else
79
- # if application has no owner or it's owner doesn't match one from the options
80
- # we render only minimum set of attributes that could be exposed to a public
81
- only = extract_serializable_attributes(options)
82
- super(options.merge(only: only))
83
- end
84
- end
85
-
86
- # We need to hook into this method to allow serializing plan-text secrets
87
- # when secrets hashing enabled.
88
- #
89
- # @param key [String] attribute name
90
- #
91
- def read_attribute_for_serialization(key)
92
- return super unless key.to_s == "secret"
93
-
94
- plaintext_secret || secret
63
+ def to_json(options)
64
+ serializable_hash(except: :secret)
65
+ .merge(secret: plaintext_secret)
66
+ .to_json(options)
95
67
  end
96
68
 
97
69
  private
@@ -118,37 +90,5 @@ module Doorkeeper
118
90
  def enforce_scopes?
119
91
  Doorkeeper.configuration.enforce_configured_scopes?
120
92
  end
121
-
122
- # Helper method to extract collection of serializable attribute names
123
- # considering serialization options (like `only`, `except` and so on).
124
- #
125
- # @param options [Hash] serialization options
126
- #
127
- # @return [Array<String>]
128
- # collection of attributes to be serialized using #as_json
129
- #
130
- def extract_serializable_attributes(options = {})
131
- opts = options.try(:dup) || {}
132
- only = Array.wrap(opts[:only]).map(&:to_s)
133
-
134
- only = if only.blank?
135
- serializable_attributes
136
- else
137
- only & serializable_attributes
138
- end
139
-
140
- only -= Array.wrap(opts[:except]).map(&:to_s) if opts.key?(:except)
141
- only.uniq
142
- end
143
-
144
- # Collection of attributes that could be serialized for public.
145
- # Override this method if you need additional attributes to be serialized.
146
- #
147
- # @return [Array<String>] collection of serializable attributes
148
- def serializable_attributes
149
- attributes = %w[id name created_at]
150
- attributes << "uid" unless confidential?
151
- attributes
152
- end
153
93
  end
154
94
  end
@@ -5,12 +5,16 @@ module Doorkeeper
5
5
  CLEANER_CLASS = "StaleRecordsCleaner"
6
6
 
7
7
  def self.for(base_scope)
8
- orm_adapter = "doorkeeper/orm/#{Doorkeeper.configuration.orm}".classify
8
+ orm_adapter = "doorkeeper/orm/#{configured_orm}".classify
9
9
 
10
10
  orm_cleaner = "#{orm_adapter}::#{CLEANER_CLASS}".constantize
11
11
  orm_cleaner.new(base_scope)
12
12
  rescue NameError
13
- raise Doorkeeper::Errors::NoOrmCleaner, "'#{Doorkeeper.configuration.orm}' ORM has no cleaner!"
13
+ raise Doorkeeper::Errors::NoOrmCleaner, "'#{configured_orm}' ORM has no cleaner!"
14
+ end
15
+
16
+ def self.configured_orm
17
+ Doorkeeper.configuration.orm
14
18
  end
15
19
 
16
20
  def self.new(base_scope)
@@ -8,9 +8,9 @@ module Doorkeeper
8
8
  module VERSION
9
9
  # Semantic versioning
10
10
  MAJOR = 5
11
- MINOR = 1
12
- TINY = 2
13
- PRE = nil
11
+ MINOR = 2
12
+ TINY = 0
13
+ PRE = "rc1"
14
14
 
15
15
  # Full version number
16
16
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -17,12 +17,12 @@ module Doorkeeper
17
17
  end
18
18
 
19
19
  def previous_refresh_token
20
- if no_previous_refresh_token_column?
21
- migration_template(
22
- "add_previous_refresh_token_to_access_tokens.rb.erb",
23
- "db/migrate/add_previous_refresh_token_to_access_tokens.rb"
24
- )
25
- end
20
+ return unless no_previous_refresh_token_column?
21
+
22
+ migration_template(
23
+ "add_previous_refresh_token_to_access_tokens.rb.erb",
24
+ "db/migrate/add_previous_refresh_token_to_access_tokens.rb"
25
+ )
26
26
  end
27
27
 
28
28
  private
@@ -196,15 +196,6 @@ Doorkeeper.configure do
196
196
  #
197
197
  # access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param
198
198
 
199
- # Change the native redirect uri for client apps
200
- # When clients register with the following redirect uri, they won't be redirected to
201
- # any server and the authorizationcode will be displayed within the provider
202
- # The value can be any string. Use nil to disable this feature. When disabled, clients
203
- # must providea valid URL
204
- # (Similar behaviour: https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi)
205
- #
206
- # native_redirect_uri 'urn:ietf:wg:oauth:2.0:oob'
207
-
208
199
  # Forces the usage of the HTTPS protocol in non-native redirect uris (enabled
209
200
  # by default in non-development environments). OAuth2 delegates security in
210
201
  # communication to the HTTPS protocol so it is wise to keep this enabled.
@@ -323,6 +314,47 @@ Doorkeeper.configure do
323
314
  # client.superapp? or resource_owner.admin?
324
315
  # end
325
316
 
317
+ # Configure custom constraints for the Token Introspection request. By default
318
+ # this configuration option allows to introspect the token that belongs to authorized
319
+ # client (from Bearer token or from authenticated client) OR when token doesn't
320
+ # belong to any client (public token). Otherwise requester has no access to the
321
+ # introspection and it will return response as stated in the RFC.
322
+ #
323
+ # You can define your custom check:
324
+ #
325
+ # allow_token_introspection do |token, authorized_client, _authorized_token|
326
+ # if token.application
327
+ # # `protected_resource` is a new database boolean column, for example
328
+ # token.application == authorized_client || authorized_client.protected_resource?
329
+ # else
330
+ # true
331
+ # end
332
+ # end
333
+ #
334
+ # Block arguments:
335
+ #
336
+ # @param token [Doorkeeper::AccessToken]
337
+ # token to be introspected
338
+ #
339
+ # @param authorized_client [Doorkeeper::Application]
340
+ # authorized client (if request is authorized using Basic auth with
341
+ # Client Credentials for example)
342
+ #
343
+ # @param authorized_token [Doorkeeper::AccessToken]
344
+ # Bearer token used to authorize the request
345
+ #
346
+ # Keep in mind, that in case the block returns `nil` or `false` introspection response
347
+ # doesn't have 401 status code and some descriptive body, you'll get 200 with
348
+ # { "active": false } body as stated in the RFC 7662 section 2.2. Introspection Response.
349
+ #
350
+ # You can completely disable any token introspection:
351
+ #
352
+ # allow_token_introspection false
353
+ #
354
+ # In such case every request for token introspection will get { "active": false } response.
355
+ # If you need to block the request at all, then configure your routes.rb or web-server
356
+ # like nginx to forbid the request.
357
+
326
358
  # WWW-Authenticate Realm (default "Doorkeeper").
327
359
  #
328
360
  # realm "Doorkeeper"