doorkeeper 5.0.3 → 5.1.0.rc1

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 +7 -3
  3. data/Dangerfile +5 -2
  4. data/Gemfile +3 -1
  5. data/NEWS.md +20 -13
  6. data/README.md +1 -1
  7. data/app/controllers/doorkeeper/applications_controller.rb +3 -3
  8. data/app/controllers/doorkeeper/authorized_applications_controller.rb +1 -1
  9. data/app/controllers/doorkeeper/tokens_controller.rb +6 -6
  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/gemfiles/rails_4_2.gemfile +1 -0
  14. data/gemfiles/rails_5_0.gemfile +1 -0
  15. data/gemfiles/rails_5_1.gemfile +1 -0
  16. data/gemfiles/rails_5_2.gemfile +2 -1
  17. data/gemfiles/rails_master.gemfile +1 -0
  18. data/lib/doorkeeper.rb +1 -0
  19. data/lib/doorkeeper/config.rb +73 -6
  20. data/lib/doorkeeper/helpers/controller.rb +3 -2
  21. data/lib/doorkeeper/models/access_grant_mixin.rb +8 -1
  22. data/lib/doorkeeper/models/access_token_mixin.rb +40 -9
  23. data/lib/doorkeeper/models/application_mixin.rb +52 -1
  24. data/lib/doorkeeper/models/concerns/hashable.rb +137 -0
  25. data/lib/doorkeeper/models/concerns/scopes.rb +1 -1
  26. data/lib/doorkeeper/oauth/authorization/code.rb +1 -1
  27. data/lib/doorkeeper/oauth/authorization/token.rb +1 -1
  28. data/lib/doorkeeper/oauth/authorization_code_request.rb +1 -1
  29. data/lib/doorkeeper/oauth/client.rb +1 -1
  30. data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -3
  31. data/lib/doorkeeper/oauth/code_response.rb +2 -2
  32. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +23 -8
  33. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +32 -0
  34. data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -2
  35. data/lib/doorkeeper/oauth/pre_authorization.rb +8 -3
  36. data/lib/doorkeeper/oauth/refresh_token_request.rb +4 -1
  37. data/lib/doorkeeper/oauth/token_response.rb +2 -2
  38. data/lib/doorkeeper/orm/active_record/access_grant.rb +22 -2
  39. data/lib/doorkeeper/orm/active_record/application.rb +12 -53
  40. data/lib/doorkeeper/version.rb +3 -3
  41. data/lib/generators/doorkeeper/templates/initializer.rb +41 -1
  42. data/spec/controllers/application_metal_controller_spec.rb +18 -4
  43. data/spec/controllers/tokens_controller_spec.rb +7 -11
  44. data/spec/dummy/app/controllers/application_controller.rb +1 -1
  45. data/spec/factories.rb +3 -3
  46. data/spec/lib/config_spec.rb +84 -0
  47. data/spec/lib/models/hashable_spec.rb +183 -0
  48. data/spec/lib/oauth/base_request_spec.rb +7 -7
  49. data/spec/lib/oauth/client_credentials/validation_spec.rb +3 -0
  50. data/spec/lib/oauth/helpers/scope_checker_spec.rb +52 -17
  51. data/spec/lib/oauth/helpers/uri_checker_spec.rb +20 -2
  52. data/spec/lib/oauth/password_access_token_request_spec.rb +32 -11
  53. data/spec/lib/oauth/pre_authorization_spec.rb +24 -0
  54. data/spec/lib/oauth/token_response_spec.rb +13 -13
  55. data/spec/lib/oauth/token_spec.rb +14 -0
  56. data/spec/models/doorkeeper/access_grant_spec.rb +61 -0
  57. data/spec/models/doorkeeper/access_token_spec.rb +123 -0
  58. data/spec/models/doorkeeper/application_spec.rb +227 -295
  59. data/spec/requests/flows/authorization_code_spec.rb +40 -0
  60. data/spec/requests/flows/password_spec.rb +4 -2
  61. data/spec/requests/flows/revoke_token_spec.rb +14 -30
  62. data/spec/spec_helper.rb +2 -1
  63. data/spec/support/ruby_2_6_rails_4_2_patch.rb +14 -0
  64. data/spec/support/shared/hashing_shared_context.rb +29 -0
  65. metadata +12 -4
@@ -141,6 +141,22 @@ describe Doorkeeper, 'configuration' do
141
141
  end
142
142
  end
143
143
 
