doorkeeper 5.1.0.rc1 → 5.1.0.rc2

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +11 -2
  3. data/Appraisals +29 -3
  4. data/Gemfile +13 -5
  5. data/NEWS.md +52 -15
  6. data/README.md +68 -487
  7. data/app/controllers/doorkeeper/token_info_controller.rb +1 -1
  8. data/app/controllers/doorkeeper/tokens_controller.rb +1 -1
  9. data/doorkeeper.gemspec +3 -2
  10. data/gemfiles/rails_4_2.gemfile +8 -5
  11. data/gemfiles/rails_5_0.gemfile +9 -6
  12. data/gemfiles/rails_5_1.gemfile +9 -6
  13. data/gemfiles/rails_5_2.gemfile +9 -6
  14. data/gemfiles/rails_6_0.gemfile +16 -0
  15. data/gemfiles/rails_master.gemfile +8 -10
  16. data/lib/doorkeeper.rb +7 -1
  17. data/lib/doorkeeper/config.rb +110 -24
  18. data/lib/doorkeeper/models/access_grant_mixin.rb +15 -7
  19. data/lib/doorkeeper/models/access_token_mixin.rb +29 -16
  20. data/lib/doorkeeper/models/application_mixin.rb +18 -28
  21. data/lib/doorkeeper/models/concerns/expirable.rb +3 -2
  22. data/lib/doorkeeper/models/concerns/reusable.rb +19 -0
  23. data/lib/doorkeeper/models/concerns/scopes.rb +4 -0
  24. data/lib/doorkeeper/models/concerns/secret_storable.rb +106 -0
  25. data/lib/doorkeeper/oauth/authorization/token.rb +3 -1
  26. data/lib/doorkeeper/oauth/error_response.rb +5 -1
  27. data/lib/doorkeeper/oauth/helpers/unique_token.rb +12 -1
  28. data/lib/doorkeeper/oauth/invalid_token_response.rb +4 -0
  29. data/lib/doorkeeper/oauth/token_introspection.rb +72 -6
  30. data/lib/doorkeeper/orm/active_record/access_grant.rb +9 -8
  31. data/lib/doorkeeper/orm/active_record/application.rb +10 -6
  32. data/lib/doorkeeper/secret_storing/base.rb +63 -0
  33. data/lib/doorkeeper/secret_storing/bcrypt.rb +59 -0
  34. data/lib/doorkeeper/secret_storing/plain.rb +33 -0
  35. data/lib/doorkeeper/secret_storing/sha256_hash.rb +25 -0
  36. data/lib/doorkeeper/version.rb +1 -1
  37. data/lib/generators/doorkeeper/templates/initializer.rb +62 -20
  38. data/spec/controllers/authorizations_controller_spec.rb +3 -3
  39. data/spec/controllers/token_info_controller_spec.rb +1 -1
  40. data/spec/controllers/tokens_controller_spec.rb +78 -30
  41. data/spec/dummy/config/application.rb +12 -1
  42. data/spec/lib/config_spec.rb +119 -35
  43. data/spec/lib/models/expirable_spec.rb +12 -0
  44. data/spec/lib/models/reusable_spec.rb +40 -0
  45. data/spec/lib/models/scopes_spec.rb +13 -1
  46. data/spec/lib/models/secret_storable_spec.rb +113 -0
  47. data/spec/lib/oauth/authorization_code_request_spec.rb +18 -1
  48. data/spec/lib/oauth/client_credentials/creator_spec.rb +51 -7
  49. data/spec/lib/oauth/error_response_spec.rb +7 -1
  50. data/spec/lib/oauth/password_access_token_request_spec.rb +11 -1
  51. data/spec/lib/oauth/token_request_spec.rb +16 -1
  52. data/spec/lib/secret_storing/base_spec.rb +60 -0
  53. data/spec/lib/secret_storing/bcrypt_spec.rb +49 -0
  54. data/spec/lib/secret_storing/plain_spec.rb +44 -0
  55. data/spec/lib/secret_storing/sha256_hash_spec.rb +48 -0
  56. data/spec/models/doorkeeper/application_spec.rb +23 -4
  57. data/spec/requests/flows/authorization_code_spec.rb +3 -3
  58. data/spec/requests/flows/client_credentials_spec.rb +2 -2
  59. data/spec/requests/flows/implicit_grant_spec.rb +1 -1
  60. data/spec/requests/flows/password_spec.rb +3 -3
  61. data/spec/routing/custom_controller_routes_spec.rb +4 -0
  62. data/spec/support/shared/hashing_shared_context.rb +12 -5
  63. metadata +51 -21
  64. data/lib/doorkeeper/models/concerns/hashable.rb +0 -137
  65. data/spec/lib/models/hashable_spec.rb +0 -183
