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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +825 -0
  3. data/CONTRIBUTING.md +11 -9
  4. data/Dangerfile +2 -2
  5. data/Dockerfile +29 -0
  6. data/Gemfile +2 -1
  7. data/NEWS.md +1 -814
  8. data/README.md +2 -2
  9. data/RELEASING.md +6 -5
  10. data/app/controllers/doorkeeper/applications_controller.rb +2 -0
  11. data/app/controllers/doorkeeper/authorizations_controller.rb +6 -1
  12. data/app/controllers/doorkeeper/tokens_controller.rb +32 -9
  13. data/app/validators/redirect_uri_validator.rb +19 -9
  14. data/app/views/doorkeeper/applications/_form.html.erb +0 -6
  15. data/app/views/doorkeeper/applications/show.html.erb +1 -1
  16. data/config/locales/en.yml +3 -1
  17. data/doorkeeper.gemspec +1 -1
  18. data/gemfiles/rails_5_0.gemfile +1 -0
  19. data/gemfiles/rails_5_1.gemfile +1 -0
  20. data/gemfiles/rails_5_2.gemfile +1 -0
  21. data/gemfiles/rails_6_0.gemfile +2 -1
  22. data/gemfiles/rails_master.gemfile +1 -0
  23. data/lib/doorkeeper/config/option.rb +13 -7
  24. data/lib/doorkeeper/config.rb +33 -3
  25. data/lib/doorkeeper/grape/helpers.rb +5 -1
  26. data/lib/doorkeeper/helpers/controller.rb +16 -3
  27. data/lib/doorkeeper/models/access_token_mixin.rb +43 -2
  28. data/lib/doorkeeper/oauth/authorization/code.rb +10 -8
  29. data/lib/doorkeeper/oauth/authorization/token.rb +1 -1
  30. data/lib/doorkeeper/oauth/client_credentials/creator.rb +14 -0
  31. data/lib/doorkeeper/oauth/code_response.rb +2 -2
  32. data/lib/doorkeeper/oauth/error_response.rb +1 -1
  33. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +18 -4
  34. data/lib/doorkeeper/oauth/nonstandard.rb +39 -0
  35. data/lib/doorkeeper/oauth/refresh_token_request.rb +8 -8
  36. data/lib/doorkeeper/oauth/token_introspection.rb +19 -12
  37. data/lib/doorkeeper/orm/active_record/access_grant.rb +1 -1
  38. data/lib/doorkeeper/orm/active_record/access_token.rb +2 -2
  39. data/lib/doorkeeper/orm/active_record/application.rb +7 -1
  40. data/lib/doorkeeper/orm/active_record.rb +17 -1
  41. data/lib/doorkeeper/stale_records_cleaner.rb +6 -2
  42. data/lib/doorkeeper/version.rb +2 -2
  43. data/lib/doorkeeper.rb +3 -0
  44. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +6 -6
  45. data/lib/generators/doorkeeper/templates/initializer.rb +57 -12
  46. data/lib/generators/doorkeeper/templates/migration.rb.erb +3 -0
  47. data/spec/controllers/applications_controller_spec.rb +93 -0
  48. data/spec/controllers/authorizations_controller_spec.rb +13 -0
  49. data/spec/controllers/tokens_controller_spec.rb +205 -37
  50. data/spec/dummy/config/application.rb +3 -1
  51. data/spec/dummy/config/initializers/doorkeeper.rb +54 -9
  52. data/spec/lib/config_spec.rb +11 -0
  53. data/spec/lib/oauth/client_credentials/creator_spec.rb +3 -0
  54. data/spec/lib/oauth/helpers/uri_checker_spec.rb +17 -2
  55. data/spec/lib/oauth/pre_authorization_spec.rb +0 -15
  56. data/spec/requests/flows/authorization_code_spec.rb +16 -4
  57. data/spec/requests/flows/revoke_token_spec.rb +19 -11
  58. data/spec/support/doorkeeper_rspec.rb +1 -1
  59. data/spec/validators/redirect_uri_validator_spec.rb +39 -14
  60. metadata +7 -13
  61. data/.coveralls.yml +0 -1
  62. data/.github/ISSUE_TEMPLATE.md +0 -25
  63. data/.github/PULL_REQUEST_TEMPLATE.md +0 -17
  64. data/.gitignore +0 -20
  65. data/.gitlab-ci.yml +0 -16
  66. data/.hound.yml +0 -3
  67. data/.rspec +0 -1
  68. data/.rubocop.yml +0 -50
  69. 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 = 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!
@@ -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 if authorized_token_matches_introspected? || !authorized_token.accessible?
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? && authorized_for_client?
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
- # 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
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.
@@ -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,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
- [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
@@ -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
11
+ MINOR = 2
12
12
  TINY = 0
13
- PRE = nil
13
+ PRE = "rc2"
14
14
 
15
15
  # Full version number
16
16
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
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
- 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
@@ -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://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator
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://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
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