doorkeeper 5.0.2 → 5.1.0.rc1

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 (63) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +7 -3
  3. data/Dangerfile +5 -2
  4. data/Gemfile +3 -1
  5. data/NEWS.md +18 -10
  6. data/README.md +1 -1
  7. data/app/controllers/doorkeeper/tokens_controller.rb +6 -6
  8. data/app/views/doorkeeper/applications/show.html.erb +1 -1
  9. data/app/views/layouts/doorkeeper/admin.html.erb +5 -3
  10. data/bin/console +15 -0
  11. data/gemfiles/rails_4_2.gemfile +1 -0
  12. data/gemfiles/rails_5_0.gemfile +1 -0
  13. data/gemfiles/rails_5_1.gemfile +1 -0
  14. data/gemfiles/rails_5_2.gemfile +2 -1
  15. data/gemfiles/rails_master.gemfile +1 -0
  16. data/lib/doorkeeper/config.rb +73 -6
  17. data/lib/doorkeeper/helpers/controller.rb +3 -2
  18. data/lib/doorkeeper/models/access_grant_mixin.rb +8 -1
  19. data/lib/doorkeeper/models/access_token_mixin.rb +40 -9
  20. data/lib/doorkeeper/models/application_mixin.rb +52 -1
  21. data/lib/doorkeeper/models/concerns/hashable.rb +137 -0
  22. data/lib/doorkeeper/models/concerns/scopes.rb +1 -1
  23. data/lib/doorkeeper/oauth/authorization/code.rb +1 -1
  24. data/lib/doorkeeper/oauth/authorization/token.rb +1 -1
  25. data/lib/doorkeeper/oauth/authorization_code_request.rb +1 -1
  26. data/lib/doorkeeper/oauth/client.rb +1 -1
  27. data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -3
  28. data/lib/doorkeeper/oauth/code_response.rb +2 -2
  29. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +23 -8
  30. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +32 -0
  31. data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -2
  32. data/lib/doorkeeper/oauth/pre_authorization.rb +8 -3
  33. data/lib/doorkeeper/oauth/refresh_token_request.rb +4 -1
  34. data/lib/doorkeeper/oauth/token_response.rb +2 -2
  35. data/lib/doorkeeper/orm/active_record/access_grant.rb +22 -2
  36. data/lib/doorkeeper/orm/active_record/application.rb +16 -2
  37. data/lib/doorkeeper/version.rb +3 -3
  38. data/lib/doorkeeper.rb +1 -0
  39. data/lib/generators/doorkeeper/templates/initializer.rb +41 -1
  40. data/spec/controllers/application_metal_controller_spec.rb +18 -4
  41. data/spec/controllers/tokens_controller_spec.rb +7 -11
  42. data/spec/dummy/app/controllers/application_controller.rb +1 -1
  43. data/spec/factories.rb +3 -3
  44. data/spec/lib/config_spec.rb +84 -0
  45. data/spec/lib/models/hashable_spec.rb +183 -0
  46. data/spec/lib/oauth/base_request_spec.rb +7 -7
  47. data/spec/lib/oauth/client_credentials/validation_spec.rb +3 -0
  48. data/spec/lib/oauth/helpers/scope_checker_spec.rb +52 -17
  49. data/spec/lib/oauth/helpers/uri_checker_spec.rb +20 -2
  50. data/spec/lib/oauth/password_access_token_request_spec.rb +32 -11
  51. data/spec/lib/oauth/pre_authorization_spec.rb +24 -0
  52. data/spec/lib/oauth/token_response_spec.rb +13 -13
  53. data/spec/lib/oauth/token_spec.rb +14 -0
  54. data/spec/models/doorkeeper/access_grant_spec.rb +61 -0
  55. data/spec/models/doorkeeper/access_token_spec.rb +123 -0
  56. data/spec/models/doorkeeper/application_spec.rb +37 -0
  57. data/spec/requests/flows/authorization_code_spec.rb +40 -0
  58. data/spec/requests/flows/password_spec.rb +4 -2
  59. data/spec/requests/flows/revoke_token_spec.rb +14 -30
  60. data/spec/spec_helper.rb +2 -1
  61. data/spec/support/ruby_2_6_rails_4_2_patch.rb +14 -0
  62. data/spec/support/shared/hashing_shared_context.rb +29 -0
  63. metadata +13 -6
data/spec/factories.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  FactoryBot.define do
2
- factory :access_grant, class: Doorkeeper::AccessGrant do
2
+ factory :access_grant, class: "Doorkeeper::AccessGrant" do
3
3
  sequence(:resource_owner_id) { |n| n }
4
4
  application
5
5
  redirect_uri { 'https://app.com/callback' }
@@ -7,7 +7,7 @@ FactoryBot.define do
7
7
  scopes { 'public write' }
8
8
  end
9
9
 
10
- factory :access_token, class: Doorkeeper::AccessToken do
10
+ factory :access_token, class: "Doorkeeper::AccessToken" do
11
11
  sequence(:resource_owner_id) { |n| n }
12
12
  application
13
13
  expires_in { 2.hours }
@@ -17,7 +17,7 @@ FactoryBot.define do
17
17
  end
18
18
  end
19
19
 
20
- factory :application, class: Doorkeeper::Application do
20
+ factory :application, class: "Doorkeeper::Application" do
21
21
  sequence(:name) { |n| "Application #{n}" }
22
22
  redirect_uri { 'https://app.com/callback' }
23
23
  end
@@ -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'
@@ -88,19 +88,40 @@ module Doorkeeper::OAuth
88
88
  PasswordAccessTokenRequest.new(server, client, owner, scope: 'public')
89
89
  end
90
90
 