@@ -1,6 +1,17 @@
1
1
  require File.expand_path('boot', __dir__)
2
2
 
3
- require 'rails/all'
3
+ require "rails"
4
+
5
+ %w[
6
+ action_controller/railtie
7
+ action_view/railtie
8
+ sprockets/railtie
9
+ ].each do |railtie|
10
+ begin
11
+ require railtie
12
+ rescue LoadError
13
+ end
14
+ end
4
15
 
5
16
  Bundler.require(*Rails.groups)
6
17
 
@@ -207,6 +207,31 @@ describe Doorkeeper, 'configuration' do
207
207
  end
208
208
  end
209
209
 
210
+ describe 'token_reuse_limit' do
211
+ it 'is 100 by default' do
212
+ expect(subject.token_reuse_limit).to eq(100)
213
+ end
214
+
215
+ it 'can change the value' do
216
+ Doorkeeper.configure do
217
+ token_reuse_limit 90
218
+ end
219
+
220
+ expect(subject.token_reuse_limit).to eq(90)
221
+ end
222
+
223
+ it 'sets the value to 100 if invalid value is being set' do
224
+ expect(Rails.logger).to receive(:warn).with(/will be set to default 100/)
225
+
226
+ Doorkeeper.configure do
227
+ reuse_access_token
228
+ token_reuse_limit 110
229
+ end
230
+
231
+ expect(subject.token_reuse_limit).to eq(100)
232
+ end
233
+ end
234
+
210
235
  describe 'enforce_configured_scopes' do
211
236
  it 'is false by default' do
212
237
  expect(subject.enforce_configured_scopes?).to eq(false)
@@ -457,6 +482,22 @@ describe Doorkeeper, 'configuration' do
457
482
  end
458
483
  end
459
484
 
485
+ describe 'default_generator_method' do
486
+ it "is :urlsafe_base64 by default" do
487
+ expect(Doorkeeper.configuration.default_generator_method)
488
+ .to eq(:urlsafe_base64)
489
+ end
490
+
491
+ it 'can change the value' do
492
+ Doorkeeper.configure do
493
+ orm DOORKEEPER_ORM
494
+ default_generator_method :hex
495
+ end
496
+
497
+ expect(subject.default_generator_method).to eq(:hex)
498
+ end
499
+ end
500
+
460
501
  describe 'base_controller' do
461
502
  context 'default' do
462
503
  it { expect(Doorkeeper.configuration.base_controller).to eq('ActionController::Base') }
@@ -542,71 +583,114 @@ describe Doorkeeper, 'configuration' do
542
583
  end
543
584
  end
544
585
 
545
- describe 'hash_application_secrets' do
546
- it 'is disabled by default' do
547
- expect(subject.hash_application_secrets?).to eq(false)
548
- expect(::Doorkeeper::Application.perform_secret_hashing?).to eq(false)
586
+ describe 'token_secret_strategy' do
587
+ it 'is plain by default' do
588
+ expect(subject.token_secret_strategy).to eq(Doorkeeper::SecretStoring::Plain)
589
+ expect(subject.token_secret_fallback_strategy).to eq(nil)
549
590
  end
550
591
 
551
592
  context 'when provided' do
552
593
  before do
553
594
  Doorkeeper.configure do
554
- reuse_access_token
555
- hash_application_secrets
595
+ hash_token_secrets
556
596
  end
557
597
  end
558
598
 
559
599
  it 'will enable hashing for applications' do
560
- expect(subject.reuse_access_token).to eq(true)
561
- expect(subject.hash_application_secrets?).to eq(true)
600
+ expect(subject.token_secret_strategy).to eq(Doorkeeper::SecretStoring::Sha256Hash)
601
+ expect(subject.token_secret_fallback_strategy).to eq(nil)
602
+ end
603
+ end
562
604
 