144
+ describe 'scopes_by_grant_type' do
145
+ it 'is {} by default' do
146
+ expect(subject.scopes_by_grant_type).to eq({})
147
+ end
148
+
149
+ it 'has hash value' do
150
+ hash = {}
151
+ Doorkeeper.configure do
152
+ orm DOORKEEPER_ORM
153
+ scopes_by_grant_type hash
154
+ end
155
+
156
+ expect(subject.scopes_by_grant_type).to eq(hash)
157
+ end
158
+ end
159
+
144
160
  describe 'use_refresh_token' do
145
161
  it 'is false by default' do
146
162
  expect(subject.refresh_token_enabled?).to eq(false)
@@ -525,4 +541,72 @@ describe Doorkeeper, 'configuration' do
525
541
  expect(subject.handle_auth_errors).to eq(:raise)
526
542
  end
527
543
  end
544
+
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)
549
+ end
550
+
551
+ context 'when provided' do
552
+ before do
553
+ Doorkeeper.configure do
554
+ reuse_access_token
555
+ hash_application_secrets
556
+ end
557
+ end
558
+
559
+ 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)
562
+
563
+ expect(::Doorkeeper::Application.perform_secret_hashing?).to eq(true)
564
+ end
565
+ end
566
+ end
567
+
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)
573
+ end
574
+
575
+ context 'when provided' do
576
+ include_context 'with token hashing enabled'
577
+
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)
582
+ end
583
+ end
584
+ end
585
+
586
+ describe 'fallback_to_plain_secrets' do
587
+ it 'is disabled by default' do
588
+ expect(subject.fallback_to_plain_secrets?).to eq(false)
589
+ end
590
+
591
+ context 'when provided' do
592
+ include_context 'with token hashing and fallback lookup enabled'
593
+
594
+ it 'will enable fallbacks' do
595
+ expect(subject.fallback_to_plain_secrets?).to eq(true)
596
+ end
597
+ end
598
+ end
599
+
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/)
603
+
604
+ Doorkeeper.configure do
605
+ reuse_access_token
606
+ hash_token_secrets
607
+ end
608
+
609
+ expect(subject.reuse_access_token).to eq(false)
610
+ end
611
+ end
528
612
  end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'Hashable' do