91
- it 'validates the current scope' do
92
- allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('another'))
93
- subject.validate
94
- expect(subject.error).to eq(:invalid_scope)
91
+ context 'when scopes_by_grant_type is not configured for grant_type' do
92
+ it 'returns error when scopes are invalid' do
93
+ allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('another'))
94
+ subject.validate
95
+ expect(subject.error).to eq(:invalid_scope)
96
+ end
97
+
98
+ it 'creates the token with scopes if scopes are valid' do
99
+ allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('public'))
100
+ expect do
101
+ subject.authorize
102
+ end.to change { Doorkeeper::AccessToken.count }.by(1)
103
+
104
+ expect(Doorkeeper::AccessToken.last.scopes).to include('public')
105
+ end
95
106
  end
96
107
 
97
- it 'creates the token with scopes' do
98
- allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('public'))
99
- expect do
100
- subject.authorize
101
- end.to change { Doorkeeper::AccessToken.count }.by(1)
102
-
103
- expect(Doorkeeper::AccessToken.last.scopes).to include('public')
108
+ context 'when scopes_by_grant_type is configured for grant_type' do
109
+ it 'returns error when scopes are valid but not permitted for grant_type' do
110
+ allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('public'))
111
+ allow(Doorkeeper.configuration).to receive(:scopes_by_grant_type).and_return(password: 'another')
112
+ subject.validate
113
+ expect(subject.error).to eq(:invalid_scope)
114
+ end
115
+
116
+ it 'creates the token with scopes if scopes are valid and permitted for grant_type' do
117
+ allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('public'))
118
+ allow(Doorkeeper.configuration).to receive(:scopes_by_grant_type).and_return(password: [:public])
119
+ expect do
120
+ subject.authorize
121
+ end.to change { Doorkeeper::AccessToken.count }.by(1)
122
+
123
+ expect(Doorkeeper::AccessToken.last.scopes).to include('public')
124
+ end
104
125
  end
105
126
  end
106
127
 
@@ -91,6 +91,18 @@ module Doorkeeper::OAuth
91
91
  subject.scope = 'invalid'
92
92
  expect(subject).not_to be_authorizable
93
93
  end
94
+
95
+ it 'accepts scopes which are permitted for grant_type' do
96
+ allow(server).to receive(:scopes_by_grant_type).and_return(authorization_code: [:public])
97
+ subject.scope = 'public'
98
+ expect(subject).to be_authorizable
99
+ end
100
+
101
+ it 'rejects scopes which are not permitted for grant_type' do
102
+ allow(server).to receive(:scopes_by_grant_type).and_return(authorization_code: [:profile])
103
+ subject.scope = 'public'
104
+ expect(subject).not_to be_authorizable
105
+ end
94
106
  end
95
107
 
96
108
  context 'client application restricts valid scopes' do
@@ -114,6 +126,18 @@ module Doorkeeper::OAuth
114
126
  subject.scope = 'profile'
115
127
  expect(subject).to_not be_authorizable
116
128
  end
129
+
130
+ it 'accepts scopes which are permitted for grant_type' do
131
+ allow(server).to receive(:scopes_by_grant_type).and_return(authorization_code: [:public])
132
+ subject.scope = 'public'
133
+ expect(subject).to be_authorizable
134
+ end
135
+
136
+ it 'rejects scopes which are not permitted for grant_type' do
137
+ allow(server).to receive(:scopes_by_grant_type).and_return(authorization_code: [:profile])
138
+ subject.scope = 'public'
139
+ expect(subject).not_to be_authorizable
140
+ end
117
141
  end
118
142
 
119
143
  it 'uses default scopes when none is required' do
@@ -17,13 +17,13 @@ module Doorkeeper::OAuth
17
17
  describe '.body' do
18
18
  let(:access_token) do
19
19
  double :access_token,
20
- token: 'some-token',
21
- expires_in: '3600',
22
- expires_in_seconds: '300',
23
- scopes_string: 'two scopes',
24
- refresh_token: 'some-refresh-token',
25
- token_type: 'bearer',
26
- created_at: 0
20
+ plaintext_token: "some-token",
21
+ expires_in: "3600",
22
+ expires_in_seconds: "300",
23
+ scopes_string: "two scopes",
24
+ plaintext_refresh_token: "some-refresh-token",
25
+ token_type: "bearer",
26
+ created_at: 0
27
27
  end
28
28
 
29
29
  subject { TokenResponse.new(access_token).body }
@@ -58,12 +58,12 @@ module Doorkeeper::OAuth
58
58
  describe '.body filters out empty values' do
59
59
  let(:access_token) do
60
60
  double :access_token,
61
- token: 'some-token',
62
- expires_in_seconds: '',
63
- scopes_string: '',
64
- refresh_token: '',
65
- token_type: 'bearer',
66
- created_at: 0
61
+ plaintext_token: 'some-token',
62
+ expires_in_seconds: '',
63
+ scopes_string: '',
64
+ plaintext_refresh_token: '',
65
+ token_type: 'bearer',
66
+ created_at: 0
67
67
  end
68
68
 
69
69
  subject { TokenResponse.new(access_token).body }
@@ -113,6 +113,20 @@ module Doorkeeper
113
113
  end
114
114
  end
115
115
 
116
+ context 'token hashing is enabled' do
117
+ include_context 'with token hashing enabled'
118
+
119
+ let(:hashed_token) { hashed_or_plain_token_func.call('token') }
120
+ let(:token) { ->(_r) { 'token' } }
121
+
122
+ it 'searches with the hashed token' do
123
+ expect(
124
+ AccessToken
125
+ ).to receive(:find_by).with(token: hashed_token).and_return(token)
126
+ Token.authenticate double, token
127
+ end
128
+ end
129
+
116
130
  context 'refresh tokens are enabled' do
117
131
  before do
118
132
  Doorkeeper.configure do