563
- expect(::Doorkeeper::Application.perform_secret_hashing?).to eq(true)
605
+ context 'when manually provided with invalid constant' do
606
+ it 'raises an exception' do
607
+ expect {
608
+ Doorkeeper.configure do
609
+ hash_token_secrets using: 'does not exist'
610
+ end
611
+ }.to raise_error(NameError)
564
612
  end
565
613
  end
566
- end
567
614
 
568
- describe 'hash_token_secrets' do
569
- it 'is disabled by default' do
570
- expect(subject.hash_token_secrets?).to eq(false)
571
- expect(::Doorkeeper::AccessToken.perform_secret_hashing?).to eq(false)
572
- expect(::Doorkeeper::AccessGrant.perform_secret_hashing?).to eq(false)
615
+ context 'when manually provided with invalid option' do
616
+ it 'raises an exception' do
617
+ expect do
618
+ Doorkeeper.configure do
619
+ hash_token_secrets using: 'Doorkeeper::SecretStoring::BCrypt'
620
+ end
621
+ end.to raise_error(ArgumentError,
622
+ /can only be used for storing application secrets/)
623
+ end
573
624
  end
574
625
 
575
- context 'when provided' do
576
- include_context 'with token hashing enabled'
626
+ context 'when provided with fallback' do
627
+ before do
628
+ Doorkeeper.configure do
629
+ hash_token_secrets fallback: :plain
630
+ end
631
+ end
577
632
 
578
- it 'will enable hashing for AccessToken and AccessGrant' do
579
- expect(subject.hash_token_secrets?).to eq(true)
580
- expect(::Doorkeeper::AccessToken.perform_secret_hashing?).to eq(true)
581
- expect(::Doorkeeper::AccessGrant.perform_secret_hashing?).to eq(true)
633
+ it 'will enable hashing for applications' do
634
+ expect(subject.token_secret_strategy).to eq(Doorkeeper::SecretStoring::Sha256Hash)
635
+ expect(subject.token_secret_fallback_strategy).to eq(Doorkeeper::SecretStoring::Plain)
636
+ end
637
+ end
638
+
639
+
640
+ describe 'hash_token_secrets together with reuse_access_token' do
641
+ it 'will disable reuse_access_token' do
642
+ expect(Rails.logger).to receive(:warn).with(/reuse_access_token will be disabled/)
643
+
644
+ Doorkeeper.configure do
645
+ reuse_access_token
646
+ hash_token_secrets
647
+ end
648
+
649
+ expect(subject.reuse_access_token).to eq(false)
582
650
  end
583
651
  end
584
652
  end
585
653
 
586
- describe 'fallback_to_plain_secrets' do
587
- it 'is disabled by default' do
588
- expect(subject.fallback_to_plain_secrets?).to eq(false)
654
+ describe 'application_secret_strategy' do
655
+ it 'is plain by default' do
656
+ expect(subject.application_secret_strategy).to eq(Doorkeeper::SecretStoring::Plain)
657
+ expect(subject.application_secret_fallback_strategy).to eq(nil)
589
658
  end
590
659
 
591
660
  context 'when provided' do
592
- include_context 'with token hashing and fallback lookup enabled'
661
+ before do
662
+ Doorkeeper.configure do
663
+ hash_application_secrets
664
+ end
665
+ end
593
666
 
594
- it 'will enable fallbacks' do
595
- expect(subject.fallback_to_plain_secrets?).to eq(true)
667
+ it 'will enable hashing for applications' do
668
+ expect(subject.application_secret_strategy).to eq(Doorkeeper::SecretStoring::Sha256Hash)
669
+ expect(subject.application_secret_fallback_strategy).to eq(nil)
596
670
  end
597
671
  end
598
- end
599
672
 
600
- describe 'hash_token_secrets together with reuse_access_token' do
601
- it 'will disable reuse_access_token' do
602
- expect(Rails.logger).to receive(:warn).with(/reuse_access_token will be disabled/)
673
+ context 'when manually provided with invalid constant' do
674
+ it 'raises an exception' do
675
+ expect {
676
+ Doorkeeper.configure do
677
+ hash_application_secrets using: 'does not exist'
678
+ end
679
+ }.to raise_error(NameError)
680
+ end
681
+ end
603
682
 
604
- Doorkeeper.configure do
605
- reuse_access_token
606
- hash_token_secrets
683
+ context 'when provided with fallback' do
684
+ before do
685
+ Doorkeeper.configure do
686
+ hash_application_secrets fallback: :plain
687
+ end
607
688
  end
