doorkeeper 5.1.0 → 5.2.0

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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +1 -1
  3. data/CHANGELOG.md +843 -0
  4. data/CONTRIBUTING.md +11 -9
  5. data/Dangerfile +2 -2
  6. data/Dockerfile +29 -0
  7. data/Gemfile +2 -1
  8. data/NEWS.md +1 -814
  9. data/README.md +11 -3
  10. data/RELEASING.md +6 -5
  11. data/app/controllers/doorkeeper/application_controller.rb +1 -1
  12. data/app/controllers/doorkeeper/application_metal_controller.rb +2 -1
  13. data/app/controllers/doorkeeper/applications_controller.rb +2 -0
  14. data/app/controllers/doorkeeper/authorizations_controller.rb +14 -7
  15. data/app/controllers/doorkeeper/tokens_controller.rb +32 -9
  16. data/app/views/doorkeeper/applications/_form.html.erb +0 -6
  17. data/app/views/doorkeeper/applications/show.html.erb +1 -1
  18. data/config/locales/en.yml +8 -2
  19. data/doorkeeper.gemspec +9 -1
  20. data/gemfiles/rails_5_0.gemfile +1 -0
  21. data/gemfiles/rails_5_1.gemfile +1 -0
  22. data/gemfiles/rails_5_2.gemfile +1 -0
  23. data/gemfiles/rails_6_0.gemfile +2 -1
  24. data/gemfiles/rails_master.gemfile +1 -0
  25. data/lib/doorkeeper/config/option.rb +13 -7
  26. data/lib/doorkeeper/config.rb +88 -6
  27. data/lib/doorkeeper/errors.rb +13 -18
  28. data/lib/doorkeeper/grape/helpers.rb +5 -1
  29. data/lib/doorkeeper/helpers/controller.rb +20 -3
  30. data/lib/doorkeeper/models/access_token_mixin.rb +43 -2
  31. data/lib/doorkeeper/oauth/authorization/code.rb +11 -13
  32. data/lib/doorkeeper/oauth/authorization/token.rb +1 -1
  33. data/lib/doorkeeper/oauth/authorization_code_request.rb +18 -9
  34. data/lib/doorkeeper/oauth/base_request.rb +2 -0
  35. data/lib/doorkeeper/oauth/client_credentials/creator.rb +14 -0
  36. data/lib/doorkeeper/oauth/client_credentials/validation.rb +8 -0
  37. data/lib/doorkeeper/oauth/code_request.rb +5 -11
  38. data/lib/doorkeeper/oauth/code_response.rb +2 -2
  39. data/lib/doorkeeper/oauth/error_response.rb +1 -1
  40. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +18 -4
  41. data/lib/doorkeeper/oauth/invalid_request_response.rb +43 -0
  42. data/lib/doorkeeper/oauth/nonstandard.rb +39 -0
  43. data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -2
  44. data/lib/doorkeeper/oauth/pre_authorization.rb +70 -37
  45. data/lib/doorkeeper/oauth/refresh_token_request.rb +13 -10
  46. data/lib/doorkeeper/oauth/token_introspection.rb +23 -13
  47. data/lib/doorkeeper/oauth/token_request.rb +4 -18
  48. data/lib/doorkeeper/orm/active_record/access_grant.rb +1 -1
  49. data/lib/doorkeeper/orm/active_record/access_token.rb +2 -2
  50. data/lib/doorkeeper/orm/active_record/application.rb +8 -2
  51. data/lib/doorkeeper/orm/active_record/redirect_uri_validator.rb +61 -0
  52. data/lib/doorkeeper/orm/active_record.rb +19 -3
  53. data/lib/doorkeeper/request/authorization_code.rb +2 -0
  54. data/lib/doorkeeper/request.rb +6 -11
  55. data/lib/doorkeeper/server.rb +2 -6
  56. data/lib/doorkeeper/stale_records_cleaner.rb +6 -2
  57. data/lib/doorkeeper/version.rb +1 -1
  58. data/lib/doorkeeper.rb +4 -0
  59. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +6 -6
  60. data/lib/generators/doorkeeper/templates/initializer.rb +110 -33
  61. data/lib/generators/doorkeeper/templates/migration.rb.erb +4 -1
  62. data/spec/controllers/applications_controller_spec.rb +93 -0
  63. data/spec/controllers/authorizations_controller_spec.rb +140 -61
  64. data/spec/controllers/protected_resources_controller_spec.rb +3 -3
  65. data/spec/controllers/tokens_controller_spec.rb +205 -37
  66. data/spec/dummy/config/application.rb +3 -1
  67. data/spec/dummy/config/initializers/doorkeeper.rb +54 -9
  68. data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +1 -1
  69. data/spec/lib/config_spec.rb +43 -1
  70. data/spec/lib/oauth/authorization_code_request_spec.rb +11 -1
  71. data/spec/lib/oauth/base_request_spec.rb +33 -16
  72. data/spec/lib/oauth/client_credentials/creator_spec.rb +3 -0
  73. data/spec/lib/oauth/code_request_spec.rb +27 -28
  74. data/spec/lib/oauth/helpers/uri_checker_spec.rb +17 -2
  75. data/spec/lib/oauth/invalid_request_response_spec.rb +75 -0
  76. data/spec/lib/oauth/pre_authorization_spec.rb +76 -66
  77. data/spec/lib/oauth/refresh_token_request_spec.rb +1 -0
  78. data/spec/lib/oauth/token_request_spec.rb +20 -17
  79. data/spec/lib/server_spec.rb +0 -12
  80. data/spec/requests/endpoints/authorization_spec.rb +21 -5
  81. data/spec/requests/endpoints/token_spec.rb +1 -1
  82. data/spec/requests/flows/authorization_code_errors_spec.rb +1 -0
  83. data/spec/requests/flows/authorization_code_spec.rb +93 -27
  84. data/spec/requests/flows/client_credentials_spec.rb +38 -0
  85. data/spec/requests/flows/implicit_grant_errors_spec.rb +22 -10
  86. data/spec/requests/flows/implicit_grant_spec.rb +9 -8
  87. data/spec/requests/flows/password_spec.rb +37 -0
  88. data/spec/requests/flows/refresh_token_spec.rb +1 -1
  89. data/spec/requests/flows/revoke_token_spec.rb +19 -11
  90. data/spec/support/doorkeeper_rspec.rb +1 -1
  91. data/spec/support/helpers/request_spec_helper.rb +14 -2
  92. data/spec/validators/redirect_uri_validator_spec.rb +40 -15
  93. metadata +15 -13
  94. data/.coveralls.yml +0 -1
  95. data/.github/ISSUE_TEMPLATE.md +0 -25
  96. data/.github/PULL_REQUEST_TEMPLATE.md +0 -17
  97. data/.gitignore +0 -20
  98. data/.gitlab-ci.yml +0 -16
  99. data/.hound.yml +0 -3
  100. data/.rspec +0 -1
  101. data/.rubocop.yml +0 -50
  102. data/.travis.yml +0 -35
  103. data/app/validators/redirect_uri_validator.rb +0 -50
