doorkeeper 5.0.2 → 5.1.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 (94) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +17 -4
  3. data/Appraisals +29 -3
  4. data/Dangerfile +5 -2
  5. data/Gemfile +14 -4
  6. data/NEWS.md +64 -19
  7. data/README.md +68 -487
  8. data/app/controllers/doorkeeper/token_info_controller.rb +1 -1
  9. data/app/controllers/doorkeeper/tokens_controller.rb +7 -7
  10. data/app/views/doorkeeper/applications/show.html.erb +1 -1
  11. data/app/views/layouts/doorkeeper/admin.html.erb +5 -3
  12. data/bin/console +15 -0
  13. data/doorkeeper.gemspec +3 -2
  14. data/gemfiles/rails_4_2.gemfile +8 -4
  15. data/gemfiles/rails_5_0.gemfile +9 -5
  16. data/gemfiles/rails_5_1.gemfile +9 -5
  17. data/gemfiles/rails_5_2.gemfile +9 -5
  18. data/gemfiles/rails_6_0.gemfile +16 -0
  19. data/gemfiles/rails_master.gemfile +8 -9
  20. data/lib/doorkeeper/config.rb +164 -11
  21. data/lib/doorkeeper/helpers/controller.rb +3 -2
  22. data/lib/doorkeeper/models/access_grant_mixin.rb +16 -1
  23. data/lib/doorkeeper/models/access_token_mixin.rb +54 -10
  24. data/lib/doorkeeper/models/application_mixin.rb +42 -1
  25. data/lib/doorkeeper/models/concerns/expirable.rb +3 -2
  26. data/lib/doorkeeper/models/concerns/reusable.rb +19 -0
  27. data/lib/doorkeeper/models/concerns/scopes.rb +5 -1
  28. data/lib/doorkeeper/models/concerns/secret_storable.rb +106 -0
  29. data/lib/doorkeeper/oauth/authorization/code.rb +1 -1
  30. data/lib/doorkeeper/oauth/authorization/token.rb +4 -2
  31. data/lib/doorkeeper/oauth/authorization_code_request.rb +1 -1
  32. data/lib/doorkeeper/oauth/client.rb +1 -1
  33. data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -3
  34. data/lib/doorkeeper/oauth/code_response.rb +2 -2
  35. data/lib/doorkeeper/oauth/error_response.rb +5 -1
  36. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +23 -8
  37. data/lib/doorkeeper/oauth/helpers/unique_token.rb +12 -1
  38. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +32 -0
  39. data/lib/doorkeeper/oauth/invalid_token_response.rb +4 -0
  40. data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -2
  41. data/lib/doorkeeper/oauth/pre_authorization.rb +8 -3
  42. data/lib/doorkeeper/oauth/refresh_token_request.rb +4 -1
  43. data/lib/doorkeeper/oauth/token_introspection.rb +72 -6
  44. data/lib/doorkeeper/oauth/token_response.rb +2 -2
  45. data/lib/doorkeeper/orm/active_record/access_grant.rb +23 -2
  46. data/lib/doorkeeper/orm/active_record/application.rb +20 -2
  47. data/lib/doorkeeper/secret_storing/base.rb +63 -0
  48. data/lib/doorkeeper/secret_storing/bcrypt.rb +59 -0
  49. data/lib/doorkeeper/secret_storing/plain.rb +33 -0
  50. data/lib/doorkeeper/secret_storing/sha256_hash.rb +25 -0
  51. data/lib/doorkeeper/version.rb +3 -3
  52. data/lib/doorkeeper.rb +7 -0
  53. data/lib/generators/doorkeeper/templates/initializer.rb +90 -8
  54. data/spec/controllers/application_metal_controller_spec.rb +18 -4
  55. data/spec/controllers/authorizations_controller_spec.rb +3 -3
  56. data/spec/controllers/token_info_controller_spec.rb +1 -1
  57. data/spec/controllers/tokens_controller_spec.rb +85 -41
  58. data/spec/dummy/app/controllers/application_controller.rb +1 -1
  59. data/spec/dummy/config/application.rb +12 -1
  60. data/spec/factories.rb +3 -3
  61. data/spec/lib/config_spec.rb +168 -0
  62. data/spec/lib/models/expirable_spec.rb +12 -0
  63. data/spec/lib/models/reusable_spec.rb +40 -0
  64. data/spec/lib/models/scopes_spec.rb +13 -1
  65. data/spec/lib/models/secret_storable_spec.rb +113 -0
  66. data/spec/lib/oauth/authorization_code_request_spec.rb +18 -1
  67. data/spec/lib/oauth/base_request_spec.rb +7 -7
  68. data/spec/lib/oauth/client_credentials/creator_spec.rb +51 -7
  69. data/spec/lib/oauth/client_credentials/validation_spec.rb +3 -0
  70. data/spec/lib/oauth/error_response_spec.rb +7 -1
  71. data/spec/lib/oauth/helpers/scope_checker_spec.rb +52 -17
  72. data/spec/lib/oauth/helpers/uri_checker_spec.rb +20 -2
  73. data/spec/lib/oauth/password_access_token_request_spec.rb +43 -12
  74. data/spec/lib/oauth/pre_authorization_spec.rb +24 -0
  75. data/spec/lib/oauth/token_request_spec.rb +16 -1
  76. data/spec/lib/oauth/token_response_spec.rb +13 -13
  77. data/spec/lib/oauth/token_spec.rb +14 -0
  78. data/spec/lib/secret_storing/base_spec.rb +60 -0
  79. data/spec/lib/secret_storing/bcrypt_spec.rb +49 -0
  80. data/spec/lib/secret_storing/plain_spec.rb +44 -0
  81. data/spec/lib/secret_storing/sha256_hash_spec.rb +48 -0
  82. data/spec/models/doorkeeper/access_grant_spec.rb +61 -0
  83. data/spec/models/doorkeeper/access_token_spec.rb +123 -0
  84. data/spec/models/doorkeeper/application_spec.rb +56 -0
  85. data/spec/requests/flows/authorization_code_spec.rb +41 -1
  86. data/spec/requests/flows/client_credentials_spec.rb +2 -2
  87. data/spec/requests/flows/implicit_grant_spec.rb +1 -1
  88. data/spec/requests/flows/password_spec.rb +7 -5
  89. data/spec/requests/flows/revoke_token_spec.rb +14 -30
  90. data/spec/routing/custom_controller_routes_spec.rb +4 -0
  91. data/spec/spec_helper.rb +2 -1
  92. data/spec/support/ruby_2_6_rails_4_2_patch.rb +14 -0
  93. data/spec/support/shared/hashing_shared_context.rb +36 -0
  94. metadata +59 -22
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ::Doorkeeper::SecretStoring::Base do
6
+ let(:instance) { double('instance', token: 'foo') }
7
+ subject { described_class }
8
+
9
+ describe '#transform_secret' do
10
+ it 'raises' do
11
+ expect { subject.transform_secret('foo') }.to raise_error(NotImplementedError)
12
+ end
13
+ end
14
+
15
+ describe '#store_secret' do
16
+ it 'sends to response of #transform_secret to the instance' do
17
+ expect(described_class)
18
+ .to receive(:transform_secret).with('bar')
19
+ .and_return 'bar+transform'
20
+
21
+ expect(instance).to receive(:token=).with 'bar+transform'
22
+ result = subject.store_secret instance, :token, 'bar'
23
+ expect(result).to eq 'bar+transform'
24
+ end
25
+ end
26
+
27
+ describe '#restore_secret' do
28
+ it 'raises' do
29
+ expect { subject.restore_secret(subject, :token) }.to raise_error(NotImplementedError)
30
+ end
31
+ end
32
+
33
+ describe '#allows_restoring_secrets?' do
34
+ it 'does not allow it' do
35
+ expect(subject.allows_restoring_secrets?).to eq false
36
+ end
37
+ end
38
+
39
+ describe 'validate_for' do
40
+ it 'allows for valid model' do
41
+ expect(subject.validate_for(:application)).to eq true
42
+ expect(subject.validate_for(:token)).to eq true
43
+ end
44
+
45
+ it 'raises for invalid model' do
46
+ expect { subject.validate_for(:wat) }.to raise_error(ArgumentError, /can not be used for wat/)
47
+ end
48
+ end
49
+
50
+ describe 'secret_matches?' do
51
+ before do
52
+ allow(subject).to receive(:transform_secret) { |input| "transformed: #{input}" }
53
+ end
54
+
55
+ it 'compares input with #transform_secret' do
56
+ expect(subject.secret_matches?('input', 'input')).to eq false
57
+ expect(subject.secret_matches?('a', 'transformed: a')).to eq true
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'bcrypt'
5
+
6
+ describe ::Doorkeeper::SecretStoring::BCrypt do
7
+ subject { described_class }
8
+ let(:instance) { double('instance', token: 'foo') }
9
+
10
+ describe '#transform_secret' do
11
+ it 'creates a bcrypt password' do
12
+ expect(subject.transform_secret('foo')).to be_a BCrypt::Password
13
+ end
14
+ end
15
+
16
+ describe '#restore_secret' do
17
+ it 'raises' do
18
+ expect { subject.restore_secret(instance, :token) }.to raise_error(NotImplementedError)
19
+ end
20
+ end
21
+
22
+ describe '#allows_restoring_secrets?' do
23
+ it 'does not allow it' do
24
+ expect(subject.allows_restoring_secrets?).to eq false
25
+ end
26
+ end
27
+
28
+ describe 'validate_for' do
29
+ it 'allows for valid model' do
30
+ expect(subject.validate_for(:application)).to eq true
31
+ end
32
+
33
+ it 'raises for invalid model' do
34
+ expect { subject.validate_for(:wat) }
35
+ .to raise_error(ArgumentError, /can only be used for storing application secrets/)
36
+ expect { subject.validate_for(:token) }
37
+ .to raise_error(ArgumentError, /can only be used for storing application secrets/)
38
+ end
39
+ end
40
+
41
+ describe 'secret_matches?' do
42
+ it 'compares input with #transform_secret' do
43
+ expect(subject.secret_matches?('input', 'input')).to eq false
44
+
45
+ password = BCrypt::Password.create 'foobar'
46
+ expect(subject.secret_matches?('foobar', password.to_s)).to eq true
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ::Doorkeeper::SecretStoring::Plain do
6
+ subject { described_class }
7
+ let(:instance) { double('instance', token: 'foo') }
8
+
9
+ describe '#transform_secret' do
10
+ it 'raises' do
11
+ expect(subject.transform_secret('foo')).to eq 'foo'
12
+ end
13
+ end
14
+
15
+ describe '#restore_secret' do
16
+ it 'raises' do
17
+ expect(subject.restore_secret(instance, :token)).to eq 'foo'
18
+ end
19
+ end
20
+
21
+ describe '#allows_restoring_secrets?' do
22
+ it 'does allow it' do
23
+ expect(subject.allows_restoring_secrets?).to eq true
24
+ end
25
+ end
26
+
27
+ describe 'validate_for' do
28
+ it 'allows for valid model' do
29
+ expect(subject.validate_for(:application)).to eq true
30
+ expect(subject.validate_for(:token)).to eq true
31
+ end
32
+
33
+ it 'raises for invalid model' do
34
+ expect { subject.validate_for(:wat) }.to raise_error(ArgumentError, /can not be used for wat/)
35
+ end
36
+ end
37
+
38
+ describe 'secret_matches?' do
39
+ it 'compares input with #transform_secret' do
40
+ expect(subject.secret_matches?('input', 'input')).to eq true
41
+ expect(subject.secret_matches?('a', 'b')).to eq false
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ::Doorkeeper::SecretStoring::Sha256Hash do
6
+ subject { described_class }
7
+ let(:instance) { double('instance') }
8
+
9
+ let(:hash_function) do
10
+ ->(input) { ::Digest::SHA256.hexdigest(input) }
11
+ end
12
+
13
+ describe '#transform_secret' do
14
+ it 'raises' do
15
+ expect(subject.transform_secret('foo')).to eq hash_function.call('foo')
16
+ end
17
+ end
18
+
19
+ describe '#restore_secret' do
20
+ it 'raises' do
21
+ expect { subject.restore_secret(instance, :token) }.to raise_error(NotImplementedError)
22
+ end
23
+ end
24
+
25
+ describe '#allows_restoring_secrets?' do
26
+ it 'does not allow it' do
27
+ expect(subject.allows_restoring_secrets?).to eq false
28
+ end
29
+ end
30
+
31
+ describe 'validate_for' do
32
+ it 'allows for valid model' do
33
+ expect(subject.validate_for(:application)).to eq true
34
+ expect(subject.validate_for(:token)).to eq true
35
+ end
36
+
37
+ it 'raises for invalid model' do
38
+ expect { subject.validate_for(:wat) }.to raise_error(ArgumentError, /can not be used for wat/)
39
+ end
40
+ end
41
+
42
+ describe 'secret_matches?' do
43
+ it 'compares input with #transform_secret' do
44
+ expect(subject.secret_matches?('input', 'input')).to eq false
45
+ expect(subject.secret_matches?('a', hash_function.call('a'))).to eq true
46
+ end
47
+ end
48
+ end
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Doorkeeper::AccessGrant do
4
+ let(:clazz) { Doorkeeper::AccessGrant }
4
5
  subject { FactoryBot.build(:access_grant) }
