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,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
@@ -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' }
@@ -15,15 +15,59 @@ class Doorkeeper::OAuth::ClientCredentialsRequest
15
15
  end.to change { Doorkeeper::AccessToken.count }.by(1)
16
16
  end
17
17
 
18
- context "when reuse_access_token is true" do
19
- it "returns the existing valid token" do
20
- allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
21
- existing_token = subject.call(client, scopes)
18
+ context 'when reuse_access_token is true' do
19
+ context 'when expiration is disabled' do
20
+ it 'returns the existing valid token' do
21
+ allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
22
+ existing_token = subject.call(client, scopes)
22
23
 
23
- result = subject.call(client, scopes)
24
+ result = subject.call(client, scopes)
25
+
26
+ expect(Doorkeeper::AccessToken.count).to eq(1)
27
+ expect(result).to eq(existing_token)
28
+ end
29
+ end
30
+
31
+ context 'when existing token has not crossed token_reuse_limit' do
32
+ it 'returns the existing valid token' do
33
+ allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
34
+ allow(Doorkeeper.configuration).to receive(:token_reuse_limit).and_return(50)
35
+ existing_token = subject.call(client, scopes, expires_in: 1000)
36
+
37
+ allow_any_instance_of(Doorkeeper::AccessToken).to receive(:expires_in_seconds).and_return(600)
38
+ result = subject.call(client, scopes, expires_in: 1000)
39
+
40
+ expect(Doorkeeper::AccessToken.count).to eq(1)
41
+ expect(result).to eq(existing_token)
42
+ end
43
+ end
44
+
45
+ context 'when existing token has crossed token_reuse_limit' do
46
+ it "returns a new token" do
47
+ allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
48
+ allow(Doorkeeper.configuration).to receive(:token_reuse_limit).and_return(50)
49
+ existing_token = subject.call(client, scopes, expires_in: 1000)
50
+
51
+ allow_any_instance_of(Doorkeeper::AccessToken).to receive(:expires_in_seconds).and_return(400)
52
+ result = subject.call(client, scopes, expires_in: 1000)
53
+
54
+ expect(Doorkeeper::AccessToken.count).to eq(2)
55
+ expect(result).not_to eq(existing_token)
56
+ end
57
+ end
58
+
59
+ context 'when existing token has been expired' do
60
+ it "returns a new token" do
61
+ allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
62
+ allow(Doorkeeper.configuration).to receive(:token_reuse_limit).and_return(50)
63
+ existing_token = subject.call(client, scopes, expires_in: 1000)
64
+
65
+ allow_any_instance_of(Doorkeeper::AccessToken).to receive(:expired?).and_return(true)
66
+ result = subject.call(client, scopes, expires_in: 1000)
24
67
 
25
- expect(Doorkeeper::AccessToken.count).to eq(1)
26
- expect(result).to eq(existing_token)
68
+ expect(Doorkeeper::AccessToken.count).to eq(2)
69
+ expect(result).not_to eq(existing_token)
70
+ end
27
71
  end
28
72
  end
29
73
 
@@ -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')
@@ -3,7 +3,13 @@ require 'spec_helper'
3
3
  module Doorkeeper::OAuth
4
4
  describe ErrorResponse do
5
5
  describe '#status' do
6
- it 'should have a status of unauthorized' do
6
+ it 'should have a status of bad_request' do
7
+ expect(subject.status).to eq(:bad_request)
8
+ end
9
+
10
+ it 'should have a status of unauthorized for an invalid_client error' do
11
+ subject = described_class.new(name: :invalid_client)
12
+
7
13
  expect(subject.status).to eq(:unauthorized)
8
14
  end
9
15
  end
@@ -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'
@@ -64,7 +64,7 @@ module Doorkeeper::OAuth
64
64
  end.to change { Doorkeeper::AccessToken.count }.by(1)
65
65
  end
66
66
 
67
- it 'skips token creation if there is already one' do
67
+ it 'skips token creation if there is already one reusable' do
68
68
  allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
69
69
  FactoryBot.create(:access_token, application_id: client.id, resource_owner_id: owner.id)
70
70
 