608
689
 
609
- expect(subject.reuse_access_token).to eq(false)
690
+ it 'will enable hashing for applications' do
691
+ expect(subject.application_secret_strategy).to eq(Doorkeeper::SecretStoring::Sha256Hash)
692
+ expect(subject.application_secret_fallback_strategy).to eq(Doorkeeper::SecretStoring::Plain)
693
+ end
610
694
  end
611
695
  end
612
696
  end
@@ -44,4 +44,16 @@ describe 'Expirable' do
44
44
  expect(subject.expires_in_seconds).to be_nil
45
45
  end
46
46
  end
47
+
48
+ describe :expires_at do
49
+ it 'should return the expiration time of the token' do
50
+ allow(subject).to receive(:expires_in).and_return(2.minutes)
51
+ expect(subject.expires_at).to be_a(Time)
52
+ end
53
+
54
+ it 'should return nil when expires_in is nil' do
55
+ allow(subject).to receive(:expires_in).and_return(nil)
56
+ expect(subject.expires_at).to be_nil
57
+ end
58
+ end
47
59
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'Reusable' do
6
+ subject do
7
+ Class.new do
8
+ include Doorkeeper::Models::Reusable
9
+ end.new
10
+ end
11
+
12
+ describe :reusable? do
13
+ it 'is reusable if its expires_in is nil' do
14
+ allow(subject).to receive(:expired?).and_return(false)
15
+ allow(subject).to receive(:expires_in).and_return(nil)
16
+ expect(subject).to be_reusable
17
+ end
18
+
19
+ it 'is reusable if its expiry has crossed reusable limit' do
20
+ allow(subject).to receive(:expired?).and_return(false)
21
+ allow(Doorkeeper.configuration).to receive(:token_reuse_limit).and_return(90)
22
+ allow(subject).to receive(:expires_in).and_return(100.seconds)
23
+ allow(subject).to receive(:expires_in_seconds).and_return(20.seconds)
24
+ expect(subject).to be_reusable
25
+ end
26
+
27
+ it 'is not reusable if its expiry has crossed reusable limit' do
28
+ allow(subject).to receive(:expired?).and_return(false)
29
+ allow(Doorkeeper.configuration).to receive(:token_reuse_limit).and_return(90)
30
+ allow(subject).to receive(:expires_in).and_return(100.seconds)
31
+ allow(subject).to receive(:expires_in_seconds).and_return(5.seconds)
32
+ expect(subject).not_to be_reusable
33
+ end
34
+
35
+ it 'is not reusable if it is already expired' do
36
+ allow(subject).to receive(:expired?).and_return(true)
37
+ expect(subject).not_to be_reusable
38
+ end
39
+ end
40
+ end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe 'Doorkeeper::Models::Scopes' do
4
4
  subject do
5
- Class.new(Hash) do
5
+ Class.new(Struct.new(:scopes)) do
6
6
  include Doorkeeper::Models::Scopes
7
7
  end.new
8
8
  end
@@ -21,6 +21,18 @@ describe 'Doorkeeper::Models::Scopes' do
21
21
  end
22
22
  end
23
23
 
24
+ describe :scopes= do
25
+ it 'accepts String' do
26
+ subject.scopes = 'private admin'
27
+ expect(subject.scopes_string).to eq('private admin')
28
+ end
29
+
30
+ it 'accepts Array' do
31
+ subject.scopes = %w[private admin]
32
+ expect(subject.scopes_string).to eq('private admin')
33
+ end
34
+ end
35
+
24
36
  describe :scopes_string do
25
37
  it 'is a `Scopes` class' do