5
6
 
6
7
  it { expect(subject).to be_valid }
@@ -11,6 +12,66 @@ describe Doorkeeper::AccessGrant do
11
12
  let(:factory_name) { :access_grant }
12
13
  end
13
14
 
15
+ context 'with hashing enabled' do
16
+ let(:grant) { FactoryBot.create :access_grant }
17
+ include_context 'with token hashing enabled'
18
+
19
+ it 'holds a volatile plaintext token when created' do
20
+ expect(grant.plaintext_token).to be_a(String)
21
+ expect(grant.token)
22
+ .to eq(hashed_or_plain_token_func.call(grant.plaintext_token))
23
+
24
+ # Finder method only finds the hashed token
25
+ loaded = clazz.find_by(token: grant.token)
26
+ expect(loaded).to eq(grant)
27
+ expect(loaded.plaintext_token).to be_nil
28
+ expect(loaded.token).to eq(grant.token)
29
+ end
30
+
31
+ it 'does not find_by plain text tokens' do
32
+ expect(clazz.find_by(token: grant.plaintext_token)).to be_nil
33
+ end
34
+
35
+ describe 'with having a plain text token' do
36
+ let(:plain_text_token) { 'plain text token' }
37
+
38
+ before do
39
+ # Assume we have a plain text token from before activating the option
40
+ grant.update_column(:token, plain_text_token)
41
+ end
42
+
43
+ context 'without fallback lookup' do
44
+ it 'does not provide lookups with either through by_token' do
45
+ expect(clazz.by_token(plain_text_token)).to eq(nil)
46
+ expect(clazz.by_token(grant.token)).to eq(nil)
47
+
48
+ # And it does not touch the token
49
+ grant.reload
50
+ expect(grant.token).to eq(plain_text_token)
51
+ end
52
+ end
53
+
54
+ context 'with fallback lookup' do
55
+ include_context 'with token hashing and fallback lookup enabled'
56
+
57
+ it 'upgrades a plain token when falling back to it' do
58
+ # Side-effect: This will automatically upgrade the token
59
+ expect(clazz).to receive(:upgrade_fallback_value).and_call_original
60
+ expect(clazz.by_token(plain_text_token)).to eq(grant)
61
+
62
+ # Will find subsequently by hashing the token
63
+ expect(clazz.by_token(plain_text_token)).to eq(grant)
64
+
65
+ # And it modifies the token value
66
+ grant.reload
67
+ expect(grant.token).not_to eq(plain_text_token)
68
+ expect(clazz.find_by(token: plain_text_token)).to eq(nil)
69
+ expect(clazz.find_by(token: grant.token)).not_to be_nil
70
+ end
71
+ end
72
+ end
73
+ end
74
+
14
75
  describe 'validations' do