6
+ let(:clazz) do
7
+ Class.new do
8
+ include Doorkeeper::Models::Hashable
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
+
24
+ describe :hashed_or_plain_token do
25
+ let(:enabled_hashing?) { false }
26
+ subject { clazz.send(:hashed_or_plain_token, 'input') }
27
+
28
+ before do
29
+ allow(clazz).to receive(:perform_secret_hashing?)
30
+ .and_return enabled_hashing?
31
+ end
32
+
33
+ context 'when no hash function set' do
34
+ it 'returns the plain input' do
35
+ expect(subject).to eq 'input'
36
+ end
37
+
38
+ context 'when hashing enabled' do
39
+ let(:enabled_hashing?) { true }
40
+
41
+ it 'uses the default function' do
42
+ expect(clazz)
43
+ .to receive(:default_hash_function)
44
+ .with('input')
45
+ .and_call_original
46
+
47
+ expect(subject).not_to eq 'input'
48
+ end
49
+ end
50
+ end
51
+
52
+ context 'when hash_function defined' do
53
+ let(:hash_function) { ->(input) { input + '-hashed' } }
54
+
55
+ before do
56
+ clazz.secret_hash_function = hash_function
57
+ end
58
+
59
+ it 'returns the plain input' do
60
+ expect(clazz).not_to receive(:default_hash_function)
61
+ expect(subject).to eq 'input'
62
+ end
63
+
64
+ context 'when hashing enabled' do
65
+ let(:enabled_hashing?) { true }
66
+
67
+ it 'uses that function' do
68
+ expect(hash_function)
69
+ .to receive(:call)
70
+ .with('input')
71
+ .and_call_original
72
+
73
+ expect(subject).to eq 'input-hashed'
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ describe :secret_matches? do
80
+ context 'when comparer undefined' do
81
+ it 'uses a default compare' do
82
+ expect(clazz).to receive(:default_comparer).with('a', 'a').and_return true
83
+ expect(clazz.secret_matches?('a', 'a')).to be_truthy
84
+ end
85
+ end
86
+
87
+ context 'when comparer defined' do
88
+ let(:comparer) do
89
+ ->(*) { false }
90
+ end
91
+
92
+ before do
93
+ clazz.secret_comparer = comparer
94
+ end
95
+
96
+ it 'uses that comparer' do
97
+ expect(comparer).to receive(:call).with('a', 'a').and_call_original
98
+ expect(clazz.secret_matches?('a', 'a')).to be_falsey
99
+ end
100
+ end
101
+ end
102
+
103
+ describe :find_by_fallback_token do
104
+ let(:old_token) { instance_double(clazz, token: 'input') }
105
+ subject { clazz.send(:find_by_fallback_token, :token, 'input') }
106
+
107
+ it 'does not call find_by when not configured' do
108
+ expect(clazz).not_to receive(:find_by)
109
+ expect(subject).to eq(nil)
110
+ end
111
+
112
+ context 'when fallback configured' do
113
+ include_context 'with token hashing and fallback lookup enabled'
114
+ let(:hashed_token) { hashed_or_plain_token_func.call('input') }
115
+
116
+ it 'upgrades the plain token if no hashed exists' do
117
+ expect(clazz).to receive(:find_by).with(token: 'input').and_return(old_token)
118
+ expect(old_token).to receive(:update_column).with(:token, hashed_token)
119
+
120
+ expect(subject).to eq(old_token)
121
+ end
122
+ end
123
+ end
124
+
125
+ describe :find_by_plaintext_token do
126
+ let(:plain_token) { 'asdf' }
127
+ let(:subject) { clazz.send(:find_by_plaintext_token, :token, plain_token) }
128
+ let(:hashing_enabled?) { false }
129
+
130
+ before do
131
+ allow(clazz).to receive(:perform_secret_hashing?).and_return(hashing_enabled?)
132
+ end
133
+
134
+ context 'when not configured' do
135
+ it 'always finds with the plain value even when nil' do
136
+ expect(clazz).to receive(:find_by).with(token: plain_token).once.and_return(nil)
137
+ expect(subject).to eq(nil)
138
+ end
139
+ end
140
+
141
+ context 'when hashing configured' do
142
+ let(:hashing_enabled?) { true }
143
+ let(:hashed_token) { clazz.send(:default_hash_function, plain_token) }
144
+
145
+ it 'calls find_by only on the hashed value if it returns' do
146
+ expect(clazz).not_to receive(:find_by).with(token: plain_token)
147
+ expect(clazz).to receive(:find_by).with(token: hashed_token).and_return(:result)
148
+
149
+ expect(subject).to eq(:result)
150
+ end
151
+
152
+ it 'does not fall back to plain token' do
153
+ expect(clazz).not_to receive(:find_by).with(token: plain_token)
154
+ expect(clazz).to receive(:find_by).with(token: hashed_token).and_return(nil)
155
+
156
+ expect(subject).to eq(nil)
157
+ end
158
+
159
+ context 'when fallback configured' do
160
+ let(:old_token) { instance_double(clazz, token: plain_token) }
161
+ let(:hashed_token) { hashed_or_plain_token_func.call(plain_token) }
162
+
163
+ include_context 'with token hashing and fallback lookup enabled'
164
+
165
+ it 'does not fallback if found by hashed token' do
166
+ expect(clazz).to receive(:find_by).with(token: hashed_token).and_return old_token
167
+ expect(clazz).not_to receive(:find_by).with(token: plain_token)
168
+ expect(clazz).not_to receive(:upgrade_fallback_value)
169
+
170
+ expect(subject).to eq(old_token)
171
+ end
172
+
173
+ it 'also searches for the plain token if no hashed exists' do
174
+ expect(clazz).to receive(:find_by).with(token: hashed_token).and_return nil
175
+ expect(clazz).to receive(:find_by).with(token: plain_token).and_return(old_token)
176
+ expect(old_token).to receive(:update_column).with(:token, hashed_token)
177
+
178
+ expect(subject).to eq(old_token)
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -4,13 +4,13 @@ module Doorkeeper::OAuth
4
4
  describe BaseRequest do
5
5
  let(:access_token) do
6
6
  double :access_token,
7
- token: "some-token",
8
- expires_in: "3600",
9
- expires_in_seconds: "300",
10
- scopes_string: "two scopes",
11
- refresh_token: "some-refresh-token",
12
- token_type: "bearer",
13
- created_at: 0
7
+ plaintext_token: "some-token",
8
+ expires_in: "3600",
9
+ expires_in_seconds: "300",
10
+ scopes_string: "two scopes",
11
+ plaintext_refresh_token: "some-refresh-token",
12
+ token_type: "bearer",
13
+ created_at: 0
14
14
  end
15
15
 
16
16
  let(:client) { double :client, id: '1' }