@@ -73,6 +73,16 @@ module Doorkeeper::OAuth
73
73
  end.not_to(change { Doorkeeper::AccessToken.count })
74
74
  end
75
75
 
76
+ it 'creates token when there is already one but non reusable' do
77
+ allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
78
+ FactoryBot.create(:access_token, application_id: client.id, resource_owner_id: owner.id)
79
+ allow_any_instance_of(Doorkeeper::AccessToken).to receive(:reusable?).and_return(false)
80
+
81
+ expect do
82
+ subject.authorize
83
+ end.to change { Doorkeeper::AccessToken.count }.by(1)
84
+ end
85
+
76
86
  it "calls configured request callback methods" do
77
87
  expect(Doorkeeper.configuration.before_successful_strategy_response)
78
88
  .to receive(:call).with(subject).once
@@ -88,19 +98,40 @@ module Doorkeeper::OAuth
88
98
  PasswordAccessTokenRequest.new(server, client, owner, scope: 'public')
89
99
  end
90
100
 
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)
101
+ context 'when scopes_by_grant_type is not configured for grant_type' do
102
+ it 'returns error when scopes are invalid' do
103
+ allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('another'))
104
+ subject.validate
105
+ expect(subject.error).to eq(:invalid_scope)
106
+ end
107
+
108
+ it 'creates the token with scopes if scopes are valid' do
109
+ allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('public'))
110
+ expect do
111
+ subject.authorize
112
+ end.to change { Doorkeeper::AccessToken.count }.by(1)
113
+
114
+ expect(Doorkeeper::AccessToken.last.scopes).to include('public')
115
+ end
95
116
  end
96
117
 
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')
118
+ context 'when scopes_by_grant_type is configured for grant_type' do
119
+ it 'returns error when scopes are valid but not permitted for grant_type' do
120
+ allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('public'))
121
+ allow(Doorkeeper.configuration).to receive(:scopes_by_grant_type).and_return(password: 'another')
122
+ subject.validate
123
+ expect(subject.error).to eq(:invalid_scope)
124
+ end
125
+
126
+ it 'creates the token with scopes if scopes are valid and permitted for grant_type' do
127
+ allow(server).to receive(:scopes).and_return(Doorkeeper::OAuth::Scopes.from_string('public'))
128
+ allow(Doorkeeper.configuration).to receive(:scopes_by_grant_type).and_return(password: [:public])
129
+ expect do
130
+ subject.authorize
131
+ end.to change { Doorkeeper::AccessToken.count }.by(1)
132
+
133
+ expect(Doorkeeper::AccessToken.last.scopes).to include('public')
134
+ end
104
135
  end
105
136
  end
106
137
 
@@ -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
@@ -80,7 +80,7 @@ module Doorkeeper::OAuth
80
80
  end.to change { Doorkeeper::AccessToken.count }.by(1)
81
81
  end
82
82
 
83
- it 'skips token creation if there is a matching one' do
83
+ it 'skips token creation if there is a matching one reusable' do
84
84
  allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
85
85
  allow(application.scopes).to receive(:has_scopes?).and_return(true)
86
86
  allow(application.scopes).to receive(:all?).and_return(true)
@@ -90,6 +90,21 @@ module Doorkeeper::OAuth
90
90
 
91
91
  expect { subject.authorize }.not_to(change { Doorkeeper::AccessToken.count })
92
92
  end
93
+
94
+ it 'creates new token if there is a matching one but non reusable' do
95
+ allow(Doorkeeper.configuration).to receive(:reuse_access_token).and_return(true)
96
+ allow(application.scopes).to receive(:has_scopes?).and_return(true)
97
+ allow(application.scopes).to receive(:all?).and_return(true)
98
+
99
+ FactoryBot.create(:access_token, application_id: pre_auth.client.id,
100
+ resource_owner_id: owner.id, scopes: 'public')
101
+
102
+ allow_any_instance_of(Doorkeeper::AccessToken).to receive(:reusable?).and_return(false)
103
+
104
+ expect do
105
+ subject.authorize
106
+ end.to change { Doorkeeper::AccessToken.count }.by(1)
107
+ end
93
108
  end
94
109
  end
95
110
  end
@@ -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