15
76
  it 'is invalid without resource_owner_id' do
16
77
  subject.resource_owner_id = nil
@@ -2,6 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  module Doorkeeper
4
4
  describe AccessToken do
5
+ let(:clazz) { Doorkeeper::AccessToken }
5
6
  subject { FactoryBot.build(:access_token) }
6
7
 
7
8
  it { expect(subject).to be_valid }
@@ -24,6 +25,67 @@ module Doorkeeper
24
25
  expect(token.token).to be_a(String)
25
26
  end
26
27
 
28
+ context 'with hashing enabled' do
29
+ let(:token) { FactoryBot.create :access_token }
30
+ include_context 'with token hashing enabled'
31
+
32
+ it 'holds a volatile plaintext token when created' do
33
+ expect(token.plaintext_token).to be_a(String)
34
+ expect(token.token)
35
+ .to eq(hashed_or_plain_token_func.call(token.plaintext_token))
36
+
37
+ # Finder method only finds the hashed token
38
+ loaded = clazz.find_by(token: token.token)
39
+ expect(loaded).to eq(token)
40
+ expect(loaded.plaintext_token).to be_nil
41
+ expect(loaded.token).to eq(token.token)
42
+ end
43
+
44
+ it 'does not find_by plain text tokens' do
45
+ expect(clazz.find_by(token: token.plaintext_token)).to be_nil
46
+ end
47
+
48
+ describe 'with having a plain text token' do
49
+ let(:plain_text_token) { 'plain text token' }
50
+ let(:access_token) { FactoryBot.create :access_token }
51
+
52
+ before do
53
+ # Assume we have a plain text token from before activating the option
54
+ access_token.update_column(:token, plain_text_token)
55
+ end
56
+
57
+ context 'without fallback lookup' do
58
+ it 'does not provide lookups with either through by_token' do
59
+ expect(clazz.by_token(plain_text_token)).to eq(nil)
60
+ expect(clazz.by_token(access_token.token)).to eq(nil)
61
+
62
+ # And it does not touch the token
63
+ access_token.reload
64
+ expect(access_token.token).to eq(plain_text_token)
65
+ end
66
+ end
67
+
68
+ context 'with fallback lookup' do
69
+ include_context 'with token hashing and fallback lookup enabled'
70
+
71
+ it 'upgrades a plain token when falling back to it' do
72
+ # Side-effect: This will automatically upgrade the token
73
+ expect(clazz).to receive(:upgrade_fallback_value).and_call_original
74
+ expect(clazz.by_token(plain_text_token)).to eq(access_token)
75
+
76
+ # Will find subsequently by hashing the token
77
+ expect(clazz.by_token(plain_text_token)).to eq(access_token)
78
+
79
+ # And it modifies the token value
80
+ access_token.reload
81
+ expect(access_token.token).not_to eq(plain_text_token)
82
+ expect(clazz.find_by(token: plain_text_token)).to eq(nil)
83
+ expect(clazz.find_by(token: access_token.token)).not_to be_nil
84
+ end
85
+ end
86
+ end
87
+ end
88
+
27
89
  it 'generates a token using a custom object' do