26
38
  expect(subject.scopes_string).to eq('public admin')
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'SecretStorable' do
6
+ let(:clazz) do
7
+ Class.new do
8
+ include Doorkeeper::Models::SecretStorable
9
+
10
+ def self.find_by(*)
11
+ raise 'stub this'
12
+ end
13
+
14
+ def update_column(*)
15
+ raise 'stub this'
16
+ end
17
+
18
+ def token
19
+ raise 'stub this'
20
+ end
21
+ end
22
+ end
23
+ let(:strategy) { clazz.secret_strategy }
24
+
25
+ describe :find_by_plaintext_token do
26
+ subject { clazz.send(:find_by_plaintext_token, 'attr', 'input') }
27
+
28
+ it 'forwards to the secret_strategy' do
29
+ expect(strategy)
30
+ .to receive(:transform_secret)
31
+ .with('input')
32
+ .and_return 'found'
33
+
34
+ expect(clazz)
35
+ .to receive(:find_by)
36
+ .with('attr' => 'found')
37
+ .and_return 'result'
38
+
39
+
40
+ expect(subject).to eq 'result'
41
+ end
42
+
43
+ it 'calls find_by_fallback_token if not found' do
44
+ expect(clazz)
45
+ .to receive(:find_by)
46
+ .with('attr' => 'input')
47
+ .and_return nil
48
+
49
+ expect(clazz)
50
+ .to receive(:find_by_fallback_token)
51
+ .with('attr', 'input')
52
+ .and_return 'fallback'
53
+
54
+ expect(subject).to eq 'fallback'
55
+ end
56
+ end
57
+
58
+ describe :find_by_fallback_token do
59
+ subject { clazz.send(:find_by_fallback_token, 'attr', 'input') }
60
+ let(:fallback) { double(::Doorkeeper::SecretStoring::Plain) }
61
+
62
+ it 'returns nil if none defined' do
63
+ expect(clazz.fallback_secret_strategy).to eq nil
64
+ expect(subject).to eq nil
65
+ end
66
+
67
+ context 'if a fallback strategy is defined' do
68
+ let(:resource) { double('Token model') }
69
+ before do
70
+ allow(clazz).to receive(:fallback_secret_strategy).and_return(fallback)
71
+ end
72
+
73
+ it 'calls the strategy for lookup' do
74
+ expect(clazz)
75
+ .to receive(:find_by)
76
+ .with('attr' => 'fallback')
77
+ .and_return(resource)
78
+
79
+ expect(fallback)
80
+ .to receive(:transform_secret)
81
+ .with('input')
82
+ .and_return('fallback')
83
+
84
+ # store_secret will call the resource
85
+ expect(resource)
86
+ .to receive(:attr=)
87
+ .with('new value')
88
+
89
+ # It will upgrade the secret automtically using the current strategy
90
+ expect(strategy)
91
+ .to receive(:transform_secret)
92
+ .with('input')
93
+ .and_return('new value')
94
+
95
+ expect(resource).to receive(:update).with('attr' => 'new value')
96
+ expect(subject).to eq resource
97
+ end
98
+ end
99
+ end
100
+
101
+
102
+ describe :secret_strategy do
103
+ it 'defaults to plain strategy' do
104
+ expect(strategy).to eq Doorkeeper::SecretStoring::Plain
105
+ end
106
+ end
107
+
108
+ describe :fallback_secret_strategy do
109
+ it 'defaults to nil' do
110
+ expect(clazz.fallback_secret_strategy).to eq nil
111
+ end
112
+ end
113
+ end
@@ -73,7 +73,7 @@ module Doorkeeper::OAuth
73
73
  expect(subject.error).to eq(:invalid_grant)
74
74
  end
75
75
 
76
- it 'skips token creation if there is a matching one' do
76
+ it 'skips token creation if there is a matching one reusable' do
77
77
  scopes = grant.scopes
78
78
 
79
79
  Doorkeeper.configure do
@@ -88,6 +88,23 @@ module Doorkeeper::OAuth
88
88
  expect { subject.authorize }.to_not(change { Doorkeeper::AccessToken.count })
89
89
  end
90
90
 
91
+ it 'creates token if there is a matching one but non reusable' do
92
+ scopes = grant.scopes
93
+
94
+ Doorkeeper.configure do
95
+ orm DOORKEEPER_ORM
96
+ reuse_access_token
97
+ default_scopes(*scopes)
98
+ end
99
+
100
+ FactoryBot.create(:access_token, application_id: client.id,
101
+ resource_owner_id: grant.resource_owner_id, scopes: grant.scopes.to_s)
102
+
103
+ allow_any_instance_of(Doorkeeper::AccessToken).to receive(:reusable?).and_return(false)
104
+
105
+ expect { subject.authorize }.to change { Doorkeeper::AccessToken.count }.by(1)
106
+ end
107
+
91
108
  it "calls configured request callback methods" do
92
109
  expect(Doorkeeper.configuration.before_successful_strategy_response)
93
110
  .to receive(:call).with(subject).once