@@ -21,6 +21,7 @@ class Doorkeeper::OAuth::ClientCredentialsRequest
21
21
  context 'with scopes' do
22
22
  it 'is invalid when scopes are not included in the server' do
23
23
  server_scopes = Doorkeeper::OAuth::Scopes.from_string 'email'
24
+ allow(request).to receive(:grant_type).and_return(Doorkeeper::OAuth::CLIENT_CREDENTIALS)
24
25
  allow(server).to receive(:scopes).and_return(server_scopes)
25
26
  allow(request).to receive(:scopes).and_return(
26
27
  Doorkeeper::OAuth::Scopes.from_string('invalid')
@@ -34,6 +35,7 @@ class Doorkeeper::OAuth::ClientCredentialsRequest
34
35
  server_scopes = Doorkeeper::OAuth::Scopes.from_string 'email app'
35
36
  allow(application).to receive(:scopes).and_return(application_scopes)
36
37
  allow(server).to receive(:scopes).and_return(server_scopes)
38
+ allow(request).to receive(:grant_type).and_return(Doorkeeper::OAuth::CLIENT_CREDENTIALS)
37
39
  allow(request).to receive(:scopes).and_return(application_scopes)
38
40
  expect(subject).to be_valid
39
41
  end
@@ -42,6 +44,7 @@ class Doorkeeper::OAuth::ClientCredentialsRequest
42
44
  application_scopes = Doorkeeper::OAuth::Scopes.from_string 'app'
43
45
  server_scopes = Doorkeeper::OAuth::Scopes.from_string 'email app'
44
46
  allow(application).to receive(:scopes).and_return(application_scopes)
47
+ allow(request).to receive(:grant_type).and_return(Doorkeeper::OAuth::CLIENT_CREDENTIALS)
45
48
  allow(server).to receive(:scopes).and_return(server_scopes)
46
49
  allow(request).to receive(:scopes).and_return(
47
50
  Doorkeeper::OAuth::Scopes.from_string('email')
@@ -6,31 +6,31 @@ module Doorkeeper::OAuth::Helpers
6
6
 
7
7
  it 'is valid if scope is present' do
8
8
  server_scopes.add :scope
9
- expect(ScopeChecker.valid?('scope', server_scopes)).to be_truthy
9
+ expect(ScopeChecker.valid?(scope_str: 'scope', server_scopes: server_scopes)).to be_truthy
10
10
  end
11
11
 
12
12
  it 'is invalid if includes tabs space' do
13
- expect(ScopeChecker.valid?("\tsomething", server_scopes)).to be_falsey
13
+ expect(ScopeChecker.valid?(scope_str: "\tsomething", server_scopes: server_scopes)).to be_falsey
14
14
  end
15
15
 
16
16
  it 'is invalid if scope is not present' do
17
- expect(ScopeChecker.valid?(nil, server_scopes)).to be_falsey
17
+ expect(ScopeChecker.valid?(scope_str: nil, server_scopes: server_scopes)).to be_falsey
18
18
  end
19
19
 
20
20
  it 'is invalid if scope is blank' do
21
- expect(ScopeChecker.valid?(' ', server_scopes)).to be_falsey
21
+ expect(ScopeChecker.valid?(scope_str: ' ', server_scopes: server_scopes)).to be_falsey
22
22
  end
23
23
 
24
24
  it 'is invalid if includes return space' do
25
- expect(ScopeChecker.valid?("scope\r", server_scopes)).to be_falsey
25
+ expect(ScopeChecker.valid?(scope_str: "scope\r", server_scopes: server_scopes)).to be_falsey
26
26
  end
27
27
 
28
28
  it 'is invalid if includes new lines' do
29
- expect(ScopeChecker.valid?("scope\nanother", server_scopes)).to be_falsey
29
+ expect(ScopeChecker.valid?(scope_str: "scope\nanother", server_scopes: server_scopes)).to be_falsey
30
30
  end
31
31
 
32
32
  it 'is invalid if any scope is not included in server scopes' do
33
- expect(ScopeChecker.valid?('scope another', server_scopes)).to be_falsey
33
+ expect(ScopeChecker.valid?(scope_str: 'scope another', server_scopes: server_scopes)).to be_falsey
34
34
  end
35
35
 
36
36
  context 'with application_scopes' do
@@ -42,19 +42,54 @@ module Doorkeeper::OAuth::Helpers
42
42
  end
43
43
 
44
44
  it 'is valid if scope is included in the application scope list' do
45
- expect(ScopeChecker.valid?(
46
- 'app123',
47
- server_scopes,
48
- application_scopes
49
- )).to be_truthy
45
+ expect(ScopeChecker.valid?(scope_str: 'app123',
46
+ server_scopes: server_scopes,
47
+ app_scopes: application_scopes)).to be_truthy
50
48
  end
51
49
 
52
50
  it 'is invalid if any scope is not included in the application' do
53
- expect(ScopeChecker.valid?(
54
- 'svr',
55
- server_scopes,
56
- application_scopes
57
- )).to be_falsey
51
+ expect(ScopeChecker.valid?(scope_str: 'svr',
52
+ server_scopes: server_scopes,
53
+ app_scopes: application_scopes)).to be_falsey
54
+ end
55
+ end
56
+
57
+ context 'with grant_type' do
58
+ let(:server_scopes) do
59
+ Doorkeeper::OAuth::Scopes.from_string 'scope1 scope2'
60
+ end
61
+
62
+ context 'with scopes_by_grant_type not configured for grant_type' do
63
+ it 'is valid if the scope is in server scopes' do
64
+ expect(ScopeChecker.valid?(scope_str: 'scope1',
65
+ server_scopes: server_scopes,
66
+ grant_type: Doorkeeper::OAuth::PASSWORD)).to be_truthy
67
+ end
68
+
69
+ it 'is invalid if the scope is not in server scopes' do
70
+ expect(ScopeChecker.valid?(scope_str: 'unknown',
71
+ server_scopes: server_scopes,
72
+ grant_type: Doorkeeper::OAuth::PASSWORD)).to be_falsey
73
+ end
74
+ end
75
+
76
+ context 'when scopes_by_grant_type configured for grant_type' do
77
+ before do
78
+ allow(Doorkeeper.configuration).to receive(:scopes_by_grant_type).
79
+ and_return(password: [:scope1])
80
+ end
81
+
82
+ it 'is valid if the scope is permitted for grant_type' do
83
+ expect(ScopeChecker.valid?(scope_str: 'scope1',
84
+ server_scopes: server_scopes,
85
+ grant_type: Doorkeeper::OAuth::PASSWORD)).to be_truthy
86
+ end
87
+
88
+ it 'is invalid if the scope is permitted for grant_type' do
89
+ expect(ScopeChecker.valid?(scope_str: 'scope2',
90
+ server_scopes: server_scopes,
91
+ grant_type: Doorkeeper::OAuth::PASSWORD)).to be_falsey
92
+ end
58
93
  end
59
94
  end
60
95
  end
@@ -61,18 +61,36 @@ module Doorkeeper::OAuth::Helpers
61
61
  expect(URIChecker.matches?(uri, client_uri)).to be_truthy
62
62
  end
63
63
 
64
- it 'doesn\'t allow non-matching domains through' do
64
+ it "doesn't allow non-matching domains through" do
65
65
  uri = 'http://app.abc/?query=hello'
66
66
  client_uri = 'http://app.co'
67
67
  expect(URIChecker.matches?(uri, client_uri)).to be_falsey
68
68
  end
69
69
 
70
- it 'doesn\'t allow non-matching domains that don\'t start at the beginning' do
70
+ it "doesn't allow non-matching domains that don't start at the beginning" do
71
71
  uri = 'http://app.co/?query=hello'
72
72
  client_uri = 'http://example.com?app.co=test'
73
73
  expect(URIChecker.matches?(uri, client_uri)).to be_falsey
74
74
  end
75
75
 
76
+ context 'loopback IP redirect URIs' do
77
+ it 'ignores port for same URIs' do
78
+ uri = 'http://127.0.0.1:5555/auth/callback'
79
+ client_uri = 'http://127.0.0.1:48599/auth/callback'
80
+ expect(URIChecker.matches?(uri, client_uri)).to be_truthy
81
+
82
+ uri = 'http://[::1]:5555/auth/callback'
83
+ client_uri = 'http://[::1]:5555/auth/callback'
84
+ expect(URIChecker.matches?(uri, client_uri)).to be_truthy
85
+ end
86
+
87
+ it "doesn't ignore port for URIs with different queries" do
88
+ uri = 'http://127.0.0.1:5555/auth/callback'
89
+ client_uri = 'http://127.0.0.1:48599/auth/callback2'
90
+ expect(URIChecker.matches?(uri, client_uri)).to be_falsey
91
+ end
92
+ end
93
+
76
94
  context "client registered query params" do
77
95
  it "doesn't allow query being absent" do
78
96
  uri = 'http://app.co'