28
90
  eigenclass = class << CustomGeneratorArgs; self; end
29
91
  eigenclass.class_eval do
@@ -196,6 +258,67 @@ module Doorkeeper
196
258
  token2.save(validate: false)
197
259
  end.to raise_error(uniqueness_error)
198
260
  end
261
+
262
+ context 'with hashing enabled' do
263
+ include_context 'with token hashing enabled'
264
+ let(:token) { FactoryBot.create :access_token, use_refresh_token: true }
265
+
266
+ it 'holds a volatile refresh token when created' do
267
+ expect(token.plaintext_refresh_token).to be_a(String)
268
+ expect(token.refresh_token)
269
+ .to eq(hashed_or_plain_token_func.call(token.plaintext_refresh_token))
270
+
271
+ # Finder method only finds the hashed token
272
+ loaded = clazz.find_by(refresh_token: token.refresh_token)
273
+ expect(loaded).to eq(token)
274
+ expect(loaded.plaintext_refresh_token).to be_nil
275
+ expect(loaded.refresh_token).to eq(token.refresh_token)
276
+ end
277
+
278
+ it 'does not find_by plain text refresh tokens' do
279
+ expect(clazz.find_by(refresh_token: token.plaintext_refresh_token)).to be_nil
280
+ end
281
+
282
+ describe 'with having a plain text token' do
283
+ let(:plain_refresh_token) { 'plain refresh token' }
284
+ let(:access_token) { FactoryBot.create :access_token }
285
+
286
+ before do
287
+ # Assume we have a plain text token from before activating the option
288
+ access_token.update_column(:refresh_token, plain_refresh_token)
289
+ end
290
+
291
+ context 'without fallback lookup' do
292
+ it 'does not provide lookups with either through by_token' do
293
+ expect(clazz.by_refresh_token(plain_refresh_token)).to eq(nil)
294
+ expect(clazz.by_refresh_token(access_token.refresh_token)).to eq(nil)
295
+
296
+ # And it does not touch the token
297
+ access_token.reload
298
+ expect(access_token.refresh_token).to eq(plain_refresh_token)
299
+ end
300
+ end
301
+
302
+ context 'with fallback lookup' do
303
+ include_context 'with token hashing and fallback lookup enabled'
304
+
305
+ it 'upgrades a plain token when falling back to it' do
306
+ # Side-effect: This will automatically upgrade the token
307
+ expect(clazz).to receive(:upgrade_fallback_value).and_call_original
308
+ expect(clazz.by_refresh_token(plain_refresh_token)).to eq(access_token)
309
+
310
+ # Will find subsequently by hashing the token
311
+ expect(clazz.by_refresh_token(plain_refresh_token)).to eq(access_token)
312
+
313
+ # And it modifies the token value
314
+ access_token.reload
315
+ expect(access_token.refresh_token).not_to eq(plain_refresh_token)
316
+ expect(clazz.find_by(refresh_token: plain_refresh_token)).to eq(nil)
317
+ expect(clazz.find_by(refresh_token: access_token.refresh_token)).not_to be_nil
318
+ end
319
+ end
320
+ end
321
+ end
199
322
  end