@@ -24,7 +24,7 @@ class CreateDoorkeeperTables < ActiveRecord::Migration<%= migration_version %>
24
24
  t.text :redirect_uri, null: false
25
25
  t.datetime :created_at, null: false
26
26
  t.datetime :revoked_at
27
- t.string :scopes
27
+ t.string :scopes, null: false, default: ''
28
28
  end
29
29
 
30
30
  add_index :oauth_access_grants, :token, unique: true
@@ -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: {
@@ -14,16 +14,14 @@ describe Doorkeeper::AuthorizationsController, "implicit grant flow" do
14
14
  end
15
15
  end
16
16
 
17
- def translated_error_message(key)
18
- I18n.translate key, scope: %i[doorkeeper errors messages]
19
- end
20
-
21
17
  let(:client) { FactoryBot.create :application }
22
18
  let(:user) { User.create!(name: "Joe", password: "sekret") }
23
- let(:access_token) { FactoryBot.build :access_token, resource_owner_id: user.id, application_id: client.id }
19
+ let(:access_token) { FactoryBot.build :access_token, resource_owner_id: user.id, application_id: client.id, scopes: "default" }
24
20
 
25
21
  before do
26
22
  Doorkeeper.configure do
23
+ default_scopes :default
24
+
27
25
  custom_access_token_expires_in(lambda do |context|
28
26
  context.grant_type == Doorkeeper::OAuth::IMPLICIT ? 1234 : nil
29
27
  end)
@@ -106,80 +104,146 @@ describe Doorkeeper::AuthorizationsController, "implicit grant flow" do
106
104
  end
107
105
 
108
106
  describe "POST #create with errors" do
109
- before do
110
- default_scopes_exist :public
107
+ context "when missing client_id" do
108
+ before do
109
+ post :create, params: {
110
+ client_id: "",
111
+ response_type: "token",
112
+ redirect_uri: client.redirect_uri,
113
+ }
114
+ end
111
115
 
112
- post :create, params: {
113
- client_id: client.uid,
114
- response_type: "token",
115
- scope: "invalid",
116
- redirect_uri: client.redirect_uri,
117
- }
118
- end
116
+ let(:response_json_body) { JSON.parse(response.body) }
119
117
 
120
- it "redirects after authorization" do
121
- expect(response).to be_redirect
122
- end
118
+ it "renders 400 error" do
119
+ expect(response.status).to eq 400
120
+ end
123
121
 
124
- it "redirects to client redirect uri" do
125
- expect(response.location).to match(/^#{client.redirect_uri}/)
126
- end
122
+ it "includes error name" do
123
+ expect(response_json_body["error"]).to eq("invalid_request")
124
+ end
127
125
 
128
- it "does not include access token in fragment" do
129
- expect(response.query_params["access_token"]).to be_nil
130
- end
126
+ it "includes error description" do
127
+ expect(response_json_body["error_description"]).to eq(
128
+ translated_invalid_request_error_message(:missing_param, :client_id)
129
+ )
130
+ end
131
131
 
132
- it "includes error in fragment" do
133
- expect(response.query_params["error"]).to eq("invalid_scope")
132
+ it "does not issue any access token" do
133
+ expect(Doorkeeper::AccessToken.all).to be_empty
134
+ end
134
135
  end
135
136
 
136
- it "includes error description in fragment" do
137
- expect(response.query_params["error_description"]).to eq(translated_error_message(:invalid_scope))
138
- end
137
+ context "when other error happens" do
138
+ before do
139
+ default_scopes_exist :public
140
+
141
+ post :create, params: {
142
+ client_id: client.uid,
143
+ response_type: "token",
144
+ scope: "invalid",
145
+ redirect_uri: client.redirect_uri,
146
+ }
147
+ end
148
+
149
+ it "redirects after authorization" do
150
+ expect(response).to be_redirect
151
+ end
152
+
153
+ it "redirects to client redirect uri" do
154
+ expect(response.location).to match(/^#{client.redirect_uri}/)
155
+ end
156
+
157
+ it "does not include access token in fragment" do
158
+ expect(response.query_params["access_token"]).to be_nil
159
+ end
160
+
161
+ it "includes error in fragment" do
162
+ expect(response.query_params["error"]).to eq("invalid_scope")
163
+ end
164
+
165
+ it "includes error description in fragment" do
166
+ expect(response.query_params["error_description"]).to eq(translated_error_message(:invalid_scope))
167
+ end
139
168
 
140
- it "does not issue any access token" do
141
- expect(Doorkeeper::AccessToken.all).to be_empty
169
+ it "does not issue any access token" do
170
+ expect(Doorkeeper::AccessToken.all).to be_empty
171
+ end
142
172
  end
143
173
  end
144
174
 
145
175
  describe "POST #create in API mode with errors" do
146
- before do
147
- allow(Doorkeeper.configuration).to receive(:api_only).and_return(true)
148
- default_scopes_exist :public
176
+ context "when missing client_id" do
177
+ before do
178
+ allow(Doorkeeper.configuration).to receive(:api_only).and_return(true)
149
179
 
150
- post :create, params: {
151
- client_id: client.uid,
152
- response_type: "token",
153
- scope: "invalid",
154
- redirect_uri: client.redirect_uri,
155
- }
156
- end
180
+ post :create, params: {
181
+ client_id: "",
182
+ response_type: "token",
183
+ redirect_uri: client.redirect_uri,
184
+ }
185
+ end
157
186
 
158
- let(:response_json_body) { JSON.parse(response.body) }
159
- let(:redirect_uri) { response_json_body["redirect_uri"] }
187
+ let(:response_json_body) { JSON.parse(response.body) }
160
188
 
161
- it "renders 400 error" do
162
- expect(response.status).to eq 400
163
- end
189
+ it "renders 400 error" do
190
+ expect(response.status).to eq 400
191
+ end
164
192
 
165
- it "includes correct redirect URI" do
166
- expect(redirect_uri).to match(/^#{client.redirect_uri}/)
167
- end
193
+ it "includes error name" do
194
+ expect(response_json_body["error"]).to eq("invalid_request")
195
+ end
168
196
 
169
- it "does not include access token in fragment" do
170
- expect(redirect_uri.match(/access_token=([a-f0-9]+)&?/)).to be_nil
171
- end
197
+ it "includes error description" do
198
+ expect(response_json_body["error_description"]).to eq(
199
+ translated_invalid_request_error_message(:missing_param, :client_id)
200
+ )
201
+ end
172
202
 
173
- it "includes error in redirect uri" do
174
- expect(redirect_uri.match(/error=([a-z_]+)&?/)[1]).to eq "invalid_scope"
203
+ it "does not issue any access token" do
204
+ expect(Doorkeeper::AccessToken.all).to be_empty
205
+ end
175
206
  end
176
207
 
177
- it "includes error description in redirect uri" do
178
- expect(redirect_uri.match(/error_description=(.+)&?/)[1]).to_not be_nil
179
- end
208
+ context "when other error happens" do
209
+ before do
210
+ allow(Doorkeeper.configuration).to receive(:api_only).and_return(true)
211
+ default_scopes_exist :public
180
212
 
181
- it "does not issue any access token" do
182
- expect(Doorkeeper::AccessToken.all).to be_empty
213
+ post :create, params: {
214
+ client_id: client.uid,
215
+ response_type: "token",
216
+ scope: "invalid",
217
+ redirect_uri: client.redirect_uri,
218
+ }
219
+ end
220
+
221
+ let(:response_json_body) { JSON.parse(response.body) }
222
+ let(:redirect_uri) { response_json_body["redirect_uri"] }
223
+
224
+ it "renders 400 error" do
225
+ expect(response.status).to eq 400
226
+ end
227
+
228
+ it "includes correct redirect URI" do
229
+ expect(redirect_uri).to match(/^#{client.redirect_uri}/)
230
+ end
231
+
232
+ it "does not include access token in fragment" do
233
+ expect(redirect_uri.match(/access_token=([a-f0-9]+)&?/)).to be_nil
234
+ end
235
+
236
+ it "includes error in redirect uri" do
237
+ expect(redirect_uri.match(/error=([a-z_]+)&?/)[1]).to eq "invalid_scope"
238
+ end
239
+
240
+ it "includes error description in redirect uri" do
241
+ expect(redirect_uri.match(/error_description=(.+)&?/)[1]).to_not be_nil
242
+ end
243
+
244
+ it "does not issue any access token" do
245
+ expect(Doorkeeper::AccessToken.all).to be_empty
246
+ end
183
247
  end
184
248
  end
185
249
 
@@ -368,7 +432,7 @@ describe Doorkeeper::AuthorizationsController, "implicit grant flow" do
368
432
  expect(json_response["redirect_uri"]).to eq(client.redirect_uri)
369
433
  expect(json_response["state"]).to be_nil
370
434
  expect(json_response["response_type"]).to eq("token")
371
- expect(json_response["scope"]).to eq("")
435
+ expect(json_response["scope"]).to eq("default")
372
436
  end
373
437
  end
374
438
 
@@ -445,12 +509,12 @@ describe Doorkeeper::AuthorizationsController, "implicit grant flow" do
445
509
  end
446
510
 
447
511
  it "includes error in body" do
448
- expect(response_json_body["error"]).to eq("unsupported_response_type")
512
+ expect(response_json_body["error"]).to eq("invalid_request")
449
513
  end
450
514
 
451
515
  it "includes error description in body" do
452
516
  expect(response_json_body["error_description"])
453
- .to eq(translated_error_message(:unsupported_response_type))
517
+ .to eq(translated_invalid_request_error_message(:missing_param, :client_id))
454
518
  end
455
519
 
456
520
  it "does not issue any token" do
@@ -513,6 +577,8 @@ describe Doorkeeper::AuthorizationsController, "implicit grant flow" do
513
577
 
514
578
  describe "authorize response memoization" do
515
579
  it "memoizes the result of the authorization" do
580
+ pre_auth = double(:pre_auth, authorizable?: true)
581
+ allow(controller).to receive(:pre_auth) { pre_auth }
516
582
  strategy = double(:strategy, authorize: true)
517
583
  expect(strategy).to receive(:authorize).once
518
584
  allow(controller).to receive(:strategy) { strategy }
@@ -524,4 +590,17 @@ describe Doorkeeper::AuthorizationsController, "implicit grant flow" do
524
590
  post :create
525
591
  end
526
592
  end
593
+
594
+ describe "strong parameters" do
595
+ it "ignores non-scalar scope parameter" do
596
+ get :new, params: {
597
+ client_id: client.uid,
598
+ response_type: "token",
599
+ redirect_uri: client.redirect_uri,
600
+ scope: { "0" => "profile" },
601
+ }
602
+
603
+ expect(response).to be_successful
604
+ end
605
+ end
527
606
  end
@@ -166,7 +166,7 @@ describe "doorkeeper authorize filter" do
166
166
  it "it renders a custom JSON response", token: :invalid do
167
167
  get :index, params: { access_token: token_string }
168
168
  expect(response.status).to eq 401
169
- expect(response.content_type).to eq("application/json")
169
+ expect(response.content_type).to include("application/json")
170
170
  expect(response.header["WWW-Authenticate"]).to match(/^Bearer/)
171
171
 
172
172
  expect(json_response).not_to be_nil
@@ -196,7 +196,7 @@ describe "doorkeeper authorize filter" do
196
196
  it "it renders a custom text response", token: :invalid do
197
197
  get :index, params: { access_token: token_string }
198
198
  expect(response.status).to eq 401
199
- expect(response.content_type).to eq("text/plain")
199
+ expect(response.content_type).to include("text/plain")
200
200
  expect(response.header["WWW-Authenticate"]).to match(/^Bearer/)
201
201
  expect(response.body).to eq("Unauthorized")
202
202
  end
@@ -246,7 +246,7 @@ describe "doorkeeper authorize filter" do
246
246
  it "renders a custom JSON response" do
247
247
  get :index, params: { access_token: token_string }
248
248
  expect(response.header).to_not include("WWW-Authenticate")
249
- expect(response.content_type).to eq("application/json")
249
+ expect(response.content_type).to include("application/json")
250
250
  expect(response.status).to eq 403
251
251
 
252
252
  expect(json_response).not_to be_nil