rockoauth 0.1.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/History.txt +5 -0
- data/README.rdoc +422 -0
- data/example/README.rdoc +11 -0
- data/example/application.rb +158 -0
- data/example/config.ru +3 -0
- data/example/environment.rb +11 -0
- data/example/models/connection.rb +9 -0
- data/example/models/note.rb +4 -0
- data/example/models/user.rb +5 -0
- data/example/public/style.css +78 -0
- data/example/schema.rb +22 -0
- data/example/views/authorize.erb +28 -0
- data/example/views/create_user.erb +3 -0
- data/example/views/error.erb +6 -0
- data/example/views/home.erb +24 -0
- data/example/views/layout.erb +24 -0
- data/example/views/login.erb +20 -0
- data/example/views/new_client.erb +25 -0
- data/example/views/new_user.erb +22 -0
- data/example/views/show_client.erb +15 -0
- data/lib/rockoauth/model/authorization.rb +132 -0
- data/lib/rockoauth/model/client.rb +54 -0
- data/lib/rockoauth/model/client_owner.rb +13 -0
- data/lib/rockoauth/model/hashing.rb +26 -0
- data/lib/rockoauth/model/helpers.rb +14 -0
- data/lib/rockoauth/model/resource_owner.rb +22 -0
- data/lib/rockoauth/model.rb +38 -0
- data/lib/rockoauth/provider/access_token.rb +70 -0
- data/lib/rockoauth/provider/authorization.rb +185 -0
- data/lib/rockoauth/provider/error.rb +19 -0
- data/lib/rockoauth/provider/exchange.rb +225 -0
- data/lib/rockoauth/provider.rb +133 -0
- data/lib/rockoauth/router.rb +75 -0
- data/lib/rockoauth/schema/20120828112156_rockoauth_schema_original_schema.rb +35 -0
- data/lib/rockoauth/schema/20121024180930_rockoauth_schema_add_authorization_index.rb +13 -0
- data/lib/rockoauth/schema/20121025180447_rockoauth_schema_add_unique_indexes.rb +31 -0
- data/lib/rockoauth/schema.rb +25 -0
- data/lib/rockoauth.rb +1 -0
- data/spec/factories.rb +20 -0
- data/spec/request_helpers.rb +62 -0
- data/spec/rockoauth/model/authorization_spec.rb +237 -0
- data/spec/rockoauth/model/client_spec.rb +44 -0
- data/spec/rockoauth/model/helpers_spec.rb +25 -0
- data/spec/rockoauth/model/resource_owner_spec.rb +87 -0
- data/spec/rockoauth/provider/access_token_spec.rb +138 -0
- data/spec/rockoauth/provider/authorization_spec.rb +356 -0
- data/spec/rockoauth/provider/exchange_spec.rb +361 -0
- data/spec/rockoauth/provider_spec.rb +560 -0
- data/spec/spec_helper.rb +80 -0
- data/spec/test_app/helper.rb +36 -0
- data/spec/test_app/provider/application.rb +67 -0
- data/spec/test_app/provider/views/authorize.erb +19 -0
- metadata +238 -0
@@ -0,0 +1,560 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RockOAuth::Provider do
|
4
|
+
include RequestHelpers
|
5
|
+
|
6
|
+
before(:all) { TestApp::Provider.start(RequestHelpers::SERVER_PORT) }
|
7
|
+
after(:all) { TestApp::Provider.stop }
|
8
|
+
|
9
|
+
let(:params) { { 'response_type' => 'code',
|
10
|
+
'client_id' => @client.client_id,
|
11
|
+
'redirect_uri' => @client.redirect_uri }
|
12
|
+
}
|
13
|
+
|
14
|
+
before do
|
15
|
+
@client = Factory(:client, :name => 'Test client')
|
16
|
+
@owner = TestApp::User['Bob']
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "access grant request" do
|
20
|
+
shared_examples_for "asks for user permission" do
|
21
|
+
it "creates an authorization" do
|
22
|
+
auth = mock_request(RockOAuth::Provider::Authorization, :client => @client, :params => {}, :scopes => [], :valid? => true)
|
23
|
+
expect(RockOAuth::Provider::Authorization).to receive(:new).with(@owner, params, nil).and_return(auth)
|
24
|
+
get(params)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "displays an authorization page" do
|
28
|
+
response = get(params)
|
29
|
+
expect(response.code.to_i).to eq(200)
|
30
|
+
expect(response.body).to match(/Do you want to allow Test client/)
|
31
|
+
expect(response['Content-Type']).to match(/text\/html/)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "with valid parameters" do
|
36
|
+
it_should_behave_like "asks for user permission"
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "for token requests" do
|
40
|
+
before { params['response_type'] = 'token' }
|
41
|
+
it_should_behave_like "asks for user permission"
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "for code_and_token requests" do
|
45
|
+
before { params['response_type'] = 'code_and_token' }
|
46
|
+
it_should_behave_like "asks for user permission"
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "enforcing SSL" do
|
50
|
+
before { RockOAuth::Provider.enforce_ssl = true }
|
51
|
+
|
52
|
+
it "does not allow non-SSL requests" do
|
53
|
+
response = get(params)
|
54
|
+
validate_response(response, 400, 'WAT')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "when there is already a pending authorization from the user" do
|
59
|
+
before do
|
60
|
+
@authorization = create_authorization(
|
61
|
+
:owner => @owner,
|
62
|
+
:client => @client,
|
63
|
+
:code => 'pending_code',
|
64
|
+
:scope => 'offline_access')
|
65
|
+
end
|
66
|
+
|
67
|
+
it "immediately redirects with the code" do
|
68
|
+
response = get(params)
|
69
|
+
expect(response.code.to_i).to eq(302)
|
70
|
+
expect(response['location']).to eq('https://client.example.com/cb?code=pending_code')
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "when the client is requesting scopes it already has access to" do
|
74
|
+
before { params['scope'] = 'offline_access' }
|
75
|
+
|
76
|
+
it "immediately redirects with the code" do
|
77
|
+
response = get(params)
|
78
|
+
expect(response.code.to_i).to eq(302)
|
79
|
+
expect(response['location']).to eq('https://client.example.com/cb?code=pending_code&scope=offline_access')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "when the client is requesting scopes it doesn't have yet" do
|
84
|
+
before { params['scope'] = 'wall_publish' }
|
85
|
+
it_should_behave_like "asks for user permission"
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "and the authorization does not have a code" do
|
89
|
+
before { @authorization.update_attribute(:code, nil) }
|
90
|
+
|
91
|
+
it "generates a new code and redirects" do
|
92
|
+
expect(RockOAuth::Model::Authorization).not_to receive(:create)
|
93
|
+
expect(RockOAuth::Model::Authorization).not_to receive(:new)
|
94
|
+
expect(RockOAuth).to receive(:random_string).and_return('new_code')
|
95
|
+
response = get(params)
|
96
|
+
expect(response.code.to_i).to eq(302)
|
97
|
+
expect(response['location']).to eq('https://client.example.com/cb?code=new_code')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "and the authorization is expired" do
|
102
|
+
before { @authorization.update_attribute(:expires_at, 2.hours.ago) }
|
103
|
+
it_should_behave_like "asks for user permission"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "when there is already a completed authorization from the user" do
|
108
|
+
before do
|
109
|
+
@authorization = create_authorization(
|
110
|
+
:owner => @owner,
|
111
|
+
:client => @client,
|
112
|
+
:code => nil,
|
113
|
+
:access_token => RockOAuth.hashify('complete_token'))
|
114
|
+
end
|
115
|
+
|
116
|
+
it "immediately redirects with a new code" do
|
117
|
+
expect(RockOAuth).to receive(:random_string).and_return('new_code')
|
118
|
+
response = get(params)
|
119
|
+
expect(response.code.to_i).to eq(302)
|
120
|
+
expect(response['location']).to eq('https://client.example.com/cb?code=new_code')
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "for token requests" do
|
124
|
+
before { params['response_type'] = 'token' }
|
125
|
+
|
126
|
+
it "immediately redirects with a new token" do
|
127
|
+
expect(RockOAuth).to receive(:random_string).and_return('new_access_token')
|
128
|
+
response = get(params)
|
129
|
+
expect(response.code.to_i).to eq(302)
|
130
|
+
expect(response['location']).to eq('https://client.example.com/cb#access_token=new_access_token')
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "with an invalid client_id" do
|
134
|
+
before { params['client_id'] = 'unknown_id' }
|
135
|
+
|
136
|
+
it "does not generate any new tokens" do
|
137
|
+
expect(RockOAuth).not_to receive(:random_string)
|
138
|
+
get(params)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it "does not create a new Authorization" do
|
144
|
+
get(params)
|
145
|
+
expect(RockOAuth::Model::Authorization.count).to eq(1)
|
146
|
+
end
|
147
|
+
|
148
|
+
it "keeps the code and access token on the Authorization" do
|
149
|
+
get(params)
|
150
|
+
authorization = RockOAuth::Model::Authorization.first
|
151
|
+
expect(authorization.code).not_to be_nil
|
152
|
+
expect(authorization.access_token_hash).not_to be_nil
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "with no parameters" do
|
157
|
+
let(:params) { {} }
|
158
|
+
|
159
|
+
it "renders an error page" do
|
160
|
+
response = get(params)
|
161
|
+
validate_response(response, 400, 'WAT')
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "with a redirect_uri and no client_id" do
|
166
|
+
let(:params) { {'redirect_uri' => 'http://evilsite.com/callback'} }
|
167
|
+
|
168
|
+
it "renders an error page" do
|
169
|
+
response = get(params)
|
170
|
+
validate_response(response, 400, 'WAT')
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe "with a client_id and a bad redirect_uri" do
|
175
|
+
let(:params) { {'redirect_uri' => 'http://evilsite.com/callback',
|
176
|
+
'client_id' => @client.client_id} }
|
177
|
+
|
178
|
+
it "redirects to the client's registered redirect_uri" do
|
179
|
+
response = get(params)
|
180
|
+
expect(response.code.to_i).to eq(302)
|
181
|
+
expect(response['location']).to eq('https://client.example.com/cb?error=invalid_request&error_description=Missing+required+parameter+response_type')
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe "with an invalid request" do
|
186
|
+
before { params.delete('response_type') }
|
187
|
+
|
188
|
+
it "redirects to the client's redirect_uri on error" do
|
189
|
+
response = get(params)
|
190
|
+
expect(response.code.to_i).to eq(302)
|
191
|
+
expect(response['location']).to eq('https://client.example.com/cb?error=invalid_request&error_description=Missing+required+parameter+response_type')
|
192
|
+
end
|
193
|
+
|
194
|
+
describe "with a state parameter" do
|
195
|
+
before { params['state'] = "Facebook\nmesses this\nup" }
|
196
|
+
|
197
|
+
it "redirects to the client, including the state param" do
|
198
|
+
response = get(params)
|
199
|
+
expect(response.code.to_i).to eq(302)
|
200
|
+
expect(response['location']).to eq("https://client.example.com/cb?error=invalid_request&error_description=Missing+required+parameter+response_type&state=Facebook%0Amesses+this%0Aup")
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
describe "authorization confirmation from the user" do
|
207
|
+
let(:mock_auth) do
|
208
|
+
mock = double RockOAuth::Provider::Authorization,
|
209
|
+
:redirect_uri => 'http://example.com/',
|
210
|
+
:response_status => 302
|
211
|
+
|
212
|
+
allow(RockOAuth::Provider::Authorization).to receive(:new).and_return(mock)
|
213
|
+
mock
|
214
|
+
end
|
215
|
+
|
216
|
+
describe "without the user's permission" do
|
217
|
+
before { params['allow'] = '' }
|
218
|
+
|
219
|
+
it "does not grant access" do
|
220
|
+
expect(mock_auth).to receive(:deny_access!)
|
221
|
+
allow_or_deny(params)
|
222
|
+
end
|
223
|
+
|
224
|
+
it "redirects to the client with an error" do
|
225
|
+
response = allow_or_deny(params)
|
226
|
+
expect(response.code.to_i).to eq(302)
|
227
|
+
expect(response['location']).to eq('https://client.example.com/cb?error=access_denied&error_description=The+user+denied+you+access')
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe "with valid parameters and user permission" do
|
232
|
+
before { allow(RockOAuth).to receive(:random_string).and_return('foo') }
|
233
|
+
before { params['allow'] = '1' }
|
234
|
+
|
235
|
+
describe "for code requests" do
|
236
|
+
it "grants access" do
|
237
|
+
expect(mock_auth).to receive(:grant_access!)
|
238
|
+
allow_or_deny(params)
|
239
|
+
end
|
240
|
+
|
241
|
+
it "redirects to the client with an authorization code" do
|
242
|
+
response = allow_or_deny(params)
|
243
|
+
expect(response.code.to_i).to eq(302)
|
244
|
+
expect(response['location']).to eq('https://client.example.com/cb?code=foo')
|
245
|
+
end
|
246
|
+
|
247
|
+
it "passes the state parameter through" do
|
248
|
+
params['state'] = 'illinois'
|
249
|
+
response = allow_or_deny(params)
|
250
|
+
expect(response.code.to_i).to eq(302)
|
251
|
+
expect(response['location']).to eq('https://client.example.com/cb?code=foo&state=illinois')
|
252
|
+
end
|
253
|
+
|
254
|
+
it "passes the scope parameter through" do
|
255
|
+
params['scope'] = 'foo bar'
|
256
|
+
response = allow_or_deny(params)
|
257
|
+
expect(response.code.to_i).to eq(302)
|
258
|
+
expect(response['location']).to eq('https://client.example.com/cb?code=foo&scope=foo+bar')
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
describe "for token requests" do
|
263
|
+
before { params['response_type'] = 'token' }
|
264
|
+
|
265
|
+
it "grants access" do
|
266
|
+
expect(mock_auth).to receive(:grant_access!)
|
267
|
+
allow_or_deny(params)
|
268
|
+
end
|
269
|
+
|
270
|
+
it "redirects to the client with an access token" do
|
271
|
+
response = allow_or_deny(params)
|
272
|
+
expect(response.code.to_i).to eq(302)
|
273
|
+
expect(response['location']).to eq('https://client.example.com/cb#access_token=foo&expires_in=10800')
|
274
|
+
end
|
275
|
+
|
276
|
+
it "passes the state parameter through" do
|
277
|
+
params['state'] = 'illinois'
|
278
|
+
response = allow_or_deny(params)
|
279
|
+
expect(response.code.to_i).to eq(302)
|
280
|
+
expect(response['location']).to eq('https://client.example.com/cb#access_token=foo&expires_in=10800&state=illinois')
|
281
|
+
end
|
282
|
+
|
283
|
+
it "passes the scope parameter through" do
|
284
|
+
params['scope'] = 'foo bar'
|
285
|
+
response = allow_or_deny(params)
|
286
|
+
expect(response.code.to_i).to eq(302)
|
287
|
+
expect(response['location']).to eq('https://client.example.com/cb#access_token=foo&expires_in=10800&scope=foo+bar')
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe "for code_and_token requests" do
|
292
|
+
before { params['response_type'] = 'code_and_token' }
|
293
|
+
|
294
|
+
it "grants access" do
|
295
|
+
expect(mock_auth).to receive(:grant_access!)
|
296
|
+
allow_or_deny(params)
|
297
|
+
end
|
298
|
+
|
299
|
+
it "redirects to the client with an access token" do
|
300
|
+
response = allow_or_deny(params)
|
301
|
+
expect(response.code.to_i).to eq(302)
|
302
|
+
expect(response['location']).to eq('https://client.example.com/cb?code=foo#access_token=foo&expires_in=10800')
|
303
|
+
end
|
304
|
+
|
305
|
+
it "passes the state parameter through" do
|
306
|
+
params['state'] = 'illinois'
|
307
|
+
response = allow_or_deny(params)
|
308
|
+
expect(response.code.to_i).to eq(302)
|
309
|
+
expect(response['location']).to eq('https://client.example.com/cb?code=foo&state=illinois#access_token=foo&expires_in=10800')
|
310
|
+
end
|
311
|
+
|
312
|
+
it "passes the scope parameter through" do
|
313
|
+
params['scope'] = 'foo bar'
|
314
|
+
response = allow_or_deny(params)
|
315
|
+
expect(response.code.to_i).to eq(302)
|
316
|
+
expect(response['location']).to eq('https://client.example.com/cb?code=foo#access_token=foo&expires_in=10800&scope=foo+bar')
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
describe "access token request" do
|
323
|
+
before do
|
324
|
+
@client = Factory(:client)
|
325
|
+
@authorization = create_authorization(
|
326
|
+
:owner => @owner,
|
327
|
+
:client => @client,
|
328
|
+
:code => 'a_fake_code',
|
329
|
+
:expires_at => 3.hours.from_now)
|
330
|
+
end
|
331
|
+
|
332
|
+
let(:auth_params) { { 'client_id' => @client.client_id,
|
333
|
+
'client_secret' => @client.client_secret }
|
334
|
+
}
|
335
|
+
|
336
|
+
describe "using authorization_code request" do
|
337
|
+
let(:query_params) { { 'client_id' => @client.client_id,
|
338
|
+
'grant_type' => 'authorization_code',
|
339
|
+
'code' => @authorization.code,
|
340
|
+
'redirect_uri' => @client.redirect_uri }
|
341
|
+
}
|
342
|
+
|
343
|
+
let(:params) { auth_params.merge(query_params) }
|
344
|
+
|
345
|
+
describe "with valid parameters" do
|
346
|
+
it "does not respond to GET" do
|
347
|
+
expect(RockOAuth::Provider::Authorization).not_to receive(:new)
|
348
|
+
params.delete('client_secret')
|
349
|
+
response = get(params)
|
350
|
+
validate_json_response(response, 400,
|
351
|
+
'error' => 'invalid_request',
|
352
|
+
'error_description' => 'Bad request: must be a POST request'
|
353
|
+
)
|
354
|
+
end
|
355
|
+
|
356
|
+
it "does not allow client credentials to be passed in the query string" do
|
357
|
+
expect(RockOAuth::Provider::Authorization).not_to receive(:new)
|
358
|
+
query_string = {'client_id' => params.delete('client_id'), 'client_secret' => params.delete('client_secret')}
|
359
|
+
response = post(params, query_string)
|
360
|
+
validate_json_response(response, 400,
|
361
|
+
'error' => 'invalid_request',
|
362
|
+
'error_description' => 'Bad request: must not send client credentials in the URI'
|
363
|
+
)
|
364
|
+
end
|
365
|
+
|
366
|
+
describe "enforcing SSL" do
|
367
|
+
before { RockOAuth::Provider.enforce_ssl = true }
|
368
|
+
|
369
|
+
it "does not allow non-SSL requests" do
|
370
|
+
response = get(params)
|
371
|
+
validate_json_response(response, 400,
|
372
|
+
'error' => 'invalid_request',
|
373
|
+
'error_description' => 'Bad request: must make requests using HTTPS'
|
374
|
+
)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
it "creates a Token when using Basic Auth" do
|
379
|
+
token = mock_request(RockOAuth::Provider::Exchange, :response_body => 'Hello')
|
380
|
+
expect(RockOAuth::Provider::Exchange).to receive(:new).with(@owner, params, nil).and_return(token)
|
381
|
+
post_basic_auth(auth_params, query_params)
|
382
|
+
end
|
383
|
+
|
384
|
+
it "creates a Token when passing params in the POST body" do
|
385
|
+
token = mock_request(RockOAuth::Provider::Exchange, :response_body => 'Hello')
|
386
|
+
expect(RockOAuth::Provider::Exchange).to receive(:new).with(@owner, params, nil).and_return(token)
|
387
|
+
post(params)
|
388
|
+
end
|
389
|
+
|
390
|
+
it "returns a successful response" do
|
391
|
+
allow(RockOAuth).to receive(:random_string).and_return('random_access_token')
|
392
|
+
response = post_basic_auth(auth_params, query_params)
|
393
|
+
validate_json_response(response, 200, 'access_token' => 'random_access_token', 'expires_in' => 10800)
|
394
|
+
end
|
395
|
+
|
396
|
+
describe "with a scope parameter" do
|
397
|
+
before do
|
398
|
+
@authorization.update_attribute(:scope, 'foo bar')
|
399
|
+
end
|
400
|
+
|
401
|
+
it "passes the scope back in the success response" do
|
402
|
+
allow(RockOAuth).to receive(:random_string).and_return('random_access_token')
|
403
|
+
response = post_basic_auth(auth_params, query_params)
|
404
|
+
validate_json_response(response, 200,
|
405
|
+
'access_token' => 'random_access_token',
|
406
|
+
'scope' => 'foo bar',
|
407
|
+
'expires_in' => 10800
|
408
|
+
)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
describe "with invalid parameters" do
|
414
|
+
before { query_params.delete('code') }
|
415
|
+
|
416
|
+
it "returns an error response" do
|
417
|
+
response = post_basic_auth(auth_params, query_params)
|
418
|
+
validate_json_response(response, 400,
|
419
|
+
'error' => 'invalid_request',
|
420
|
+
'error_description' => 'Missing required parameter code'
|
421
|
+
)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
describe "with mismatched client_id in POST params and Basic Auth params" do
|
426
|
+
before { query_params['client_id'] = 'foo' }
|
427
|
+
|
428
|
+
it "returns an error response" do
|
429
|
+
response = post_basic_auth(auth_params, query_params)
|
430
|
+
validate_json_response(response, 400,
|
431
|
+
'error' => 'invalid_request',
|
432
|
+
'error_description' => 'Bad request: client_id from Basic Auth and request body do not match'
|
433
|
+
)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
describe "when there is an Authorization with code and token" do
|
438
|
+
before do
|
439
|
+
@authorization.update_attributes(:code => 'pending_code', :access_token => 'working_token')
|
440
|
+
allow(RockOAuth).to receive(:random_string).and_return('random_access_token')
|
441
|
+
end
|
442
|
+
|
443
|
+
it "returns a new access token" do
|
444
|
+
response = post(params)
|
445
|
+
validate_json_response(response, 200,
|
446
|
+
'access_token' => 'random_access_token',
|
447
|
+
'expires_in' => 10800
|
448
|
+
)
|
449
|
+
end
|
450
|
+
|
451
|
+
it "exchanges the code for the new token on the existing Authorization" do
|
452
|
+
post(params)
|
453
|
+
@authorization.reload
|
454
|
+
expect(@authorization.code).to be_nil
|
455
|
+
expect(@authorization.access_token_hash).to eq(RockOAuth.hashify('random_access_token'))
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
describe "protected resource request" do
|
462
|
+
before do
|
463
|
+
@authorization = create_authorization(
|
464
|
+
:owner => @owner,
|
465
|
+
:client => @client,
|
466
|
+
:access_token => 'magic-key',
|
467
|
+
:scope => 'profile')
|
468
|
+
end
|
469
|
+
|
470
|
+
shared_examples_for "protected resource" do
|
471
|
+
it "creates an AccessToken response" do
|
472
|
+
mock_token = double(RockOAuth::Provider::AccessToken)
|
473
|
+
expect(mock_token).to receive(:response_headers).and_return({})
|
474
|
+
expect(mock_token).to receive(:response_status).and_return(200)
|
475
|
+
expect(mock_token).to receive(:valid?).and_return(true)
|
476
|
+
expect(RockOAuth::Provider::AccessToken).to receive(:new).with(TestApp::User['Bob'], ['profile'], 'magic-key', nil).and_return(mock_token)
|
477
|
+
request('/user_profile', 'oauth_token' => 'magic-key')
|
478
|
+
end
|
479
|
+
|
480
|
+
it "allows access when the key is passed" do
|
481
|
+
response = request('/user_profile', 'oauth_token' => 'magic-key')
|
482
|
+
expect(JSON.parse(response.body)['data']).to eq('Top secret')
|
483
|
+
expect(response.code.to_i).to eq(200)
|
484
|
+
end
|
485
|
+
|
486
|
+
it "blocks access when the wrong key is passed" do
|
487
|
+
response = request('/user_profile', 'oauth_token' => 'is-the-password-books')
|
488
|
+
expect(JSON.parse(response.body)['data']).to eq('No soup for you')
|
489
|
+
expect(response.code.to_i).to eq(401)
|
490
|
+
expect(response['WWW-Authenticate']).to eq("OAuth realm='Demo App', error='invalid_token'")
|
491
|
+
end
|
492
|
+
|
493
|
+
it "blocks access when the no key is passed" do
|
494
|
+
response = request('/user_profile')
|
495
|
+
expect(JSON.parse(response.body)['data']).to eq('No soup for you')
|
496
|
+
expect(response.code.to_i).to eq(401)
|
497
|
+
expect(response['WWW-Authenticate']).to eq("OAuth realm='Demo App'")
|
498
|
+
end
|
499
|
+
|
500
|
+
describe "enforcing SSL" do
|
501
|
+
before { RockOAuth::Provider.enforce_ssl = true }
|
502
|
+
|
503
|
+
let(:authorization) do
|
504
|
+
RockOAuth::Model::Authorization.find_by_access_token_hash(RockOAuth.hashify('magic-key'))
|
505
|
+
end
|
506
|
+
|
507
|
+
it "blocks access when not using HTTPS" do
|
508
|
+
response = request('/user_profile', 'oauth_token' => 'magic-key')
|
509
|
+
expect(JSON.parse(response.body)['data']).to eq('No soup for you')
|
510
|
+
expect(response.code.to_i).to eq(401)
|
511
|
+
expect(response['WWW-Authenticate']).to eq("OAuth realm='Demo App', error='invalid_request'")
|
512
|
+
end
|
513
|
+
|
514
|
+
it "destroys the access token since it's been leaked" do
|
515
|
+
expect(authorization.access_token_hash).to eq(RockOAuth.hashify('magic-key'))
|
516
|
+
request('/user_profile', 'oauth_token' => 'magic-key')
|
517
|
+
authorization.reload
|
518
|
+
expect(authorization.access_token_hash).to be_nil
|
519
|
+
end
|
520
|
+
|
521
|
+
it "keeps the access token if the wrong key is passed" do
|
522
|
+
expect(authorization.access_token_hash).to eq(RockOAuth.hashify('magic-key'))
|
523
|
+
request('/user_profile', 'oauth_token' => 'is-the-password-books')
|
524
|
+
authorization.reload
|
525
|
+
expect(authorization.access_token_hash).to eq(RockOAuth.hashify('magic-key'))
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
describe "for header-based requests" do
|
531
|
+
def request(path, params = {})
|
532
|
+
access_token = params.delete('oauth_token')
|
533
|
+
http = Net::HTTP.new('localhost', RequestHelpers::SERVER_PORT)
|
534
|
+
qs = params.map { |k,v| "#{ CGI.escape k.to_s }=#{ CGI.escape v.to_s }" }.join('&')
|
535
|
+
header = {'Authorization' => "OAuth #{access_token}"}
|
536
|
+
http.request_get(path + '?' + qs, header)
|
537
|
+
end
|
538
|
+
|
539
|
+
it_should_behave_like "protected resource"
|
540
|
+
end
|
541
|
+
|
542
|
+
describe "for GET requests" do
|
543
|
+
def request(path, params = {})
|
544
|
+
qs = params.map { |k,v| "#{ CGI.escape k.to_s }=#{ CGI.escape v.to_s }" }.join('&')
|
545
|
+
uri = URI.parse("http://localhost:#{RequestHelpers::SERVER_PORT}" + path + '?' + qs)
|
546
|
+
Net::HTTP.get_response(uri)
|
547
|
+
end
|
548
|
+
|
549
|
+
it_should_behave_like "protected resource"
|
550
|
+
end
|
551
|
+
|
552
|
+
describe "for POST requests" do
|
553
|
+
def request(path, params = {})
|
554
|
+
Net::HTTP.post_form(URI.parse("http://localhost:#{RequestHelpers::SERVER_PORT}" + path), params)
|
555
|
+
end
|
556
|
+
|
557
|
+
it_should_behave_like "protected resource"
|
558
|
+
end
|
559
|
+
end
|
560
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'active_record'
|
5
|
+
|
6
|
+
require 'rockoauth/provider'
|
7
|
+
|
8
|
+
case ENV['DB']
|
9
|
+
when 'mysql'
|
10
|
+
ActiveRecord::Base.establish_connection(
|
11
|
+
:adapter => 'mysql',
|
12
|
+
:host => '127.0.0.1',
|
13
|
+
:username => 'root',
|
14
|
+
:database => 'oauth2_test')
|
15
|
+
when 'postgres'
|
16
|
+
ActiveRecord::Base.establish_connection(
|
17
|
+
:adapter => 'postgresql',
|
18
|
+
:host => '127.0.0.1',
|
19
|
+
:username => 'postgres',
|
20
|
+
:database => 'oauth2_test')
|
21
|
+
else
|
22
|
+
dbfile = File.expand_path('../test.sqlite3', __FILE__)
|
23
|
+
File.unlink(dbfile) if File.file?(dbfile)
|
24
|
+
|
25
|
+
ActiveRecord::Base.establish_connection(
|
26
|
+
:adapter => 'sqlite3',
|
27
|
+
:database => dbfile)
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'logger'
|
31
|
+
ActiveRecord::Base.logger = Logger.new(STDERR)
|
32
|
+
ActiveRecord::Base.logger.level = Logger::INFO
|
33
|
+
|
34
|
+
RockOAuth::Model::Schema.up
|
35
|
+
|
36
|
+
ActiveRecord::Schema.define do |version|
|
37
|
+
create_table :users, :force => true do |t|
|
38
|
+
t.string :name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
require 'test_app/provider/application'
|
43
|
+
require 'request_helpers'
|
44
|
+
require 'factories'
|
45
|
+
|
46
|
+
require 'thin'
|
47
|
+
Thin::Logging.silent = true
|
48
|
+
$VERBOSE = nil
|
49
|
+
|
50
|
+
RSpec.configure do |config|
|
51
|
+
# to run only specific specs, add :focus to the spec
|
52
|
+
# describe "foo", :focus do
|
53
|
+
# OR
|
54
|
+
# it "should foo", :focus do
|
55
|
+
|
56
|
+
config.filter_run :focus => true
|
57
|
+
config.run_all_when_everything_filtered = true
|
58
|
+
|
59
|
+
config.before do
|
60
|
+
RockOAuth::Provider.enforce_ssl = false
|
61
|
+
time = Time.now
|
62
|
+
allow(Time).to receive(:now).and_return time
|
63
|
+
end
|
64
|
+
|
65
|
+
config.after do
|
66
|
+
[ RockOAuth::Model::Client,
|
67
|
+
RockOAuth::Model::Authorization,
|
68
|
+
TestApp::User
|
69
|
+
|
70
|
+
].each { |k| k.delete_all }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_authorization(params)
|
75
|
+
RockOAuth::Model::Authorization.__send__(:create) do |authorization|
|
76
|
+
params.each do |key, value|
|
77
|
+
authorization.__send__ "#{key}=", value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module TestApp
|
2
|
+
|
3
|
+
class User < ActiveRecord::Base
|
4
|
+
self.table_name = :users
|
5
|
+
|
6
|
+
include RockOAuth::Model::ResourceOwner
|
7
|
+
include RockOAuth::Model::ClientOwner
|
8
|
+
|
9
|
+
def self.[](name)
|
10
|
+
if respond_to?(:find_or_create_by)
|
11
|
+
find_or_create_by(:name => name)
|
12
|
+
else
|
13
|
+
find_or_create_by_name(name)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Helper
|
19
|
+
module RackRunner
|
20
|
+
def start(port)
|
21
|
+
handler = Rack::Handler.get('thin')
|
22
|
+
Thread.new do
|
23
|
+
handler.run(new, :Port => port) { |server| @server = server }
|
24
|
+
end
|
25
|
+
sleep 0.1 until @server
|
26
|
+
end
|
27
|
+
|
28
|
+
def stop
|
29
|
+
@server.stop if @server
|
30
|
+
@server = nil
|
31
|
+
sleep 0.1 while EM.reactor_running?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|