200
323
 
201
324
  describe 'validations' do
@@ -1,7 +1,9 @@
1
1
  require 'spec_helper'
2
+ require 'bcrypt'
2
3
 
3
4
  module Doorkeeper
4
5
  describe Application do
6
+ let(:clazz) { Doorkeeper::Application }
5
7
  let(:require_owner) { Doorkeeper.configuration.instance_variable_set('@confirm_application_owner', true) }
6
8
  let(:unset_require_owner) { Doorkeeper.configuration.instance_variable_set('@confirm_application_owner', false) }
7
9
  let(:new_application) { FactoryBot.build(:application) }
@@ -122,6 +124,60 @@ module Doorkeeper
122
124
  expect(new_application).not_to be_valid
123
125
  end
124
126
 
127
+ context 'with hashing enabled' do
128
+ include_context 'with application hashing enabled'
129
+ let(:app) { FactoryBot.create :application }
130
+ let(:default_strategy) { Doorkeeper::SecretStoring::Sha256Hash }
131
+
132
+ it 'uses SHA256 to avoid additional dependencies' do
133
+ # Ensure token was generated
134
+ app.validate
135
+ expect(app.secret).to eq(default_strategy.transform_secret(app.plaintext_secret))
136
+ end
137
+
138
+ context 'when bcrypt strategy is configured' do
139
+ # In this text context, we have bcrypt loaded so `bcrypt_present?`
140
+ # will always be true
141
+ before do
142
+ Doorkeeper.configure do
143
+ hash_application_secrets using: 'Doorkeeper::SecretStoring::BCrypt'
144
+ end
145
+ end
146
+
147
+ it 'holds a volatile plaintext and BCrypt secret' do
148
+ expect(app.secret_strategy).to eq Doorkeeper::SecretStoring::BCrypt
149
+ expect(app.plaintext_secret).to be_a(String)
150
+ expect(app.secret).not_to eq(app.plaintext_secret)
151
+ expect { ::BCrypt::Password.create(app.secret) }.not_to raise_error
152
+ end
153
+ end
154
+
155
+ it 'does not fallback to plain lookup by default' do
156
+ lookup = clazz.by_uid_and_secret(app.uid, app.secret)
157
+ expect(lookup).to eq(nil)
158
+
159
+ lookup = clazz.by_uid_and_secret(app.uid, app.plaintext_secret)
160
+ expect(lookup).to eq(app)
161
+ end
162
+
163
+ context 'with fallback enabled' do
164
+ include_context 'with token hashing and fallback lookup enabled'
165
+
166
+ it 'provides plain and hashed lookup' do
167
+ lookup = clazz.by_uid_and_secret(app.uid, app.secret)
168
+ expect(lookup).to eq(app)
169
+
170
+ lookup = clazz.by_uid_and_secret(app.uid, app.plaintext_secret)
171
+ expect(lookup).to eq(app)
172
+ end
173
+ end
174
+
175
+ it 'does not provide access to secret after loading' do
176
+ lookup = clazz.by_uid_and_secret(app.uid, app.plaintext_secret)
177
+ expect(lookup.plaintext_secret).to be_nil
178
+ end
179
+ end
180
+
125
181
  describe 'destroy related models on cascade' do
