grape_oauth2 0.1.1
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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.rubocop.yml +18 -0
- data/.travis.yml +42 -0
- data/Gemfile +23 -0
- data/README.md +820 -0
- data/Rakefile +11 -0
- data/gemfiles/active_record.rb +25 -0
- data/gemfiles/mongoid.rb +14 -0
- data/gemfiles/sequel.rb +24 -0
- data/grape_oauth2.gemspec +27 -0
- data/grape_oauth2.png +0 -0
- data/lib/grape_oauth2.rb +129 -0
- data/lib/grape_oauth2/configuration.rb +143 -0
- data/lib/grape_oauth2/configuration/class_accessors.rb +36 -0
- data/lib/grape_oauth2/configuration/validation.rb +71 -0
- data/lib/grape_oauth2/endpoints/authorize.rb +34 -0
- data/lib/grape_oauth2/endpoints/token.rb +72 -0
- data/lib/grape_oauth2/gem_version.rb +24 -0
- data/lib/grape_oauth2/generators/authorization.rb +44 -0
- data/lib/grape_oauth2/generators/base.rb +26 -0
- data/lib/grape_oauth2/generators/token.rb +62 -0
- data/lib/grape_oauth2/helpers/access_token_helpers.rb +54 -0
- data/lib/grape_oauth2/helpers/oauth_params.rb +41 -0
- data/lib/grape_oauth2/mixins/active_record/access_grant.rb +47 -0
- data/lib/grape_oauth2/mixins/active_record/access_token.rb +75 -0
- data/lib/grape_oauth2/mixins/active_record/client.rb +35 -0
- data/lib/grape_oauth2/mixins/mongoid/access_grant.rb +58 -0
- data/lib/grape_oauth2/mixins/mongoid/access_token.rb +88 -0
- data/lib/grape_oauth2/mixins/mongoid/client.rb +41 -0
- data/lib/grape_oauth2/mixins/sequel/access_grant.rb +68 -0
- data/lib/grape_oauth2/mixins/sequel/access_token.rb +86 -0
- data/lib/grape_oauth2/mixins/sequel/client.rb +46 -0
- data/lib/grape_oauth2/responses/authorization.rb +10 -0
- data/lib/grape_oauth2/responses/base.rb +56 -0
- data/lib/grape_oauth2/responses/token.rb +10 -0
- data/lib/grape_oauth2/scopes.rb +74 -0
- data/lib/grape_oauth2/strategies/authorization_code.rb +38 -0
- data/lib/grape_oauth2/strategies/base.rb +47 -0
- data/lib/grape_oauth2/strategies/client_credentials.rb +20 -0
- data/lib/grape_oauth2/strategies/password.rb +22 -0
- data/lib/grape_oauth2/strategies/refresh_token.rb +47 -0
- data/lib/grape_oauth2/unique_token.rb +20 -0
- data/lib/grape_oauth2/version.rb +14 -0
- data/spec/configuration/config_spec.rb +231 -0
- data/spec/configuration/version_spec.rb +12 -0
- data/spec/dummy/endpoints/custom_authorization.rb +25 -0
- data/spec/dummy/endpoints/custom_token.rb +35 -0
- data/spec/dummy/endpoints/status.rb +25 -0
- data/spec/dummy/grape_oauth2_config.rb +11 -0
- data/spec/dummy/orm/active_record/app/config/db.rb +7 -0
- data/spec/dummy/orm/active_record/app/models/access_code.rb +3 -0
- data/spec/dummy/orm/active_record/app/models/access_token.rb +3 -0
- data/spec/dummy/orm/active_record/app/models/application.rb +3 -0
- data/spec/dummy/orm/active_record/app/models/application_record.rb +3 -0
- data/spec/dummy/orm/active_record/app/models/user.rb +10 -0
- data/spec/dummy/orm/active_record/app/twitter.rb +36 -0
- data/spec/dummy/orm/active_record/config.ru +7 -0
- data/spec/dummy/orm/active_record/db/schema.rb +53 -0
- data/spec/dummy/orm/mongoid/app/config/db.rb +6 -0
- data/spec/dummy/orm/mongoid/app/config/mongoid.yml +21 -0
- data/spec/dummy/orm/mongoid/app/models/access_code.rb +3 -0
- data/spec/dummy/orm/mongoid/app/models/access_token.rb +3 -0
- data/spec/dummy/orm/mongoid/app/models/application.rb +3 -0
- data/spec/dummy/orm/mongoid/app/models/user.rb +11 -0
- data/spec/dummy/orm/mongoid/app/twitter.rb +34 -0
- data/spec/dummy/orm/mongoid/config.ru +5 -0
- data/spec/dummy/orm/sequel/app/config/db.rb +1 -0
- data/spec/dummy/orm/sequel/app/models/access_code.rb +4 -0
- data/spec/dummy/orm/sequel/app/models/access_token.rb +4 -0
- data/spec/dummy/orm/sequel/app/models/application.rb +4 -0
- data/spec/dummy/orm/sequel/app/models/application_record.rb +2 -0
- data/spec/dummy/orm/sequel/app/models/user.rb +11 -0
- data/spec/dummy/orm/sequel/app/twitter.rb +47 -0
- data/spec/dummy/orm/sequel/config.ru +5 -0
- data/spec/dummy/orm/sequel/db/schema.rb +50 -0
- data/spec/lib/scopes_spec.rb +50 -0
- data/spec/mixins/active_record/access_token_spec.rb +185 -0
- data/spec/mixins/active_record/client_spec.rb +95 -0
- data/spec/mixins/mongoid/access_token_spec.rb +185 -0
- data/spec/mixins/mongoid/client_spec.rb +95 -0
- data/spec/mixins/sequel/access_token_spec.rb +185 -0
- data/spec/mixins/sequel/client_spec.rb +96 -0
- data/spec/requests/flows/authorization_code_spec.rb +67 -0
- data/spec/requests/flows/client_credentials_spec.rb +101 -0
- data/spec/requests/flows/password_spec.rb +210 -0
- data/spec/requests/flows/refresh_token_spec.rb +222 -0
- data/spec/requests/flows/revoke_token_spec.rb +103 -0
- data/spec/requests/protected_resources_spec.rb +64 -0
- data/spec/spec_helper.rb +60 -0
- data/spec/support/api_helper.rb +11 -0
- metadata +257 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'Token Endpoint' do
|
|
4
|
+
let(:application) { Application.create(name: 'App1') }
|
|
5
|
+
let(:user) { User.create(username: 'test', password: '12345678') }
|
|
6
|
+
|
|
7
|
+
describe 'Resource Owner Password Credentials flow' do
|
|
8
|
+
describe 'POST /oauth/token' do
|
|
9
|
+
let(:authentication_url) { '/api/v1/oauth/token' }
|
|
10
|
+
|
|
11
|
+
context 'with valid params' do
|
|
12
|
+
context 'when request is invalid' do
|
|
13
|
+
it 'fails without Grant Type' do
|
|
14
|
+
post authentication_url,
|
|
15
|
+
username: user.username,
|
|
16
|
+
password: '12345678',
|
|
17
|
+
client_id: application.key,
|
|
18
|
+
client_secret: application.secret
|
|
19
|
+
|
|
20
|
+
expect(AccessToken.all).to be_empty
|
|
21
|
+
|
|
22
|
+
expect(json_body[:error]).to eq('invalid_request')
|
|
23
|
+
expect(last_response.status).to eq 400
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'fails with invalid Grant Type' do
|
|
27
|
+
post authentication_url,
|
|
28
|
+
grant_type: 'invalid',
|
|
29
|
+
username: user.username,
|
|
30
|
+
password: '12345678'
|
|
31
|
+
|
|
32
|
+
expect(AccessToken.all).to be_empty
|
|
33
|
+
|
|
34
|
+
expect(json_body[:error]).to eq('unsupported_grant_type')
|
|
35
|
+
expect(last_response.status).to eq 400
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'fails without Client Credentials' do
|
|
39
|
+
post authentication_url,
|
|
40
|
+
grant_type: 'password',
|
|
41
|
+
username: user.username,
|
|
42
|
+
password: '12345678'
|
|
43
|
+
|
|
44
|
+
expect(AccessToken.all).to be_empty
|
|
45
|
+
|
|
46
|
+
expect(json_body[:error]).to eq('invalid_request')
|
|
47
|
+
expect(last_response.status).to eq 400
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'fails with invalid Client Credentials' do
|
|
51
|
+
post authentication_url,
|
|
52
|
+
grant_type: 'password',
|
|
53
|
+
username: user.username,
|
|
54
|
+
password: '12345678',
|
|
55
|
+
client_id: 'blah-blah',
|
|
56
|
+
client_secret: application.secret
|
|
57
|
+
|
|
58
|
+
expect(AccessToken.all).to be_empty
|
|
59
|
+
|
|
60
|
+
expect(json_body[:error]).to eq('invalid_client')
|
|
61
|
+
expect(last_response.status).to eq 401
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'fails without Resource Owner credentials' do
|
|
65
|
+
post authentication_url,
|
|
66
|
+
grant_type: 'password',
|
|
67
|
+
client_id: application.key,
|
|
68
|
+
client_secret: application.secret
|
|
69
|
+
|
|
70
|
+
expect(json_body[:error]).to eq('invalid_request')
|
|
71
|
+
expect(json_body[:error_description]).not_to be_blank
|
|
72
|
+
expect(last_response.status).to eq 400
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'fails with invalid Resource Owner credentials' do
|
|
76
|
+
post authentication_url,
|
|
77
|
+
grant_type: 'password',
|
|
78
|
+
username: 'invalid@example.com',
|
|
79
|
+
password: 'invalid',
|
|
80
|
+
client_id: application.key,
|
|
81
|
+
client_secret: application.secret
|
|
82
|
+
|
|
83
|
+
expect(json_body[:error]).to eq('invalid_grant')
|
|
84
|
+
expect(json_body[:error_description]).not_to be_blank
|
|
85
|
+
expect(last_response.status).to eq 400
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
context 'with valid data' do
|
|
90
|
+
context 'when scopes requested' do
|
|
91
|
+
it 'returns an Access Token with scopes' do
|
|
92
|
+
post authentication_url,
|
|
93
|
+
grant_type: 'password',
|
|
94
|
+
username: user.username,
|
|
95
|
+
password: '12345678',
|
|
96
|
+
scope: 'read write',
|
|
97
|
+
client_id: application.key,
|
|
98
|
+
client_secret: application.secret
|
|
99
|
+
|
|
100
|
+
expect(AccessToken.count).to eq 1
|
|
101
|
+
expect(AccessToken.first.client_id).to eq application.id
|
|
102
|
+
|
|
103
|
+
expect(json_body[:access_token]).to be_present
|
|
104
|
+
expect(json_body[:token_type]).to eq 'bearer'
|
|
105
|
+
expect(json_body[:expires_in]).to eq 7200
|
|
106
|
+
expect(json_body[:refresh_token]).to be_nil
|
|
107
|
+
expect(json_body[:scope]).to eq('read write')
|
|
108
|
+
|
|
109
|
+
expect(last_response.status).to eq 200
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context 'without scopes' do
|
|
114
|
+
it 'returns an Access Token without scopes' do
|
|
115
|
+
post authentication_url,
|
|
116
|
+
grant_type: 'password',
|
|
117
|
+
username: user.username,
|
|
118
|
+
password: '12345678',
|
|
119
|
+
client_id: application.key,
|
|
120
|
+
client_secret: application.secret
|
|
121
|
+
|
|
122
|
+
expect(AccessToken.count).to eq 1
|
|
123
|
+
expect(AccessToken.first.client_id).to eq application.id
|
|
124
|
+
|
|
125
|
+
expect(json_body[:access_token]).to be_present
|
|
126
|
+
expect(json_body[:token_type]).to eq 'bearer'
|
|
127
|
+
expect(json_body[:expires_in]).to eq 7200
|
|
128
|
+
expect(json_body[:refresh_token]).to be_nil
|
|
129
|
+
expect(json_body[:scope]).to be_nil
|
|
130
|
+
|
|
131
|
+
expect(last_response.status).to eq 200
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
context 'when Token endpoint not mounted' do
|
|
137
|
+
before do
|
|
138
|
+
Twitter::API.reset!
|
|
139
|
+
Twitter::API.change!
|
|
140
|
+
|
|
141
|
+
# Mount only Authorization Endpoint
|
|
142
|
+
Twitter::API.send(:include, Grape::OAuth2.api(:authorize))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
after do
|
|
146
|
+
Twitter::API.reset!
|
|
147
|
+
Twitter::API.change!
|
|
148
|
+
|
|
149
|
+
Twitter::API.send(:include, Grape::OAuth2.api)
|
|
150
|
+
Twitter::API.mount(Twitter::Endpoints::Status)
|
|
151
|
+
Twitter::API.mount(Twitter::Endpoints::CustomToken)
|
|
152
|
+
Twitter::API.mount(Twitter::Endpoints::CustomAuthorization)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'returns 404' do
|
|
156
|
+
post authentication_url,
|
|
157
|
+
grant_type: 'password',
|
|
158
|
+
username: user.username,
|
|
159
|
+
password: '12345678',
|
|
160
|
+
client_id: application.key,
|
|
161
|
+
client_secret: application.secret
|
|
162
|
+
|
|
163
|
+
expect(last_response.status).to eq 404
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
describe 'POST /oauth/custom_token' do
|
|
171
|
+
context 'when block processed successfully' do
|
|
172
|
+
it 'returns an access token' do
|
|
173
|
+
application.update(name: 'Admin')
|
|
174
|
+
|
|
175
|
+
post '/api/v1/oauth/custom_token',
|
|
176
|
+
grant_type: 'password',
|
|
177
|
+
username: user.username,
|
|
178
|
+
password: '12345678',
|
|
179
|
+
client_id: application.key,
|
|
180
|
+
client_secret: application.secret
|
|
181
|
+
|
|
182
|
+
expect(last_response.status).to eq 200
|
|
183
|
+
|
|
184
|
+
expect(AccessToken.count).to eq 1
|
|
185
|
+
expect(AccessToken.first.client_id).to eq application.id
|
|
186
|
+
|
|
187
|
+
expect(json_body[:access_token]).to be_present
|
|
188
|
+
expect(json_body[:token_type]).to eq 'bearer'
|
|
189
|
+
expect(json_body[:expires_in]).to eq 7200
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
context 'when authentication failed' do
|
|
194
|
+
it 'returns an error' do
|
|
195
|
+
application.update(name: 'Admin')
|
|
196
|
+
|
|
197
|
+
post '/api/v1/oauth/custom_token',
|
|
198
|
+
grant_type: 'password',
|
|
199
|
+
username: 'invalid@example.com',
|
|
200
|
+
password: 'invalid',
|
|
201
|
+
client_id: application.key,
|
|
202
|
+
client_secret: application.secret
|
|
203
|
+
|
|
204
|
+
expect(json_body[:error]).to eq('invalid_grant')
|
|
205
|
+
expect(json_body[:error_description]).not_to be_blank
|
|
206
|
+
expect(last_response.status).to eq 400
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'Token Endpoint' do
|
|
4
|
+
describe 'POST /oauth/token' do
|
|
5
|
+
describe 'Refresh Token flow' do
|
|
6
|
+
context 'with valid params' do
|
|
7
|
+
let(:api_url) { '/api/v1/oauth/token' }
|
|
8
|
+
let(:application) { Application.create(name: 'App1') }
|
|
9
|
+
let(:user) { User.create(username: 'test', password: '12345678') }
|
|
10
|
+
|
|
11
|
+
context 'when request is invalid' do
|
|
12
|
+
it 'fails without Grant Type' do
|
|
13
|
+
post api_url,
|
|
14
|
+
client_id: application.key,
|
|
15
|
+
client_secret: application.secret
|
|
16
|
+
|
|
17
|
+
expect(AccessToken.all).to be_empty
|
|
18
|
+
|
|
19
|
+
expect(json_body[:error]).to eq('invalid_request')
|
|
20
|
+
expect(last_response.status).to eq 400
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'fails with invalid Grant Type' do
|
|
24
|
+
post api_url,
|
|
25
|
+
grant_type: 'invalid'
|
|
26
|
+
|
|
27
|
+
expect(AccessToken.all).to be_empty
|
|
28
|
+
|
|
29
|
+
expect(json_body[:error]).to eq('unsupported_grant_type')
|
|
30
|
+
expect(last_response.status).to eq 400
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'fails without Client Credentials' do
|
|
34
|
+
post api_url,
|
|
35
|
+
grant_type: 'refresh_token'
|
|
36
|
+
|
|
37
|
+
expect(AccessToken.all).to be_empty
|
|
38
|
+
|
|
39
|
+
expect(json_body[:error]).to eq('invalid_request')
|
|
40
|
+
expect(last_response.status).to eq 400
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'fails with invalid Client Credentials' do
|
|
44
|
+
post api_url,
|
|
45
|
+
grant_type: 'refresh_token',
|
|
46
|
+
refresh_token: SecureRandom.hex(6),
|
|
47
|
+
client_id: 'blah-blah',
|
|
48
|
+
client_secret: application.secret
|
|
49
|
+
|
|
50
|
+
expect(AccessToken.all).to be_empty
|
|
51
|
+
|
|
52
|
+
expect(json_body[:error]).to eq('invalid_client')
|
|
53
|
+
expect(last_response.status).to eq 401
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'fails when Access Token was issued to another client' do
|
|
57
|
+
allow(Grape::OAuth2.config).to receive(:issue_refresh_token).and_return(true)
|
|
58
|
+
|
|
59
|
+
another_client = Application.create(name: 'Some')
|
|
60
|
+
token = AccessToken.create_for(another_client, user)
|
|
61
|
+
expect(token.refresh_token).not_to be_nil
|
|
62
|
+
|
|
63
|
+
post api_url,
|
|
64
|
+
grant_type: 'refresh_token',
|
|
65
|
+
refresh_token: token.refresh_token,
|
|
66
|
+
client_id: application.key,
|
|
67
|
+
client_secret: application.secret
|
|
68
|
+
|
|
69
|
+
expect(json_body[:error]).to eq('unauthorized_client')
|
|
70
|
+
expect(last_response.status).to eq 400
|
|
71
|
+
|
|
72
|
+
expect(AccessToken.count).to eq(1)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context 'with valid data' do
|
|
77
|
+
before { allow(Grape::OAuth2.config).to receive(:issue_refresh_token).and_return(true) }
|
|
78
|
+
|
|
79
|
+
it 'returns a new Access Token' do
|
|
80
|
+
token = AccessToken.create_for(application, user)
|
|
81
|
+
expect(token.refresh_token).not_to be_nil
|
|
82
|
+
|
|
83
|
+
post api_url,
|
|
84
|
+
grant_type: 'refresh_token',
|
|
85
|
+
refresh_token: token.refresh_token,
|
|
86
|
+
client_id: application.key,
|
|
87
|
+
client_secret: application.secret
|
|
88
|
+
|
|
89
|
+
expect(last_response.status).to eq 200
|
|
90
|
+
|
|
91
|
+
expect(AccessToken.count).to eq 2
|
|
92
|
+
expect(AccessToken.last.client_id).to eq application.id
|
|
93
|
+
expect(AccessToken.last.resource_owner_id).to eq user.id
|
|
94
|
+
|
|
95
|
+
expect(json_body[:access_token]).to eq AccessToken.last.token
|
|
96
|
+
expect(json_body[:token_type]).to eq 'bearer'
|
|
97
|
+
expect(json_body[:expires_in]).to eq 7200
|
|
98
|
+
expect(json_body[:refresh_token]).to eq AccessToken.last.refresh_token
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'revokes old Access Token if it is configured' do
|
|
102
|
+
allow(Grape::OAuth2.config).to receive(:on_refresh).and_return(:revoke!)
|
|
103
|
+
|
|
104
|
+
token = AccessToken.create_for(application, user)
|
|
105
|
+
expect(token.refresh_token).not_to be_nil
|
|
106
|
+
|
|
107
|
+
post api_url,
|
|
108
|
+
grant_type: 'refresh_token',
|
|
109
|
+
refresh_token: token.refresh_token,
|
|
110
|
+
client_id: application.key,
|
|
111
|
+
client_secret: application.secret
|
|
112
|
+
|
|
113
|
+
expect(last_response.status).to eq 200
|
|
114
|
+
|
|
115
|
+
expect(AccessToken.count).to eq 2
|
|
116
|
+
expect(AccessToken.last.client).to eq application
|
|
117
|
+
expect(AccessToken.last.resource_owner).to eq user
|
|
118
|
+
|
|
119
|
+
expect(token.reload.revoked?).to be_truthy
|
|
120
|
+
|
|
121
|
+
expect(json_body[:access_token]).to eq AccessToken.last.token
|
|
122
|
+
expect(json_body[:refresh_token]).to eq AccessToken.last.refresh_token
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'destroy old Access Token if it is configured' do
|
|
126
|
+
allow(Grape::OAuth2.config).to receive(:on_refresh).and_return(:destroy)
|
|
127
|
+
|
|
128
|
+
token = AccessToken.create_for(application, user)
|
|
129
|
+
expect(token.refresh_token).not_to be_nil
|
|
130
|
+
|
|
131
|
+
post api_url,
|
|
132
|
+
grant_type: 'refresh_token',
|
|
133
|
+
refresh_token: token.refresh_token,
|
|
134
|
+
client_id: application.key,
|
|
135
|
+
client_secret: application.secret
|
|
136
|
+
|
|
137
|
+
expect(last_response.status).to eq 200
|
|
138
|
+
|
|
139
|
+
expect(AccessToken.count).to eq 1
|
|
140
|
+
expect(AccessToken.where(token: token.token).first).to be_nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'calls custom block on token refresh if it is configured' do
|
|
144
|
+
scopes = 'just for test'
|
|
145
|
+
allow(Grape::OAuth2.config).to receive(:on_refresh).and_return(->(token) { token.update(scopes: scopes) })
|
|
146
|
+
|
|
147
|
+
token = AccessToken.create_for(application, user)
|
|
148
|
+
expect(token.refresh_token).not_to be_nil
|
|
149
|
+
|
|
150
|
+
post api_url,
|
|
151
|
+
grant_type: 'refresh_token',
|
|
152
|
+
refresh_token: token.refresh_token,
|
|
153
|
+
client_id: application.key,
|
|
154
|
+
client_secret: application.secret
|
|
155
|
+
|
|
156
|
+
expect(last_response.status).to eq 200
|
|
157
|
+
|
|
158
|
+
expect(AccessToken.count).to eq 2
|
|
159
|
+
expect(token.reload.scopes).to eq(scopes)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it 'does nothing on token refresh if :on_refresh is equal to :nothing or nil' do
|
|
163
|
+
allow(Grape::OAuth2.config).to receive(:on_refresh).and_return(:nothing)
|
|
164
|
+
|
|
165
|
+
token = AccessToken.create_for(application, user)
|
|
166
|
+
expect(token.refresh_token).not_to be_nil
|
|
167
|
+
|
|
168
|
+
# Check for :nothing
|
|
169
|
+
expect(Grape::OAuth2::Strategies::RefreshToken).not_to receive(:run_on_refresh_callback)
|
|
170
|
+
|
|
171
|
+
post api_url,
|
|
172
|
+
grant_type: 'refresh_token',
|
|
173
|
+
refresh_token: token.refresh_token,
|
|
174
|
+
client_id: application.key,
|
|
175
|
+
client_secret: application.secret
|
|
176
|
+
|
|
177
|
+
expect(last_response.status).to eq 200
|
|
178
|
+
|
|
179
|
+
allow(Grape::OAuth2.config).to receive(:on_refresh).and_return(nil)
|
|
180
|
+
|
|
181
|
+
token = AccessToken.create_for(application, user)
|
|
182
|
+
expect(token.refresh_token).not_to be_nil
|
|
183
|
+
|
|
184
|
+
# Check for nil
|
|
185
|
+
expect(Grape::OAuth2::Strategies::RefreshToken).not_to receive(:run_on_refresh_callback)
|
|
186
|
+
|
|
187
|
+
post api_url,
|
|
188
|
+
grant_type: 'refresh_token',
|
|
189
|
+
refresh_token: token.refresh_token,
|
|
190
|
+
client_id: application.key,
|
|
191
|
+
client_secret: application.secret
|
|
192
|
+
|
|
193
|
+
expect(last_response.status).to eq 200
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'returns a new Access Token even if used token is expired' do
|
|
197
|
+
token = AccessToken.create_for(application, user)
|
|
198
|
+
token.update(expires_at: Time.now - 604800) # - 7 days
|
|
199
|
+
expect(token.refresh_token).not_to be_nil
|
|
200
|
+
|
|
201
|
+
post api_url,
|
|
202
|
+
grant_type: 'refresh_token',
|
|
203
|
+
refresh_token: token.refresh_token,
|
|
204
|
+
client_id: application.key,
|
|
205
|
+
client_secret: application.secret
|
|
206
|
+
|
|
207
|
+
expect(last_response.status).to eq 200
|
|
208
|
+
|
|
209
|
+
expect(AccessToken.count).to eq 2
|
|
210
|
+
expect(AccessToken.last.client_id).to eq application.id
|
|
211
|
+
expect(AccessToken.last.resource_owner_id).to eq user.id
|
|
212
|
+
|
|
213
|
+
expect(json_body[:access_token]).to eq AccessToken.last.token
|
|
214
|
+
expect(json_body[:token_type]).to eq 'bearer'
|
|
215
|
+
expect(json_body[:expires_in]).to eq 7200
|
|
216
|
+
expect(json_body[:refresh_token]).to eq AccessToken.last.refresh_token
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'Token Endpoint' do
|
|
4
|
+
describe 'POST /oauth/revoke' do
|
|
5
|
+
describe 'Revoke Token flow' do
|
|
6
|
+
context 'with valid params' do
|
|
7
|
+
let(:api_url) { '/api/v1/oauth/revoke' }
|
|
8
|
+
let(:application) { Application.create(name: 'App1') }
|
|
9
|
+
let(:user) { User.create(username: 'test', password: '12345678') }
|
|
10
|
+
|
|
11
|
+
let(:headers) { { 'HTTP_AUTHORIZATION' => ('Basic ' + Base64::encode64("#{application.key}:#{application.secret}")) } }
|
|
12
|
+
|
|
13
|
+
describe 'for public token' do
|
|
14
|
+
context 'when request is invalid' do
|
|
15
|
+
before { AccessToken.create_for(application, user) }
|
|
16
|
+
|
|
17
|
+
it 'does nothing' do
|
|
18
|
+
expect {
|
|
19
|
+
post api_url, { token: 'invalid token' }, headers
|
|
20
|
+
}.not_to change { AccessToken.count }
|
|
21
|
+
|
|
22
|
+
expect(json_body).to eq({})
|
|
23
|
+
expect(last_response.status).to eq 200
|
|
24
|
+
|
|
25
|
+
expect(AccessToken.last).not_to be_revoked
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'returns an error with invalid token_type_hint' do
|
|
29
|
+
expect {
|
|
30
|
+
post api_url, { token: AccessToken.last.token, token_type_hint: 'undefined' }, headers
|
|
31
|
+
}.not_to change { AccessToken.count }
|
|
32
|
+
|
|
33
|
+
expect(last_response.status).to eq 400
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context 'with valid data' do
|
|
38
|
+
# Token doesn't belongs to anybody
|
|
39
|
+
before { AccessToken.create_for(nil, nil) }
|
|
40
|
+
|
|
41
|
+
it 'revokes Access Token by its token' do
|
|
42
|
+
expect {
|
|
43
|
+
post api_url, { token: AccessToken.last.token }, headers
|
|
44
|
+
}.to change { AccessToken.where(revoked_at: nil).count }.from(1).to(0)
|
|
45
|
+
|
|
46
|
+
expect(json_body).to eq({})
|
|
47
|
+
expect(last_response.status).to eq 200
|
|
48
|
+
|
|
49
|
+
expect(AccessToken.last).to be_revoked
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'revokes Access Token by its refresh token' do
|
|
53
|
+
refresh_token = SecureRandom.hex(16)
|
|
54
|
+
AccessToken.last.update(refresh_token: refresh_token)
|
|
55
|
+
|
|
56
|
+
expect {
|
|
57
|
+
post api_url, { token: refresh_token, token_type_hint: 'refresh_token' }, headers
|
|
58
|
+
}.to change { AccessToken.where(revoked_at: nil).count }.from(1).to(0)
|
|
59
|
+
|
|
60
|
+
expect(json_body).to eq({})
|
|
61
|
+
expect(last_response.status).to eq 200
|
|
62
|
+
|
|
63
|
+
expect(AccessToken.last).to be_revoked
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe 'for private token' do
|
|
69
|
+
before { AccessToken.create_for(application, user) }
|
|
70
|
+
|
|
71
|
+
context 'with valid data' do
|
|
72
|
+
it 'revokes token with client authorization' do
|
|
73
|
+
expect {
|
|
74
|
+
post api_url, { token: AccessToken.last.token}, headers
|
|
75
|
+
}.to change { AccessToken.where(revoked_at: nil).count }.from(1).to(0)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context 'with invalid data' do
|
|
80
|
+
it 'does not revokes Access Token when credentials is invalid' do
|
|
81
|
+
expect {
|
|
82
|
+
post api_url, token: AccessToken.last.token
|
|
83
|
+
}.to_not change { AccessToken.where(revoked_at: nil).count }
|
|
84
|
+
|
|
85
|
+
expect(json_body[:error]).to eq('invalid_client')
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'does not revokes Access Token when token was issued to another client' do
|
|
89
|
+
another_client = Application.create(name: 'Some')
|
|
90
|
+
AccessToken.last.update(client_id: another_client.id)
|
|
91
|
+
|
|
92
|
+
expect {
|
|
93
|
+
post api_url, token: AccessToken.last.token
|
|
94
|
+
}.to_not change { AccessToken.where(revoked_at: nil).count }
|
|
95
|
+
|
|
96
|
+
expect(json_body[:error]).to eq('invalid_client')
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|