doorkeeper 5.1.0 → 5.2.0.rc2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +825 -0
- data/CONTRIBUTING.md +11 -9
- data/Dangerfile +2 -2
- data/Dockerfile +29 -0
- data/Gemfile +2 -1
- data/NEWS.md +1 -814
- data/README.md +2 -2
- data/RELEASING.md +6 -5
- data/app/controllers/doorkeeper/applications_controller.rb +2 -0
- data/app/controllers/doorkeeper/authorizations_controller.rb +6 -1
- data/app/controllers/doorkeeper/tokens_controller.rb +32 -9
- data/app/validators/redirect_uri_validator.rb +19 -9
- data/app/views/doorkeeper/applications/_form.html.erb +0 -6
- data/app/views/doorkeeper/applications/show.html.erb +1 -1
- data/config/locales/en.yml +3 -1
- data/doorkeeper.gemspec +1 -1
- data/gemfiles/rails_5_0.gemfile +1 -0
- data/gemfiles/rails_5_1.gemfile +1 -0
- data/gemfiles/rails_5_2.gemfile +1 -0
- data/gemfiles/rails_6_0.gemfile +2 -1
- data/gemfiles/rails_master.gemfile +1 -0
- data/lib/doorkeeper/config/option.rb +13 -7
- data/lib/doorkeeper/config.rb +33 -3
- data/lib/doorkeeper/grape/helpers.rb +5 -1
- data/lib/doorkeeper/helpers/controller.rb +16 -3
- data/lib/doorkeeper/models/access_token_mixin.rb +43 -2
- data/lib/doorkeeper/oauth/authorization/code.rb +10 -8
- data/lib/doorkeeper/oauth/authorization/token.rb +1 -1
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +14 -0
- data/lib/doorkeeper/oauth/code_response.rb +2 -2
- data/lib/doorkeeper/oauth/error_response.rb +1 -1
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +18 -4
- data/lib/doorkeeper/oauth/nonstandard.rb +39 -0
- data/lib/doorkeeper/oauth/refresh_token_request.rb +8 -8
- data/lib/doorkeeper/oauth/token_introspection.rb +19 -12
- data/lib/doorkeeper/orm/active_record/access_grant.rb +1 -1
- data/lib/doorkeeper/orm/active_record/access_token.rb +2 -2
- data/lib/doorkeeper/orm/active_record/application.rb +7 -1
- data/lib/doorkeeper/orm/active_record.rb +17 -1
- data/lib/doorkeeper/stale_records_cleaner.rb +6 -2
- data/lib/doorkeeper/version.rb +2 -2
- data/lib/doorkeeper.rb +3 -0
- data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +6 -6
- data/lib/generators/doorkeeper/templates/initializer.rb +57 -12
- data/lib/generators/doorkeeper/templates/migration.rb.erb +3 -0
- data/spec/controllers/applications_controller_spec.rb +93 -0
- data/spec/controllers/authorizations_controller_spec.rb +13 -0
- data/spec/controllers/tokens_controller_spec.rb +205 -37
- data/spec/dummy/config/application.rb +3 -1
- data/spec/dummy/config/initializers/doorkeeper.rb +54 -9
- data/spec/lib/config_spec.rb +11 -0
- data/spec/lib/oauth/client_credentials/creator_spec.rb +3 -0
- data/spec/lib/oauth/helpers/uri_checker_spec.rb +17 -2
- data/spec/lib/oauth/pre_authorization_spec.rb +0 -15
- data/spec/requests/flows/authorization_code_spec.rb +16 -4
- data/spec/requests/flows/revoke_token_spec.rb +19 -11
- data/spec/support/doorkeeper_rspec.rb +1 -1
- data/spec/validators/redirect_uri_validator_spec.rb +39 -14
- metadata +7 -13
- data/.coveralls.yml +0 -1
- data/.github/ISSUE_TEMPLATE.md +0 -25
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -17
- data/.gitignore +0 -20
- data/.gitlab-ci.yml +0 -16
- data/.hound.yml +0 -3
- data/.rspec +0 -1
- data/.rubocop.yml +0 -50
- data/.travis.yml +0 -35
|
@@ -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
|
|
19
|
-
@refresh_token
|
|
20
|
-
@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!
|
|
@@ -67,7 +67,7 @@ module Doorkeeper
|
|
|
67
67
|
# HTTP 401 code as described in Section 3 of OAuth 2.0 Bearer Token
|
|
68
68
|
# Usage [RFC6750].
|
|
69
69
|
#
|
|
70
|
-
@error = :invalid_token
|
|
70
|
+
@error = :invalid_token unless valid_authorized_token?
|
|
71
71
|
else
|
|
72
72
|
@error = :invalid_request
|
|
73
73
|
end
|
|
@@ -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,7 +149,7 @@ module Doorkeeper
|
|
|
150
149
|
#
|
|
151
150
|
def active?
|
|
152
151
|
if authorized_client
|
|
153
|
-
valid_token? &&
|
|
152
|
+
valid_token? && token_introspection_allowed?(auth_client: authorized_client.application)
|
|
154
153
|
else
|
|
155
154
|
valid_token?
|
|
156
155
|
end
|
|
@@ -161,19 +160,27 @@ module Doorkeeper
|
|
|
161
160
|
@token&.accessible?
|
|
162
161
|
end
|
|
163
162
|
|
|
163
|
+
def valid_authorized_token?
|
|
164
|
+
!authorized_token_matches_introspected? &&
|
|
165
|
+
authorized_token.accessible? &&
|
|
166
|
+
token_introspection_allowed?(auth_token: authorized_token)
|
|
167
|
+
end
|
|
168
|
+
|
|
164
169
|
# RFC7662 Section 2.1
|
|
165
170
|
def authorized_token_matches_introspected?
|
|
166
171
|
authorized_token.token == @token&.token
|
|
167
172
|
end
|
|
168
173
|
|
|
169
|
-
#
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
# config constraints for introspection in Doorkeeper.configuration.allow_token_introspection
|
|
175
|
+
def token_introspection_allowed?(auth_client: nil, auth_token: nil)
|
|
176
|
+
allow_introspection = Doorkeeper.configuration.allow_token_introspection
|
|
177
|
+
return allow_introspection unless allow_introspection.respond_to?(:call)
|
|
178
|
+
|
|
179
|
+
allow_introspection.call(
|
|
180
|
+
@token,
|
|
181
|
+
auth_client,
|
|
182
|
+
auth_token
|
|
183
|
+
)
|
|
177
184
|
end
|
|
178
185
|
|
|
179
186
|
# Allows to customize introspection response.
|
|
@@ -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,6 +60,12 @@ module Doorkeeper
|
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
def to_json(options)
|
|
64
|
+
serializable_hash(except: :secret)
|
|
65
|
+
.merge(secret: plaintext_secret)
|
|
66
|
+
.to_json(options)
|
|
67
|
+
end
|
|
68
|
+
|
|
63
69
|
private
|
|
64
70
|
|
|
65
71
|
def generate_uid
|
|
@@ -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
|
-
|
|
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
|
|
@@ -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/#{
|
|
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, "'#{
|
|
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)
|
data/lib/doorkeeper/version.rb
CHANGED
data/lib/doorkeeper.rb
CHANGED
|
@@ -52,6 +52,7 @@ require "doorkeeper/oauth/token"
|
|
|
52
52
|
require "doorkeeper/oauth/token_introspection"
|
|
53
53
|
require "doorkeeper/oauth/invalid_token_response"
|
|
54
54
|
require "doorkeeper/oauth/forbidden_token_response"
|
|
55
|
+
require "doorkeeper/oauth/nonstandard"
|
|
55
56
|
|
|
56
57
|
require "doorkeeper/secret_storing/base"
|
|
57
58
|
require "doorkeeper/secret_storing/plain"
|
|
@@ -80,6 +81,8 @@ require "doorkeeper/stale_records_cleaner"
|
|
|
80
81
|
|
|
81
82
|
require "doorkeeper/orm/active_record"
|
|
82
83
|
|
|
84
|
+
# Main Doorkeeper namespace.
|
|
85
|
+
#
|
|
83
86
|
module Doorkeeper
|
|
84
87
|
def self.authenticate(request, methods = Doorkeeper.configuration.access_token_methods)
|
|
85
88
|
OAuth::Token.authenticate(request, *methods)
|
|
@@ -17,12 +17,12 @@ module Doorkeeper
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def previous_refresh_token
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
@@ -65,7 +65,7 @@ Doorkeeper.configure do
|
|
|
65
65
|
# end
|
|
66
66
|
|
|
67
67
|
# Use a custom class for generating the access token.
|
|
68
|
-
# See https://
|
|
68
|
+
# See https://doorkeeper.gitbook.io/guides/configuration/other-configurations#custom-access-token-generator
|
|
69
69
|
#
|
|
70
70
|
# access_token_generator '::Doorkeeper::JWT'
|
|
71
71
|
|
|
@@ -160,7 +160,7 @@ Doorkeeper.configure do
|
|
|
160
160
|
|
|
161
161
|
# Define access token scopes for your provider
|
|
162
162
|
# For more information go to
|
|
163
|
-
# https://
|
|
163
|
+
# https://doorkeeper.gitbook.io/guides/ruby-on-rails/scopes
|
|
164
164
|
#
|
|
165
165
|
# default_scopes :public
|
|
166
166
|
# optional_scopes :write, :update
|
|
@@ -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.
|
|
@@ -305,7 +296,7 @@ Doorkeeper.configure do
|
|
|
305
296
|
# or add any other functionality.
|
|
306
297
|
#
|
|
307
298
|
# before_successful_authorization do |controller|
|
|
308
|
-
# Rails.logger.info(params.inspect)
|
|
299
|
+
# Rails.logger.info(controller.request.params.inspect)
|
|
309
300
|
# end
|
|
310
301
|
#
|
|
311
302
|
# after_successful_authorization do |controller|
|
|
@@ -323,6 +314,60 @@ 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.
|
|
318
|
+
# By default this configuration option allows to introspect a token by another
|
|
319
|
+
# token of the same application, OR to introspect the token that belongs to
|
|
320
|
+
# authorized client (from authenticated client) OR when token doesn't
|
|
321
|
+
# belong to any client (public token). Otherwise requester has no access to the
|
|
322
|
+
# introspection and it will return response as stated in the RFC.
|
|
323
|
+
#
|
|
324
|
+
# Block arguments:
|
|
325
|
+
#
|
|
326
|
+
# @param token [Doorkeeper::AccessToken]
|
|
327
|
+
# token to be introspected
|
|
328
|
+
#
|
|
329
|
+
# @param authorized_client [Doorkeeper::Application]
|
|
330
|
+
# authorized client (if request is authorized using Basic auth with
|
|
331
|
+
# Client Credentials for example)
|
|
332
|
+
#
|
|
333
|
+
# @param authorized_token [Doorkeeper::AccessToken]
|
|
334
|
+
# Bearer token used to authorize the request
|
|
335
|
+
#
|
|
336
|
+
# In case the block returns `nil` or `false` introspection responses with 401 status code
|
|
337
|
+
# when using authorized token to introspect, or you'll get 200 with { "active": false } body
|
|
338
|
+
# when using authorized client to introspect as stated in the
|
|
339
|
+
# RFC 7662 section 2.2. Introspection Response.
|
|
340
|
+
#
|
|
341
|
+
# Using with caution:
|
|
342
|
+
# Keep in mind that these three parameters pass to block can be nil as following case:
|
|
343
|
+
# `authorized_client` is nil if and only if `authorized_token` is present, and vice versa.
|
|
344
|
+
# `token` will be nil if and only if `authorized_token` is present.
|
|
345
|
+
# So remember to use `&` or check if it is present before calling method on
|
|
346
|
+
# them to make sure you doesn't get NoMethodError exception.
|
|
347
|
+
#
|
|
348
|
+
# You can define your custom check:
|
|
349
|
+
#
|
|
350
|
+
# allow_token_introspection do |token, authorized_client, authorized_token|
|
|
351
|
+
# if authorized_token
|
|
352
|
+
# # customize: require `introspection` scope
|
|
353
|
+
# authorized_token.application == token&.application ||
|
|
354
|
+
# authorized_token.scopes.include?("introspection")
|
|
355
|
+
# elsif token.application
|
|
356
|
+
# # `protected_resource` is a new database boolean column, for example
|
|
357
|
+
# authorized_client == token.application || authorized_client.protected_resource?
|
|
358
|
+
# else
|
|
359
|
+
# # public token (when token.application is nil, token doesn't belong to any application)
|
|
360
|
+
# true
|
|
361
|
+
# end
|
|
362
|
+
# end
|
|
363
|
+
#
|
|
364
|
+
# Or you can completely disable any token introspection:
|
|
365
|
+
#
|
|
366
|
+
# allow_token_introspection false
|
|
367
|
+
#
|
|
368
|
+
# If you need to block the request at all, then configure your routes.rb or web-server
|
|
369
|
+
# like nginx to forbid the request.
|
|
370
|
+
|
|
326
371
|
# WWW-Authenticate Realm (default "Doorkeeper").
|
|
327
372
|
#
|
|
328
373
|
# realm "Doorkeeper"
|
|
@@ -36,6 +36,9 @@ class CreateDoorkeeperTables < ActiveRecord::Migration<%= migration_version %>
|
|
|
36
36
|
|
|
37
37
|
create_table :oauth_access_tokens do |t|
|
|
38
38
|
t.references :resource_owner, index: true
|
|
39
|
+
|
|
40
|
+
# Remove `null: false` if you are planning to use Password
|
|
41
|
+
# Credentials Grant flow that doesn't require an application.
|
|
39
42
|
t.references :application, null: false
|
|
40
43
|
|
|
41
44
|
# If you use a custom token generator you may need to change this column
|
|
@@ -26,6 +26,10 @@ module Doorkeeper
|
|
|
26
26
|
|
|
27
27
|
expect(json_response).to include("id", "name", "uid", "secret", "redirect_uri", "scopes")
|
|
28
28
|
|
|
29
|
+
application = Application.last
|
|
30
|
+
secret_from_response = json_response["secret"]
|
|
31
|
+
expect(application.secret_matches?(secret_from_response)).to be_truthy
|
|
32
|
+
|
|
29
33
|
expect(json_response["name"]).to eq("Example")
|
|
30
34
|
expect(json_response["redirect_uri"]).to eq("https://example.com")
|
|
31
35
|
end
|
|
@@ -44,6 +48,21 @@ module Doorkeeper
|
|
|
44
48
|
expect(json_response).to include("errors")
|
|
45
49
|
end
|
|
46
50
|
|
|
51
|
+
it "returns validations on wrong create params (unspecified scheme)" do
|
|
52
|
+
expect do
|
|
53
|
+
post :create, params: {
|
|
54
|
+
doorkeeper_application: {
|
|
55
|
+
name: "Example",
|
|
56
|
+
redirect_uri: "app.com:80",
|
|
57
|
+
}, format: :json,
|
|
58
|
+
}
|
|
59
|
+
end.not_to(change { Doorkeeper::Application.count })
|
|
60
|
+
|
|
61
|
+
expect(response).to have_http_status(422)
|
|
62
|
+
|
|
63
|
+
expect(json_response).to include("errors")
|
|
64
|
+
end
|
|
65
|
+
|
|
47
66
|
it "returns application info" do
|
|
48
67
|
application = FactoryBot.create(:application, name: "Change me")
|
|
49
68
|
|
|
@@ -121,6 +140,72 @@ module Doorkeeper
|
|
|
121
140
|
end
|
|
122
141
|
|
|
123
142
|
context "when admin is authenticated" do
|
|
143
|
+
context "when application secrets are hashed" do
|
|
144
|
+
before do
|
|
145
|
+
allow(Doorkeeper.configuration).to receive(:application_secret_strategy).and_return(Doorkeeper::SecretStoring::Sha256Hash)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it "shows the application secret after creating a new application" do
|
|
149
|
+
expect do
|
|
150
|
+
post :create, params: {
|
|
151
|
+
doorkeeper_application: {
|
|
152
|
+
name: "Example",
|
|
153
|
+
redirect_uri: "https://example.com",
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
end.to change { Doorkeeper::Application.count }.by(1)
|
|
157
|
+
|
|
158
|
+
application = Application.last
|
|
159
|
+
|
|
160
|
+
secret_from_flash = flash[:application_secret]
|
|
161
|
+
expect(secret_from_flash).not_to be_empty
|
|
162
|
+
expect(application.secret_matches?(secret_from_flash)).to be_truthy
|
|
163
|
+
expect(response).to redirect_to(controller.main_app.oauth_application_url(application.id))
|
|
164
|
+
|
|
165
|
+
get :show, params: { id: application.id, format: :html }
|
|
166
|
+
|
|
167
|
+
# We don't know the application secret here (because its hashed) so we can not assert its text on the page
|
|
168
|
+
# Instead, we read it from the page and then check if it matches the application secret
|
|
169
|
+
code_element = %r{<code.*id="secret".*>(.*)<\/code>}.match(response.body)
|
|
170
|
+
secret_from_page = code_element[1]
|
|
171
|
+
|
|
172
|
+
expect(response.body).to have_selector("code#application_id", text: application.uid)
|
|
173
|
+
expect(response.body).to have_selector("code#secret")
|
|
174
|
+
expect(secret_from_page).not_to be_empty
|
|
175
|
+
expect(application.secret_matches?(secret_from_page)).to be_truthy
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "does not show an application secret when application did already exist" do
|
|
179
|
+
application = FactoryBot.create(:application)
|
|
180
|
+
get :show, params: { id: application.id, format: :html }
|
|
181
|
+
|
|
182
|
+
expect(response.body).to have_selector("code#application_id", text: application.uid)
|
|
183
|
+
expect(response.body).to have_selector("code#secret", text: "")
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "returns the application details in a json response" do
|
|
187
|
+
expect do
|
|
188
|
+
post :create, params: {
|
|
189
|
+
doorkeeper_application: {
|
|
190
|
+
name: "Example",
|
|
191
|
+
redirect_uri: "https://example.com",
|
|
192
|
+
}, format: :json,
|
|
193
|
+
}
|
|
194
|
+
end.to(change { Doorkeeper::Application.count })
|
|
195
|
+
|
|
196
|
+
expect(response).to be_successful
|
|
197
|
+
|
|
198
|
+
expect(json_response).to include("id", "name", "uid", "secret", "redirect_uri", "scopes")
|
|
199
|
+
|
|
200
|
+
application = Application.last
|
|
201
|
+
secret_from_response = json_response["secret"]
|
|
202
|
+
expect(application.secret_matches?(secret_from_response)).to be_truthy
|
|
203
|
+
|
|
204
|
+
expect(json_response["name"]).to eq("Example")
|
|
205
|
+
expect(json_response["redirect_uri"]).to eq("https://example.com")
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
124
209
|
render_views
|
|
125
210
|
|
|
126
211
|
before do
|
|
@@ -151,6 +236,14 @@ module Doorkeeper
|
|
|
151
236
|
expect(response).to be_redirect
|
|
152
237
|
end
|
|
153
238
|
|
|
239
|
+
it "shows application details" do
|
|
240
|
+
application = FactoryBot.create(:application)
|
|
241
|
+
get :show, params: { id: application.id, format: :html }
|
|
242
|
+
|
|
243
|
+
expect(response.body).to have_selector("code#application_id", text: application.uid)
|
|
244
|
+
expect(response.body).to have_selector("code#secret", text: application.plaintext_secret)
|
|
245
|
+
end
|
|
246
|
+
|
|
154
247
|
it "does not allow mass assignment of uid or secret" do
|
|
155
248
|
application = FactoryBot.create(:application)
|
|
156
249
|
put :update, params: {
|
|
@@ -524,4 +524,17 @@ describe Doorkeeper::AuthorizationsController, "implicit grant flow" do
|
|
|
524
524
|
post :create
|
|
525
525
|
end
|
|
526
526
|
end
|
|
527
|
+
|
|
528
|
+
describe "strong parameters" do
|
|
529
|
+
it "ignores non-scalar scope parameter" do
|
|
530
|
+
get :new, params: {
|
|
531
|
+
client_id: client.uid,
|
|
532
|
+
response_type: "token",
|
|
533
|
+
redirect_uri: client.redirect_uri,
|
|
534
|
+
scope: { "0" => "profile" },
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
expect(response).to be_successful
|
|
538
|
+
end
|
|
539
|
+
end
|
|
527
540
|
end
|