126
182
  before(:each) do
127
183
  new_application.save
@@ -21,6 +21,33 @@ feature 'Authorization Code Flow' do
21
21
  url_should_not_have_param('error')
22
22
  end
23
23
 
24
+ context 'with grant hashing enabled' do
25
+ background do
26
+ config_is_set(:token_secret_strategy, ::Doorkeeper::SecretStoring::Sha256Hash)
27
+ end
28
+
29
+ scenario 'Authorization Code Flow with hashing' do
30
+ @client.redirect_uri = Doorkeeper.configuration.native_redirect_uri
31
+ @client.save!
32
+ visit authorization_endpoint_url(client: @client)
33
+ click_on 'Authorize'
34
+
35
+ access_grant_should_exist_for(@client, @resource_owner)
36
+
37
+ code = current_params['code']
38
+ expect(code).not_to be_nil
39
+
40
+ hashed_code = Doorkeeper::AccessGrant.secret_strategy.transform_secret code
41
+ expect(hashed_code).to eq Doorkeeper::AccessGrant.first.token
42
+
43
+ expect(code).not_to eq(hashed_code)
44
+
45
+ i_should_see 'Authorization code:'
46
+ i_should_see code
47
+ i_should_not_see hashed_code
48
+ end
49
+ end
50
+
24
51
  scenario 'resource owner authorizes using test url' do
