simple_oauth2 0.0.0
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/.codeclimate.yml +25 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +26 -0
- data/.hound.yml +4 -0
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +12 -0
- data/.travis.yml +31 -0
- data/Gemfile +18 -0
- data/LICENSE +21 -0
- data/README.md +11 -0
- data/Rakefile +11 -0
- data/gemfiles/nobrainer.rb +15 -0
- data/lib/simple_oauth2/configuration/class_accessors.rb +36 -0
- data/lib/simple_oauth2/configuration/constants.rb +36 -0
- data/lib/simple_oauth2/configuration.rb +169 -0
- data/lib/simple_oauth2/generators/authorization.rb +64 -0
- data/lib/simple_oauth2/generators/base.rb +31 -0
- data/lib/simple_oauth2/generators/token.rb +71 -0
- data/lib/simple_oauth2/helpers.rb +40 -0
- data/lib/simple_oauth2/mixins/nobrainer/access_grant.rb +62 -0
- data/lib/simple_oauth2/mixins/nobrainer/access_token.rb +98 -0
- data/lib/simple_oauth2/mixins/nobrainer/client.rb +43 -0
- data/lib/simple_oauth2/resource/bearer.rb +20 -0
- data/lib/simple_oauth2/responses.rb +62 -0
- data/lib/simple_oauth2/scopes.rb +59 -0
- data/lib/simple_oauth2/strategies/authorization_code.rb +22 -0
- data/lib/simple_oauth2/strategies/base.rb +61 -0
- data/lib/simple_oauth2/strategies/client_credentials.rb +21 -0
- data/lib/simple_oauth2/strategies/code.rb +25 -0
- data/lib/simple_oauth2/strategies/password.rb +21 -0
- data/lib/simple_oauth2/strategies/refresh_token.rb +53 -0
- data/lib/simple_oauth2/strategies/token.rb +24 -0
- data/lib/simple_oauth2/uniq_token.rb +20 -0
- data/lib/simple_oauth2/version.rb +26 -0
- data/lib/simple_oauth2.rb +62 -0
- data/logo.png +0 -0
- data/simple_oauth2.gemspec +22 -0
- data/spec/configuration/config_spec.rb +181 -0
- data/spec/configuration/version_spec.rb +11 -0
- data/spec/dummy/endpoints/authorization.rb +15 -0
- data/spec/dummy/endpoints/custom_authorization.rb +21 -0
- data/spec/dummy/endpoints/custom_token.rb +21 -0
- data/spec/dummy/endpoints/status.rb +51 -0
- data/spec/dummy/endpoints/token.rb +22 -0
- data/spec/dummy/orm/nobrainer/app/config/db.rb +8 -0
- data/spec/dummy/orm/nobrainer/app/models/access_grant.rb +3 -0
- data/spec/dummy/orm/nobrainer/app/models/access_token.rb +3 -0
- data/spec/dummy/orm/nobrainer/app/models/client.rb +3 -0
- data/spec/dummy/orm/nobrainer/app/models/user.rb +11 -0
- data/spec/dummy/orm/nobrainer/app/twitter.rb +51 -0
- data/spec/dummy/orm/nobrainer/config.ru +37 -0
- data/spec/dummy/simple_oauth2_config.rb +7 -0
- data/spec/requests/flows/authorization_code_spec.rb +177 -0
- data/spec/requests/flows/client_credentials_spec.rb +163 -0
- data/spec/requests/flows/code_spec.rb +98 -0
- data/spec/requests/flows/password_spec.rb +183 -0
- data/spec/requests/flows/refresh_token_spec.rb +282 -0
- data/spec/requests/flows/token_spec.rb +113 -0
- data/spec/requests/protected_resources_spec.rb +65 -0
- data/spec/requests/revoke_token_spec.rb +90 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/helper.rb +11 -0
- metadata +125 -0
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Token Endpoint' do
|
4
|
+
subject { -> { post url, params } }
|
5
|
+
|
6
|
+
let(:url) { '/oauth/token' }
|
7
|
+
let(:client) { Client.create(name: FFaker::Internet.domain_word, redirect_uri: 'http://localhost:3000/home') }
|
8
|
+
let(:user) { User.create(username: FFaker::Internet.user_name, encrypted_password: FFaker::Internet.password) }
|
9
|
+
let(:client_id) { client.key }
|
10
|
+
let(:client_secret) { client.secret }
|
11
|
+
let(:grant_type) { 'refresh_token' }
|
12
|
+
let(:scopes) { nil }
|
13
|
+
let(:access_token) { AccessToken.create_for(client, user, scopes) }
|
14
|
+
let(:refresh_token) { access_token.refresh_token }
|
15
|
+
let(:params) do
|
16
|
+
{
|
17
|
+
client_id: client_id,
|
18
|
+
client_secret: client_secret,
|
19
|
+
refresh_token: refresh_token,
|
20
|
+
grant_type: grant_type,
|
21
|
+
scope: scopes
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'POST /oauth/token' do
|
26
|
+
describe 'RefreshToken flow' do
|
27
|
+
context 'when valid params' do
|
28
|
+
context 'return a new Access Token' do
|
29
|
+
before do
|
30
|
+
allow(Simple::OAuth2.config).to receive(:issue_refresh_token).and_return(true)
|
31
|
+
subject.call
|
32
|
+
end
|
33
|
+
|
34
|
+
it { expect(last_response.status).to eq 200 }
|
35
|
+
|
36
|
+
it { expect(AccessToken.count).to eq 2 }
|
37
|
+
it { expect(AccessToken.last.client_id).to eq client.id }
|
38
|
+
it { expect(AccessToken.last.resource_owner_id).to eq user.id }
|
39
|
+
it { expect(AccessToken.last.scopes).to be_empty }
|
40
|
+
|
41
|
+
it { expect(json_body[:access_token]).to eq AccessToken.last.token }
|
42
|
+
it { expect(json_body[:token_type]).to eq 'bearer' }
|
43
|
+
it { expect(json_body[:expires_in]).to eq 7200 }
|
44
|
+
it { expect(json_body[:refresh_token]).to eq AccessToken.last.refresh_token }
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'returns a new Access Token even if used token is expired' do
|
48
|
+
before do
|
49
|
+
allow(Simple::OAuth2.config).to receive(:issue_refresh_token).and_return(true)
|
50
|
+
access_token.update(expires_at: Time.now - 604_800) # - 7 days
|
51
|
+
subject.call
|
52
|
+
end
|
53
|
+
|
54
|
+
it { expect(access_token.refresh_token).not_to be_nil }
|
55
|
+
it { expect(last_response.status).to eq 200 }
|
56
|
+
|
57
|
+
it { expect(AccessToken.count).to eq 2 }
|
58
|
+
it { expect(AccessToken.last.client_id).to eq client.id }
|
59
|
+
it { expect(AccessToken.last.resource_owner_id).to eq user.id }
|
60
|
+
it { expect(AccessToken.last.scopes).to be_empty }
|
61
|
+
|
62
|
+
it { expect(json_body[:access_token]).to eq AccessToken.last.token }
|
63
|
+
it { expect(json_body[:token_type]).to eq 'bearer' }
|
64
|
+
it { expect(json_body[:expires_in]).to eq 7200 }
|
65
|
+
it { expect(json_body[:refresh_token]).to eq AccessToken.last.refresh_token }
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'return a new Access Token with scopes' do
|
69
|
+
before do
|
70
|
+
allow(Simple::OAuth2.config).to receive(:issue_refresh_token).and_return(true)
|
71
|
+
subject.call
|
72
|
+
end
|
73
|
+
|
74
|
+
let(:scopes) { 'read' }
|
75
|
+
|
76
|
+
it { expect(AccessToken.count).to eq 2 }
|
77
|
+
it { expect(AccessToken.last.scopes).to eq 'read' }
|
78
|
+
|
79
|
+
it { expect(json_body[:scope]).to eq 'read' }
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'revokes old Access Token if it is configured' do
|
83
|
+
before do
|
84
|
+
allow(Simple::OAuth2.config).to receive(:issue_refresh_token).and_return(true)
|
85
|
+
allow(Simple::OAuth2.config).to receive(:on_refresh).and_return(:revoke!)
|
86
|
+
subject.call
|
87
|
+
end
|
88
|
+
|
89
|
+
it { expect(last_response.status).to eq 200 }
|
90
|
+
|
91
|
+
it { expect(AccessToken.count).to eq 2 }
|
92
|
+
it { expect(AccessToken.last.client_id).to eq client.id }
|
93
|
+
it { expect(AccessToken.last.resource_owner_id).to eq user.id }
|
94
|
+
|
95
|
+
it { expect(access_token.reload.revoked?).to be_truthy }
|
96
|
+
|
97
|
+
it { expect(json_body[:access_token]).to eq AccessToken.last.token }
|
98
|
+
it { expect(json_body[:refresh_token]).to eq AccessToken.last.refresh_token }
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'destroy old Access Token if it is configured' do
|
102
|
+
before do
|
103
|
+
allow(Simple::OAuth2.config).to receive(:issue_refresh_token).and_return(true)
|
104
|
+
allow(Simple::OAuth2.config).to receive(:on_refresh).and_return(:destroy)
|
105
|
+
subject.call
|
106
|
+
end
|
107
|
+
|
108
|
+
it { expect(last_response.status).to eq 200 }
|
109
|
+
|
110
|
+
it { expect(AccessToken.count).to eq 1 }
|
111
|
+
it { expect(AccessToken.where(token: access_token.token).first).to be_nil }
|
112
|
+
|
113
|
+
it { expect(json_body[:access_token]).to eq AccessToken.last.token }
|
114
|
+
it { expect(json_body[:token_type]).to eq 'bearer' }
|
115
|
+
it { expect(json_body[:expires_in]).to eq 7200 }
|
116
|
+
it { expect(json_body[:refresh_token]).to eq AccessToken.last.refresh_token }
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'calls custom block on token refresh if it is configured' do
|
120
|
+
before do
|
121
|
+
allow(Simple::OAuth2.config).to receive(:issue_refresh_token).and_return(true)
|
122
|
+
allow(Simple::OAuth2.config).to receive(:on_refresh).and_return(
|
123
|
+
->(token) { token.update(scopes: scopes) }
|
124
|
+
)
|
125
|
+
subject.call
|
126
|
+
end
|
127
|
+
let(:scopes) { 'for example' }
|
128
|
+
|
129
|
+
it { expect(last_response.status).to eq 200 }
|
130
|
+
|
131
|
+
it { expect(AccessToken.count).to eq 2 }
|
132
|
+
it { expect(access_token.reload.scopes).to eq(scopes) }
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'does nothing on token refresh if :on_refresh is equal to' do
|
136
|
+
let(:callback) { nil }
|
137
|
+
|
138
|
+
before do
|
139
|
+
allow(Simple::OAuth2.config).to receive(:issue_refresh_token).and_return(true)
|
140
|
+
allow(Simple::OAuth2.config).to receive(:on_refresh).and_return(callback)
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'nil' do
|
144
|
+
before { subject.call }
|
145
|
+
|
146
|
+
it { expect(Simple::OAuth2::Strategies::RefreshToken).not_to receive(:run_callback_on_refresh_token) }
|
147
|
+
it { expect(last_response.status).to eq 200 }
|
148
|
+
end
|
149
|
+
|
150
|
+
context ':nothing' do
|
151
|
+
let(:callback) { :nothing }
|
152
|
+
before { subject.call }
|
153
|
+
|
154
|
+
it { expect(Simple::OAuth2::Strategies::RefreshToken).not_to receive(:run_callback_on_refresh_token) }
|
155
|
+
it { expect(last_response.status).to eq 200 }
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'String' do
|
159
|
+
let(:callback) { 'SomeClass' }
|
160
|
+
let(:error) do
|
161
|
+
":on_refresh is not a block and Access Token class doesn't respond to #{callback}!"
|
162
|
+
end
|
163
|
+
|
164
|
+
it { expect { subject.call }.to raise_error(ArgumentError, error) }
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'when invalid params' do
|
170
|
+
before do
|
171
|
+
allow(Simple::OAuth2.config).to receive(:issue_refresh_token).and_return(true)
|
172
|
+
subject.call
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'without client_id' do
|
176
|
+
let(:client_id) {}
|
177
|
+
|
178
|
+
it { expect(AccessToken.count).to eq 1 }
|
179
|
+
|
180
|
+
it { expect(json_body[:error]).to eq('invalid_request') }
|
181
|
+
it { expect(json_body[:error_description]).to eq("'client_id' required.") }
|
182
|
+
it { expect(last_response.status).to eq 400 }
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'with invalid client_id' do
|
186
|
+
let(:client_id) { 'invalid' }
|
187
|
+
let(:error_description) do
|
188
|
+
'The client identifier provided is invalid, the client failed to authenticate, '\
|
189
|
+
'the client did not include its credentials, provided multiple client credentials, '\
|
190
|
+
'or used unsupported credentials type.'
|
191
|
+
end
|
192
|
+
|
193
|
+
it { expect(AccessToken.count).to eq 1 }
|
194
|
+
|
195
|
+
it { expect(json_body[:error]).to eq('invalid_client') }
|
196
|
+
it { expect(json_body[:error_description]).to eq(error_description) }
|
197
|
+
it { expect(last_response.status).to eq 401 }
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'without client_secret' do
|
201
|
+
let(:client_secret) {}
|
202
|
+
let(:error_description) do
|
203
|
+
'The client identifier provided is invalid, the client failed to authenticate, '\
|
204
|
+
'the client did not include its credentials, provided multiple client credentials, '\
|
205
|
+
'or used unsupported credentials type.'
|
206
|
+
end
|
207
|
+
|
208
|
+
it { expect(AccessToken.count).to eq 1 }
|
209
|
+
|
210
|
+
it { expect(json_body[:error]).to eq('invalid_client') }
|
211
|
+
it { expect(json_body[:error_description]).to eq(error_description) }
|
212
|
+
it { expect(last_response.status).to eq 401 }
|
213
|
+
end
|
214
|
+
|
215
|
+
context 'with invalid client_secret' do
|
216
|
+
let(:client_secret) { 'invalid' }
|
217
|
+
let(:error_description) do
|
218
|
+
'The client identifier provided is invalid, the client failed to authenticate, '\
|
219
|
+
'the client did not include its credentials, provided multiple client credentials, '\
|
220
|
+
'or used unsupported credentials type.'
|
221
|
+
end
|
222
|
+
|
223
|
+
it { expect(AccessToken.count).to eq 1 }
|
224
|
+
|
225
|
+
it { expect(json_body[:error]).to eq('invalid_client') }
|
226
|
+
it { expect(json_body[:error_description]).to eq(error_description) }
|
227
|
+
it { expect(last_response.status).to eq 401 }
|
228
|
+
end
|
229
|
+
|
230
|
+
context 'without refresh_token' do
|
231
|
+
let(:refresh_token) {}
|
232
|
+
|
233
|
+
it { expect(AccessToken.count).to be_zero }
|
234
|
+
|
235
|
+
it { expect(json_body[:error]).to eq('invalid_request') }
|
236
|
+
it { expect(json_body[:error_description]).to eq("'refresh_token' required.") }
|
237
|
+
it { expect(last_response.status).to eq 400 }
|
238
|
+
end
|
239
|
+
|
240
|
+
context 'with invalid refresh_token' do
|
241
|
+
let(:refresh_token) { 'invalid' }
|
242
|
+
let(:error_description) do
|
243
|
+
'The provided access grant is invalid, expired, or revoked '\
|
244
|
+
'(e.g. invalid assertion, expired authorization token, '\
|
245
|
+
'bad end-user password credentials, or mismatching authorization code and redirection URI).'
|
246
|
+
end
|
247
|
+
|
248
|
+
it { expect(AccessToken.count).to be_zero }
|
249
|
+
|
250
|
+
it { expect(json_body[:error]).to eq('invalid_grant') }
|
251
|
+
it { expect(json_body[:error_description]).to eq(error_description) }
|
252
|
+
it { expect(last_response.status).to eq 400 }
|
253
|
+
end
|
254
|
+
|
255
|
+
context 'without grant_type' do
|
256
|
+
let(:grant_type) {}
|
257
|
+
|
258
|
+
it { expect(AccessToken.count).to eq 1 }
|
259
|
+
|
260
|
+
it { expect(json_body[:error]).to eq('invalid_request') }
|
261
|
+
it { expect(json_body[:error_description]).to eq("'grant_type' required.") }
|
262
|
+
it { expect(last_response.status).to eq 400 }
|
263
|
+
end
|
264
|
+
|
265
|
+
context 'with invalid grant_type' do
|
266
|
+
let(:grant_type) { 'invalid' }
|
267
|
+
let(:error_description) do
|
268
|
+
'The access grant included - '\
|
269
|
+
'its type or another attribute - '\
|
270
|
+
'is not supported by the authorization server.'
|
271
|
+
end
|
272
|
+
|
273
|
+
it { expect(AccessToken.count).to eq 1 }
|
274
|
+
|
275
|
+
it { expect(json_body[:error]).to eq('unsupported_grant_type') }
|
276
|
+
it { expect(json_body[:error_description]).to eq(error_description) }
|
277
|
+
it { expect(last_response.status).to eq 400 }
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Authorization Endpoint' do
|
4
|
+
subject { -> { post url, params } }
|
5
|
+
|
6
|
+
let(:url) { '/oauth/authorization' }
|
7
|
+
let!(:user) { User.create(username: FFaker::Internet.user_name, encrypted_password: FFaker::Internet.password) }
|
8
|
+
let(:client) { Client.create(name: FFaker::Internet.domain_word, redirect_uri: 'http://localhost:3000/home') }
|
9
|
+
let(:client_id) { client.key }
|
10
|
+
let(:redirect_uri) { client.redirect_uri }
|
11
|
+
let(:response_type) { 'token' }
|
12
|
+
let(:state) { nil }
|
13
|
+
let(:scope) { nil }
|
14
|
+
let(:params) do
|
15
|
+
{
|
16
|
+
client_id: client_id,
|
17
|
+
redirect_uri: redirect_uri,
|
18
|
+
response_type: response_type,
|
19
|
+
state: state,
|
20
|
+
scope: scope
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'POST /oauth/custom_authorization' do
|
25
|
+
let(:url) { '/oauth/custom_authorization' }
|
26
|
+
|
27
|
+
before { subject.call }
|
28
|
+
|
29
|
+
context 'invokes custom block' do
|
30
|
+
it { expect(last_response.status).to eq(400) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'POST /oauth/authorization' do
|
35
|
+
describe 'Token flow' do
|
36
|
+
before { subject.call }
|
37
|
+
|
38
|
+
context 'when valid params' do
|
39
|
+
let(:access_token) { AccessToken.last.token }
|
40
|
+
let(:response_header) do
|
41
|
+
"#{client.redirect_uri}#access_token=#{access_token}&expires_in=7200&token_type=bearer"
|
42
|
+
end
|
43
|
+
|
44
|
+
it { expect(AccessToken.count).to eq 1 }
|
45
|
+
it { expect(AccessToken.last.scopes).to be_empty }
|
46
|
+
it { expect(AccessToken.last.resource_owner.username).to eq user.username }
|
47
|
+
it { expect(last_response.status).to eq 302 }
|
48
|
+
it { expect(last_response.headers['Location']).to eq response_header }
|
49
|
+
|
50
|
+
context 'without redirect_uri' do
|
51
|
+
let(:redirect_uri) {}
|
52
|
+
|
53
|
+
it { expect(last_response.headers['Location']).to eq response_header }
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'with state' do
|
57
|
+
let(:state) { 'zxc' }
|
58
|
+
let(:response_header_with_state) do
|
59
|
+
"#{client.redirect_uri}#access_token=#{access_token}&expires_in=7200&state=#{state}&token_type=bearer"
|
60
|
+
end
|
61
|
+
|
62
|
+
it { expect(last_response.headers['Location']).to eq response_header_with_state }
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'with scopes' do
|
66
|
+
let(:scope) { 'read,write' }
|
67
|
+
|
68
|
+
it { expect(AccessToken.last.scopes).to eq 'read,write' }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'when invalid params' do
|
73
|
+
context 'without client_id' do
|
74
|
+
let(:client_id) {}
|
75
|
+
|
76
|
+
it { expect(last_response.status).to eq 400 }
|
77
|
+
it { expect(json_body[:error]).to eq('bad_request') }
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'with invalid client_id' do
|
81
|
+
let(:client_id) { 'invalid' }
|
82
|
+
|
83
|
+
it { expect(last_response.status).to eq 400 }
|
84
|
+
it { expect(json_body[:error]).to eq('bad_request') }
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'with invalid redirect_uri' do
|
88
|
+
let(:redirect_uri) { 'invalid' }
|
89
|
+
|
90
|
+
it { expect(last_response.status).to eq 400 }
|
91
|
+
it { expect(json_body[:error]).to eq('bad_request') }
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'without response_type' do
|
95
|
+
let(:response_type) {}
|
96
|
+
|
97
|
+
it { expect(last_response.status).to eq 400 }
|
98
|
+
it { expect(json_body[:error]).to eq('invalid_request') }
|
99
|
+
it { expect(json_body[:error_description]).to eq "'response_type' required." }
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'with invalid response_type' do
|
103
|
+
let(:response_type) { 'invalid' }
|
104
|
+
let(:error_description) { 'The requested response type is not supported by the authorization server.' }
|
105
|
+
|
106
|
+
it { expect(last_response.status).to eq 400 }
|
107
|
+
it { expect(json_body[:error]).to eq 'unsupported_response_type' }
|
108
|
+
it { expect(json_body[:error_description]).to eq error_description }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'GET Protected Resources' do
|
4
|
+
subject { -> { get url, params } }
|
5
|
+
|
6
|
+
let(:url) { '/api/v1/status' }
|
7
|
+
let(:client) { Client.create(name: FFaker::Internet.domain_word, redirect_uri: 'http://localhost:3000/home') }
|
8
|
+
let(:user) { User.create(username: FFaker::Internet.user_name, encrypted_password: FFaker::Internet.password) }
|
9
|
+
let(:scopes) { nil }
|
10
|
+
let(:access_token) { AccessToken.create_for(client, user, scopes) }
|
11
|
+
let(:params) { { access_token: access_token.token } }
|
12
|
+
|
13
|
+
before { subject.call }
|
14
|
+
|
15
|
+
context 'with invalid data' do
|
16
|
+
context 'returns Unauthorized without access_token' do
|
17
|
+
let(:params) {}
|
18
|
+
|
19
|
+
it { expect(last_response.status).to eq 401 }
|
20
|
+
it { expect(json_body[:error]).to eq 'unauthorized' }
|
21
|
+
it { expect(last_response.headers['WWW-Authenticate']).to eq('Bearer realm="Custom Realm"') }
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'returns Unauthorized when token scopes are blank' do
|
25
|
+
let(:url) { '/api/v1/status/single_scope' }
|
26
|
+
|
27
|
+
it { expect(last_response.status).to eq 403 }
|
28
|
+
it { expect(json_body[:error]).to eq 'forbidden' }
|
29
|
+
end
|
30
|
+
|
31
|
+
context "returns Unauthorized when token scopes doesn't match required scopes" do
|
32
|
+
let(:url) { '/api/v1/status/multiple_scopes' }
|
33
|
+
let(:scopes) { 'read' }
|
34
|
+
|
35
|
+
it { expect(last_response.status).to eq 403 }
|
36
|
+
it { expect(json_body[:error]).to eq 'forbidden' }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'with valid data' do
|
41
|
+
context "returns status for endpoint that doesn't requires any scope" do
|
42
|
+
it { expect(last_response.status).to eq 200 }
|
43
|
+
it { expect(json_body[:value]).to eq('Access') }
|
44
|
+
it { expect(json_body[:current_user_name]).not_to be_nil }
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'returns status for endpoint with specific scope' do
|
48
|
+
let(:url) { '/api/v1/status/single_scope' }
|
49
|
+
let(:scopes) { 'read' }
|
50
|
+
|
51
|
+
it { expect(last_response.status).to eq 200 }
|
52
|
+
it { expect(json_body[:value]).to eq('Access read') }
|
53
|
+
it { expect(json_body[:current_user_name]).not_to be_nil }
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'returns status for endpoint with specific set of scopes' do
|
57
|
+
let(:url) { '/api/v1/status/multiple_scopes' }
|
58
|
+
let(:scopes) { 'read,write' }
|
59
|
+
|
60
|
+
it { expect(last_response.status).to eq 200 }
|
61
|
+
it { expect(json_body[:value]).to eq('Access read, write') }
|
62
|
+
it { expect(json_body[:current_user_name]).not_to be_nil }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'RevokeToken Endpoint' do
|
4
|
+
subject { -> { post url, params } }
|
5
|
+
|
6
|
+
let(:url) { '/oauth/revoke_token' }
|
7
|
+
let(:user) { User.create(username: FFaker::Internet.user_name, encrypted_password: FFaker::Internet.password) }
|
8
|
+
let(:client) { Client.create(name: FFaker::Internet.domain_word, redirect_uri: 'http://localhost:3000/home') }
|
9
|
+
let(:access_token) { AccessToken.create_for(client, user) }
|
10
|
+
let(:client_id) { client.key }
|
11
|
+
let(:refresh_token) { access_token.refresh_token }
|
12
|
+
let(:params) do
|
13
|
+
{
|
14
|
+
client_id: client_id,
|
15
|
+
token: refresh_token
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'POST /oauth/revoke_token' do
|
20
|
+
before do
|
21
|
+
allow(Simple::OAuth2.config).to receive(:issue_refresh_token).and_return(true)
|
22
|
+
subject.call
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'with valid params' do
|
26
|
+
it { expect(last_response.status).to eq 200 }
|
27
|
+
it { expect(last_response.header).to be_empty }
|
28
|
+
it { expect(last_response.body).to be_empty }
|
29
|
+
|
30
|
+
it { expect(access_token.reload.refresh_token).to_not be_nil }
|
31
|
+
it { expect(access_token.reload.revoked_at).to_not be_nil }
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with invalid params' do
|
35
|
+
context 'without client_id' do
|
36
|
+
let(:client_id) {}
|
37
|
+
let(:error_description) do
|
38
|
+
'The client identifier provided is invalid, the client failed to authenticate, '\
|
39
|
+
'the client did not include its credentials, provided multiple client credentials, '\
|
40
|
+
'or used unsupported credentials type.'
|
41
|
+
end
|
42
|
+
|
43
|
+
it { expect(last_response.status).to eq 401 }
|
44
|
+
it { expect(json_body[:error]).to eq 'invalid_client' }
|
45
|
+
it { expect(json_body[:error_description]).to eq error_description }
|
46
|
+
|
47
|
+
it { expect(access_token.reload.refresh_token).to_not be_nil }
|
48
|
+
it { expect(access_token.reload.revoked_at).to be_nil }
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when client_id invalid' do
|
52
|
+
let(:client_id) { 'invalid' }
|
53
|
+
let(:error_description) do
|
54
|
+
'The client identifier provided is invalid, the client failed to authenticate, '\
|
55
|
+
'the client did not include its credentials, provided multiple client credentials, '\
|
56
|
+
'or used unsupported credentials type.'
|
57
|
+
end
|
58
|
+
|
59
|
+
it { expect(last_response.status).to eq 401 }
|
60
|
+
it { expect(json_body[:error]).to eq 'invalid_client' }
|
61
|
+
it { expect(json_body[:error_description]).to eq error_description }
|
62
|
+
|
63
|
+
it { expect(access_token.reload.refresh_token).to_not be_nil }
|
64
|
+
it { expect(access_token.reload.revoked_at).to be_nil }
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'without token' do
|
68
|
+
let(:refresh_token) {}
|
69
|
+
|
70
|
+
it { expect(last_response.status).to eq 200 }
|
71
|
+
it { expect(last_response.header).to be_empty }
|
72
|
+
it { expect(last_response.body).to be_empty }
|
73
|
+
|
74
|
+
it { expect(access_token.reload.refresh_token).to_not be_nil }
|
75
|
+
it { expect(access_token.reload.revoked_at).to be_nil }
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when token invalid' do
|
79
|
+
let(:refresh_token) { 'invalid' }
|
80
|
+
|
81
|
+
it { expect(last_response.status).to eq 200 }
|
82
|
+
it { expect(last_response.header).to be_empty }
|
83
|
+
it { expect(last_response.body).to be_empty }
|
84
|
+
|
85
|
+
it { expect(access_token.reload.refresh_token).to_not be_nil }
|
86
|
+
it { expect(access_token.reload.revoked_at).to be_nil }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
ENV['RAILS_ENV'] ||= 'test'
|
2
|
+
ENV['ORM'] ||= 'nobrainer'
|
3
|
+
|
4
|
+
ORM_GEMS_MAPPING = {
|
5
|
+
'nobrainer' => 'nobrainer'
|
6
|
+
}.freeze
|
7
|
+
|
8
|
+
if RUBY_VERSION >= '1.9'
|
9
|
+
require 'simplecov'
|
10
|
+
require 'coveralls'
|
11
|
+
|
12
|
+
SimpleCov.formatters = [
|
13
|
+
SimpleCov::Formatter::HTMLFormatter,
|
14
|
+
Coveralls::SimpleCov::Formatter
|
15
|
+
]
|
16
|
+
|
17
|
+
SimpleCov.start do
|
18
|
+
add_filter '/spec/'
|
19
|
+
minimum_coverage(90)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'rack/test'
|
24
|
+
require 'ffaker'
|
25
|
+
require ORM_GEMS_MAPPING[ENV['ORM']]
|
26
|
+
require File.expand_path("../dummy/orm/#{ENV['ORM']}/app/twitter", __FILE__)
|
27
|
+
|
28
|
+
APP = Rack::Builder.parse_file(File.expand_path("../dummy/orm/#{ENV['ORM']}/config.ru", __FILE__)).first
|
29
|
+
|
30
|
+
require 'support/helper'
|
31
|
+
|
32
|
+
RSpec.configure do |config|
|
33
|
+
config.include Helper
|
34
|
+
|
35
|
+
config.filter_run_excluding skip_if: true
|
36
|
+
config.expect_with :rspec do |c|
|
37
|
+
c.syntax = :expect
|
38
|
+
end
|
39
|
+
|
40
|
+
config.order = :random
|
41
|
+
config.color = true
|
42
|
+
|
43
|
+
config.before(:all) do
|
44
|
+
NoBrainer.sync_schema
|
45
|
+
end
|
46
|
+
|
47
|
+
config.before(:each) do
|
48
|
+
NoBrainer.purge!
|
49
|
+
NoBrainer::Loader.cleanup
|
50
|
+
end
|
51
|
+
end
|