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.
- checksums.yaml +5 -5
- data/.travis.yml +17 -4
- data/Appraisals +29 -3
- data/Dangerfile +5 -2
- data/Gemfile +14 -4
- data/NEWS.md +64 -19
- data/README.md +68 -487
- data/app/controllers/doorkeeper/token_info_controller.rb +1 -1
- data/app/controllers/doorkeeper/tokens_controller.rb +7 -7
- data/app/views/doorkeeper/applications/show.html.erb +1 -1
- data/app/views/layouts/doorkeeper/admin.html.erb +5 -3
- data/bin/console +15 -0
- data/doorkeeper.gemspec +3 -2
- data/gemfiles/rails_4_2.gemfile +8 -4
- data/gemfiles/rails_5_0.gemfile +9 -5
- data/gemfiles/rails_5_1.gemfile +9 -5
- data/gemfiles/rails_5_2.gemfile +9 -5
- data/gemfiles/rails_6_0.gemfile +16 -0
- data/gemfiles/rails_master.gemfile +8 -9
- data/lib/doorkeeper/config.rb +164 -11
- data/lib/doorkeeper/helpers/controller.rb +3 -2
- data/lib/doorkeeper/models/access_grant_mixin.rb +16 -1
- data/lib/doorkeeper/models/access_token_mixin.rb +54 -10
- data/lib/doorkeeper/models/application_mixin.rb +42 -1
- data/lib/doorkeeper/models/concerns/expirable.rb +3 -2
- data/lib/doorkeeper/models/concerns/reusable.rb +19 -0
- data/lib/doorkeeper/models/concerns/scopes.rb +5 -1
- data/lib/doorkeeper/models/concerns/secret_storable.rb +106 -0
- data/lib/doorkeeper/oauth/authorization/code.rb +1 -1
- data/lib/doorkeeper/oauth/authorization/token.rb +4 -2
- data/lib/doorkeeper/oauth/authorization_code_request.rb +1 -1
- data/lib/doorkeeper/oauth/client.rb +1 -1
- data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -3
- data/lib/doorkeeper/oauth/code_response.rb +2 -2
- data/lib/doorkeeper/oauth/error_response.rb +5 -1
- data/lib/doorkeeper/oauth/helpers/scope_checker.rb +23 -8
- data/lib/doorkeeper/oauth/helpers/unique_token.rb +12 -1
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +32 -0
- data/lib/doorkeeper/oauth/invalid_token_response.rb +4 -0
- data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -2
- data/lib/doorkeeper/oauth/pre_authorization.rb +8 -3
- data/lib/doorkeeper/oauth/refresh_token_request.rb +4 -1
- data/lib/doorkeeper/oauth/token_introspection.rb +72 -6
- data/lib/doorkeeper/oauth/token_response.rb +2 -2
- data/lib/doorkeeper/orm/active_record/access_grant.rb +23 -2
- data/lib/doorkeeper/orm/active_record/application.rb +20 -2
- data/lib/doorkeeper/secret_storing/base.rb +63 -0
- data/lib/doorkeeper/secret_storing/bcrypt.rb +59 -0
- data/lib/doorkeeper/secret_storing/plain.rb +33 -0
- data/lib/doorkeeper/secret_storing/sha256_hash.rb +25 -0
- data/lib/doorkeeper/version.rb +3 -3
- data/lib/doorkeeper.rb +7 -0
- data/lib/generators/doorkeeper/templates/initializer.rb +90 -8
- data/spec/controllers/application_metal_controller_spec.rb +18 -4
- data/spec/controllers/authorizations_controller_spec.rb +3 -3
- data/spec/controllers/token_info_controller_spec.rb +1 -1
- data/spec/controllers/tokens_controller_spec.rb +85 -41
- data/spec/dummy/app/controllers/application_controller.rb +1 -1
- data/spec/dummy/config/application.rb +12 -1
- data/spec/factories.rb +3 -3
- data/spec/lib/config_spec.rb +168 -0
- data/spec/lib/models/expirable_spec.rb +12 -0
- data/spec/lib/models/reusable_spec.rb +40 -0
- data/spec/lib/models/scopes_spec.rb +13 -1
- data/spec/lib/models/secret_storable_spec.rb +113 -0
- data/spec/lib/oauth/authorization_code_request_spec.rb +18 -1
- data/spec/lib/oauth/base_request_spec.rb +7 -7
- data/spec/lib/oauth/client_credentials/creator_spec.rb +51 -7
- data/spec/lib/oauth/client_credentials/validation_spec.rb +3 -0
- data/spec/lib/oauth/error_response_spec.rb +7 -1
- data/spec/lib/oauth/helpers/scope_checker_spec.rb +52 -17
- data/spec/lib/oauth/helpers/uri_checker_spec.rb +20 -2
- data/spec/lib/oauth/password_access_token_request_spec.rb +43 -12
- data/spec/lib/oauth/pre_authorization_spec.rb +24 -0
- data/spec/lib/oauth/token_request_spec.rb +16 -1
- data/spec/lib/oauth/token_response_spec.rb +13 -13
- data/spec/lib/oauth/token_spec.rb +14 -0
- data/spec/lib/secret_storing/base_spec.rb +60 -0
- data/spec/lib/secret_storing/bcrypt_spec.rb +49 -0
- data/spec/lib/secret_storing/plain_spec.rb +44 -0
- data/spec/lib/secret_storing/sha256_hash_spec.rb +48 -0
- data/spec/models/doorkeeper/access_grant_spec.rb +61 -0
- data/spec/models/doorkeeper/access_token_spec.rb +123 -0
- data/spec/models/doorkeeper/application_spec.rb +56 -0
- data/spec/requests/flows/authorization_code_spec.rb +41 -1
- data/spec/requests/flows/client_credentials_spec.rb +2 -2
- data/spec/requests/flows/implicit_grant_spec.rb +1 -1
- data/spec/requests/flows/password_spec.rb +7 -5
- data/spec/requests/flows/revoke_token_spec.rb +14 -30
- data/spec/routing/custom_controller_routes_spec.rb +4 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/support/ruby_2_6_rails_4_2_patch.rb +14 -0
- data/spec/support/shared/hashing_shared_context.rb +36 -0
- 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
|
-
|
|
8
|
-
expires_in:
|
|
9
|
-
expires_in_seconds:
|
|
10
|
-
scopes_string:
|
|
11
|
-
|
|
12
|
-
token_type:
|
|
13
|
-
created_at:
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
21
|
-
expires_in:
|
|
22
|
-
expires_in_seconds:
|
|
23
|
-
scopes_string:
|
|
24
|
-
|
|
25
|
-
token_type:
|
|
26
|
-
created_at:
|
|
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
|
-
|
|
62
|
-
expires_in_seconds:
|
|
63
|
-
scopes_string:
|
|
64
|
-
|
|
65
|
-
token_type:
|
|
66
|
-
created_at:
|
|
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
|