25
52
  @client.redirect_uri = Doorkeeper.configuration.native_redirect_uri
26
53
  @client.save!
@@ -71,6 +98,19 @@ feature 'Authorization Code Flow' do
71
98
  should_have_json 'error', 'invalid_client'
72
99
  end
73
100
 
101
+ scenario 'resource owner requests an access token with authorization code but without client id' do
102
+ visit authorization_endpoint_url(client: @client)
103
+ click_on 'Authorize'
104
+
105
+ authorization_code = Doorkeeper::AccessGrant.first.token
106
+ page.driver.post token_endpoint_url(code: authorization_code, client_secret: @client.secret,
107
+ redirect_uri: @client.redirect_uri)
108
+
109
+ expect(Doorkeeper::AccessToken.count).to be_zero
110
+
111
+ should_have_json 'error', 'invalid_client'
112
+ end
113
+
74
114
  scenario 'silently authorizes if matching token exists' do
75
115
  default_scopes_exist :public, :write
76
116
 
@@ -290,7 +330,7 @@ feature 'Authorization Code Flow' do
290
330
 
291
331
  context 'when application scopes are present and no scope is passed' do
292
332
  background do
293
- @client.update_attributes(scopes: 'public write read')
333
+ @client.update(scopes: 'public write read')
294
334
  end
295
335
 
296
336
  scenario 'access grant has no scope' do
@@ -58,7 +58,7 @@ describe 'Client Credentials Request' do
58
58
  should_have_json 'error_description', translated_error_message(:invalid_scope)
59
59
  should_not_have_json 'access_token'
60
60
 
61
- expect(response.status).to eq(401)
61
+ expect(response.status).to eq(400)
62
62
  end
63
63
  end
64
64
  end
@@ -66,7 +66,7 @@ describe 'Client Credentials Request' do
66
66
 
67
67
  context 'when application scopes contain some of the default scopes and no scope is passed' do
68
68
  before do
69
- client.update_attributes(scopes: 'read write public')
69
+ client.update(scopes: 'read write public')
70
70
  end
71
71
 
72
72
  it 'issues new token with one default scope that are present in application scopes' do
@@ -20,7 +20,7 @@ feature 'Implicit Grant Flow (feature spec)' do
20
20
 
21
21
  context 'when application scopes are present and no scope is passed' do
22
22
  background do
23
- @client.update_attributes(scopes: 'public write read')
23
+ @client.update(scopes: 'public write read')
24
24
  end
25
25
 
26
26
  scenario 'access token has no scopes' do
@@ -64,7 +64,8 @@ describe 'Resource Owner Password Credentials Flow' do
64
64
  )
65
65
  end.not_to(change { Doorkeeper::AccessToken.count })
66
66
 
67
- expect(response).not_to be_ok
67
+ expect(response.status).to eq(401)
68
+ should_have_json 'error', 'invalid_client'
68
69
  end
69
70
  end
70
71
  end
@@ -88,7 +89,8 @@ describe 'Resource Owner Password Credentials Flow' do
88
89
  post password_token_endpoint_url(client_id: @client.uid, resource_owner: @resource_owner)
89
90
  end.not_to(change { Doorkeeper::AccessToken.count })
90
91
 
91
- expect(response).not_to be_ok
92
+ expect(response.status).to eq(401)
93
+ should_have_json 'error', 'invalid_client'
92
94
  end
93
95
  end
94
96
  end
@@ -147,7 +149,7 @@ describe 'Resource Owner Password Credentials Flow' do
147
149
  context 'when application scopes are present and differs from configured default scopes and no scope is passed' do
148
150
  before do
149
151
  default_scopes_exist :public
150
- @client.update_attributes(scopes: 'abc')
152
+ @client.update(scopes: 'abc')
151
153
  end
152
154
 
153
155
  it 'issues new token without any scope' do
@@ -166,7 +168,7 @@ describe 'Resource Owner Password Credentials Flow' do
166
168
 
167
169
  context 'when application scopes contain some of the default scopes and no scope is passed' do
168
170
  before do
169
- @client.update_attributes(scopes: 'read write public')
171
+ @client.update(scopes: 'read write public')
170
172
  end
171
173
 
172
174
  it 'issues new token with one default scope that are present in application scopes' do
@@ -215,7 +217,7 @@ describe 'Resource Owner Password Credentials Flow' do
215
217
  should_have_json 'error_description', translated_error_message(:invalid_scope)
216
218
  should_not_have_json 'access_token'
217
219
 
218
- expect(response.status).to eq(401)
220
+ expect(response.status).to eq(400)
219
221
  end
220
222
  end
221
223