oauth2-provider-jonrowe 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/README.rdoc +314 -0
  2. data/example/README.rdoc +11 -0
  3. data/example/application.rb +151 -0
  4. data/example/config.ru +3 -0
  5. data/example/environment.rb +11 -0
  6. data/example/models/connection.rb +9 -0
  7. data/example/models/note.rb +4 -0
  8. data/example/models/user.rb +6 -0
  9. data/example/public/style.css +78 -0
  10. data/example/schema.rb +27 -0
  11. data/example/views/authorize.erb +28 -0
  12. data/example/views/create_user.erb +3 -0
  13. data/example/views/home.erb +25 -0
  14. data/example/views/layout.erb +25 -0
  15. data/example/views/login.erb +20 -0
  16. data/example/views/new_client.erb +25 -0
  17. data/example/views/new_user.erb +22 -0
  18. data/example/views/show_client.erb +15 -0
  19. data/lib/oauth2/model.rb +17 -0
  20. data/lib/oauth2/model/authorization.rb +113 -0
  21. data/lib/oauth2/model/client.rb +55 -0
  22. data/lib/oauth2/model/client_owner.rb +13 -0
  23. data/lib/oauth2/model/hashing.rb +27 -0
  24. data/lib/oauth2/model/resource_owner.rb +26 -0
  25. data/lib/oauth2/model/schema.rb +42 -0
  26. data/lib/oauth2/provider.rb +117 -0
  27. data/lib/oauth2/provider/access_token.rb +66 -0
  28. data/lib/oauth2/provider/authorization.rb +168 -0
  29. data/lib/oauth2/provider/error.rb +29 -0
  30. data/lib/oauth2/provider/exchange.rb +212 -0
  31. data/lib/oauth2/router.rb +60 -0
  32. data/spec/factories.rb +27 -0
  33. data/spec/oauth2/model/authorization_spec.rb +216 -0
  34. data/spec/oauth2/model/client_spec.rb +55 -0
  35. data/spec/oauth2/model/resource_owner_spec.rb +55 -0
  36. data/spec/oauth2/provider/access_token_spec.rb +125 -0
  37. data/spec/oauth2/provider/authorization_spec.rb +323 -0
  38. data/spec/oauth2/provider/exchange_spec.rb +330 -0
  39. data/spec/oauth2/provider_spec.rb +531 -0
  40. data/spec/request_helpers.rb +46 -0
  41. data/spec/spec_helper.rb +44 -0
  42. data/spec/test_app/helper.rb +33 -0
  43. data/spec/test_app/provider/application.rb +61 -0
  44. data/spec/test_app/provider/views/authorize.erb +19 -0
  45. metadata +220 -0
@@ -0,0 +1,330 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::Provider::Exchange do
4
+ before do
5
+ @client = Factory(:client)
6
+ @owner = TestApp::User['Bob']
7
+ @authorization = Factory(:authorization, :client => @client, :owner => @owner, :scope => 'foo bar')
8
+ OAuth2.stub(:random_string).and_return('random_string')
9
+ end
10
+
11
+ let(:exchange) { OAuth2::Provider::Exchange.new(@owner, params) }
12
+
13
+ shared_examples_for "validates required parameters" do
14
+ describe "missing grant_type" do
15
+ before { params.delete('client_id') }
16
+
17
+ it "is invalid" do
18
+ exchange.error.should == "invalid_request"
19
+ exchange.error_description.should == "Missing required parameter client_id"
20
+ end
21
+ end
22
+
23
+ describe "with an unknown grant type" do
24
+ before { params['grant_type'] = 'unknown' }
25
+
26
+ it "is invalid" do
27
+ exchange.error.should == "unsupported_grant_type"
28
+ exchange.error_description.should == "The grant type unknown is not recognized"
29
+ end
30
+ end
31
+
32
+ describe "missing client_id" do
33
+ before { params.delete('client_id') }
34
+
35
+ it "is invalid" do
36
+ exchange.error.should == "invalid_request"
37
+ exchange.error_description.should == "Missing required parameter client_id"
38
+ end
39
+ end
40
+
41
+ describe "with an unknown client_id" do
42
+ before { params['client_id'] = "unknown" }
43
+
44
+ it "is invalid" do
45
+ exchange.error.should == "invalid_client"
46
+ exchange.error_description.should == "Unknown client ID unknown"
47
+ end
48
+ end
49
+
50
+ describe "missing client_secret" do
51
+ before { params.delete('client_secret') }
52
+
53
+ it "is invalid" do
54
+ exchange.error.should == "invalid_request"
55
+ exchange.error_description.should == "Missing required parameter client_secret"
56
+ end
57
+ end
58
+
59
+ describe "with a mismatched client_secret" do
60
+ before { params['client_secret'] = "nosoupforyou" }
61
+
62
+ it "is invalid" do
63
+ exchange.error.should == "invalid_client"
64
+ exchange.error_description.should == "Parameter client_secret does not match"
65
+ end
66
+ end
67
+
68
+ describe "with lesser scope than the authorization code represents" do
69
+ before { params['scope'] = 'bar' }
70
+
71
+ it "is valid" do
72
+ exchange.error.should be_nil
73
+ end
74
+ end
75
+
76
+ describe "with scopes not covered by the authorization code" do
77
+ before { params['scope'] = 'qux' }
78
+
79
+ it "is invalid" do
80
+ exchange.error.should == 'invalid_scope'
81
+ exchange.error_description.should == 'The request scope was never granted by the user'
82
+ end
83
+ end
84
+ end
85
+
86
+ shared_examples_for "valid token request" do
87
+ before do
88
+ OAuth2.stub(:random_string).and_return('random_access_token')
89
+ end
90
+
91
+ it "is valid" do
92
+ exchange.error.should be_nil
93
+ end
94
+
95
+ it "updates the Authorization with tokens" do
96
+ exchange.update_authorization
97
+ authorization.reload
98
+ authorization.code.should be_nil
99
+ authorization.access_token_hash.should == OAuth2.hashify('random_access_token')
100
+ authorization.refresh_token.should be_nil
101
+ end
102
+ end
103
+
104
+ describe "using authorization_code grant type" do
105
+ let(:params) { { 'client_id' => @client.client_id,
106
+ 'client_secret' => @client.client_secret,
107
+ 'grant_type' => 'authorization_code',
108
+ 'code' => @authorization.code,
109
+ 'redirect_uri' => @client.redirect_uri }
110
+ }
111
+
112
+ let(:authorization) { @authorization }
113
+
114
+ it_should_behave_like "validates required parameters"
115
+ it_should_behave_like "valid token request"
116
+
117
+ describe "missing redirect_uri" do
118
+ before { params.delete('redirect_uri') }
119
+
120
+ it "is invalid" do
121
+ exchange.error.should == "invalid_request"
122
+ exchange.error_description.should == "Missing required parameter redirect_uri"
123
+ end
124
+ end
125
+
126
+ describe "with a mismatched redirect_uri" do
127
+ before { params['redirect_uri'] = "http://songkick.com" }
128
+
129
+ it "is invalid" do
130
+ exchange.error.should == "redirect_uri_mismatch"
131
+ exchange.error_description.should == "Parameter redirect_uri does not match registered URI"
132
+ end
133
+
134
+ describe "when the client has not registered a redirect_uri" do
135
+ before { @client.update_attribute(:redirect_uri, nil) }
136
+
137
+ it "is valid" do
138
+ exchange.error.should be_nil
139
+ end
140
+ end
141
+ end
142
+
143
+ describe "missing code" do
144
+ before { params.delete('code') }
145
+
146
+ it "is invalid" do
147
+ exchange.error.should == "invalid_request"
148
+ exchange.error_description.should == "Missing required parameter code"
149
+ end
150
+ end
151
+
152
+ describe "with an unknown code" do
153
+ before { params['code'] = "unknown" }
154
+
155
+ it "is invalid" do
156
+ exchange.error.should == "invalid_grant"
157
+ exchange.error_description.should == "The access grant you supplied is invalid"
158
+ end
159
+ end
160
+
161
+ describe "with an expired code" do
162
+ before { @authorization.update_attribute(:expires_at, 1.day.ago) }
163
+
164
+ it "is invalid" do
165
+ exchange.error.should == "invalid_grant"
166
+ exchange.error_description.should == "The access grant you supplied is invalid"
167
+ end
168
+ end
169
+ end
170
+
171
+ describe "using password grant type" do
172
+ let(:params) { { 'client_id' => @client.client_id,
173
+ 'client_secret' => @client.client_secret,
174
+ 'grant_type' => 'password',
175
+ 'username' => 'Bob',
176
+ 'password' => 'soldier' }
177
+ }
178
+
179
+ let(:authorization) { @authorization }
180
+
181
+ before do
182
+ OAuth2::Provider.handle_passwords do |client, username, password|
183
+ user = TestApp::User[username]
184
+ if password == 'soldier'
185
+ user.grant_access!(client, :scopes => ['foo', 'bar'])
186
+ else
187
+ nil
188
+ end
189
+ end
190
+ end
191
+
192
+ it_should_behave_like "validates required parameters"
193
+ it_should_behave_like "valid token request"
194
+
195
+ describe "missing username" do
196
+ before { params.delete('username') }
197
+
198
+ it "is invalid" do
199
+ exchange.error.should == 'invalid_request'
200
+ exchange.error_description.should == 'Missing required parameter username'
201
+ end
202
+ end
203
+
204
+ describe "missing password" do
205
+ before { params.delete('password') }
206
+
207
+ it "is invalid" do
208
+ exchange.error.should == 'invalid_request'
209
+ exchange.error_description.should == 'Missing required parameter password'
210
+ end
211
+ end
212
+
213
+ describe "with a bad password" do
214
+ before { params['password'] = 'bad' }
215
+
216
+ it "is invalid" do
217
+ exchange.error.should == 'invalid_grant'
218
+ exchange.error_description.should == 'The access grant you supplied is invalid'
219
+ end
220
+ end
221
+ end
222
+
223
+ describe "using assertion grant type" do
224
+ let(:params) { { 'client_id' => @client.client_id,
225
+ 'client_secret' => @client.client_secret,
226
+ 'grant_type' => 'assertion',
227
+ 'assertion_type' => 'https://graph.facebook.com/me',
228
+ 'assertion' => 'Bob' }
229
+ }
230
+
231
+ let(:authorization) { @authorization }
232
+
233
+ before do
234
+ OAuth2::Provider.filter_assertions { |client| @client == client }
235
+
236
+ OAuth2::Provider.handle_assertions('https://graph.facebook.com/me') do |client, assertion|
237
+ user = TestApp::User[assertion]
238
+ user.grant_access!(client, :scopes => ['foo', 'bar'])
239
+ end
240
+ end
241
+
242
+ after do
243
+ OAuth2::Provider.clear_assertion_handlers!
244
+ end
245
+
246
+ it_should_behave_like "validates required parameters"
247
+ it_should_behave_like "valid token request"
248
+
249
+ describe "missing assertion_type" do
250
+ before { params.delete('assertion_type') }
251
+
252
+ it "is invalid" do
253
+ exchange.error.should == 'invalid_request'
254
+ exchange.error_description.should == 'Missing required parameter assertion_type'
255
+ end
256
+ end
257
+
258
+ describe "with a non-URI assertion_type" do
259
+ before { params['assertion_type'] = 'invalid' }
260
+
261
+ it "is invalid" do
262
+ exchange.error.should == 'invalid_request'
263
+ exchange.error_description.should == 'Parameter assertion_type must be an absolute URI'
264
+ end
265
+ end
266
+
267
+ describe "missing assertion" do
268
+ before { params.delete('assertion') }
269
+
270
+ it "is invalid" do
271
+ exchange.error.should == 'invalid_request'
272
+ exchange.error_description.should == 'Missing required parameter assertion'
273
+ end
274
+ end
275
+
276
+ describe "with an unrecognized assertion_type" do
277
+ before { params['assertion_type'] = 'https://oauth.what.com/ohai' }
278
+
279
+ it "is invalid" do
280
+ exchange.error.should == 'unauthorized_client'
281
+ exchange.error_description.should == 'Client cannot use the given assertion type'
282
+ end
283
+ end
284
+
285
+ describe "with a client unauthorized to use the assertion scheme" do
286
+ before do
287
+ client = Factory(:client)
288
+ params['client_id'] = client.client_id
289
+ params['client_secret'] = client.client_secret
290
+ end
291
+
292
+ it "is invalid" do
293
+ exchange.error.should == 'unauthorized_client'
294
+ exchange.error_description.should == 'Client cannot use the given assertion type'
295
+ end
296
+ end
297
+ end
298
+
299
+ describe "using refresh_token grant type" do
300
+ before do
301
+ @refresher = Factory(:authorization, :client => @client,
302
+ :owner => @owner,
303
+ :scope => 'foo bar',
304
+ :code => nil,
305
+ :refresh_token => 'roflscale')
306
+ end
307
+
308
+ let(:params) { { 'client_id' => @client.client_id,
309
+ 'client_secret' => @client.client_secret,
310
+ 'grant_type' => 'refresh_token',
311
+ 'refresh_token' => 'roflscale' }
312
+ }
313
+
314
+ let(:authorization) { @refresher }
315
+
316
+ it_should_behave_like "validates required parameters"
317
+ it_should_behave_like "valid token request"
318
+
319
+ describe "with unknown refresh_token" do
320
+ before { params['refresh_token'] = 'woops' }
321
+
322
+ it "is invalid" do
323
+ exchange.error.should == "invalid_grant"
324
+ exchange.error_description.should == "The access grant you supplied is invalid"
325
+ end
326
+ end
327
+
328
+ end
329
+ end
330
+
@@ -0,0 +1,531 @@
1
+ require 'spec_helper'
2
+
3
+ describe OAuth2::Provider do
4
+ before(:all) { TestApp::Provider.start(8000) }
5
+ after(:all) { TestApp::Provider.stop }
6
+
7
+ let(:params) { { 'response_type' => 'code',
8
+ 'client_id' => @client.client_id,
9
+ 'redirect_uri' => @client.redirect_uri }
10
+ }
11
+
12
+ before do
13
+ @client = Factory(:client, :name => 'Test client')
14
+ @owner = TestApp::User['Bob']
15
+ end
16
+
17
+ include RequestHelpers
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(OAuth2::Provider::Authorization, :client => @client, :params => {}, :scopes => [])
23
+ OAuth2::Provider::Authorization.should_receive(:new).with(@owner, params).and_return(auth)
24
+ get(params)
25
+ end
26
+
27
+ it "displays an authorization page" do
28
+ response = get(params)
29
+ response.code.to_i.should == 200
30
+ response.body.should =~ /Do you want to allow Test client/
31
+ response['Content-Type'].should =~ /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 { OAuth2::Provider.enforce_ssl = true }
51
+
52
+ it "does not allow non-SSL requests" do
53
+ response = get(params)
54
+ validate_json_response(response, 400,
55
+ 'error' => 'invalid_request',
56
+ 'error_description' => 'Bad request: must make requests using HTTPS'
57
+ )
58
+ end
59
+ end
60
+
61
+ describe "when there is already a pending authorization from the user" do
62
+ before do
63
+ @authorization = OAuth2::Model::Authorization.create(
64
+ :owner => @owner,
65
+ :client => @client,
66
+ :code => 'pending_code',
67
+ :scope => 'offline_access')
68
+ end
69
+
70
+ it "immediately redirects with the code" do
71
+ response = get(params)
72
+ response.code.to_i.should == 302
73
+ response['location'].should == 'https://client.example.com/cb?code=pending_code'
74
+ end
75
+
76
+ describe "when the client is requesting scopes it already has access to" do
77
+ before { params['scope'] = 'offline_access' }
78
+
79
+ it "immediately redirects with the code" do
80
+ response = get(params)
81
+ response.code.to_i.should == 302
82
+ response['location'].should == 'https://client.example.com/cb?code=pending_code&scope=offline_access'
83
+ end
84
+ end
85
+
86
+ describe "when the client is requesting scopes it doesn't have yet" do
87
+ before { params['scope'] = 'wall_publish' }
88
+ it_should_behave_like "asks for user permission"
89
+ end
90
+
91
+ describe "and the authorization does not have a code" do
92
+ before { @authorization.update_attribute(:code, nil) }
93
+
94
+ it "generates a new code and redirects" do
95
+ OAuth2::Model::Authorization.should_not_receive(:create)
96
+ OAuth2::Model::Authorization.should_not_receive(:new)
97
+ OAuth2.should_receive(:random_string).and_return('new_code')
98
+ response = get(params)
99
+ response.code.to_i.should == 302
100
+ response['location'].should == 'https://client.example.com/cb?code=new_code'
101
+ end
102
+ end
103
+
104
+ describe "and the authorization is expired" do
105
+ before { @authorization.update_attribute(:expires_at, 2.hours.ago) }
106
+ it_should_behave_like "asks for user permission"
107
+ end
108
+ end
109
+
110
+ describe "when there is already a completed authorization from the user" do
111
+ before do
112
+ @authorization = OAuth2::Model::Authorization.create(
113
+ :owner => @owner,
114
+ :client => @client,
115
+ :code => nil,
116
+ :access_token => OAuth2.hashify('complete_token'))
117
+ end
118
+
119
+ it "immediately redirects with a new code" do
120
+ OAuth2.should_receive(:random_string).and_return('new_code')
121
+ response = get(params)
122
+ response.code.to_i.should == 302
123
+ response['location'].should == 'https://client.example.com/cb?code=new_code'
124
+ end
125
+
126
+ it "does not create a new Authorization" do
127
+ get(params)
128
+ OAuth2::Model::Authorization.count.should == 1
129
+ end
130
+
131
+ it "keeps the code and access token on the Authorization" do
132
+ get(params)
133
+ authorization = OAuth2::Model::Authorization.first
134
+ authorization.code.should_not be_nil
135
+ authorization.access_token_hash.should_not be_nil
136
+ end
137
+ end
138
+
139
+ describe "with no parameters" do
140
+ let(:params) { {} }
141
+
142
+ it "renders an error page" do
143
+ response = get(params)
144
+ validate_json_response(response, 400,
145
+ 'error' => 'invalid_request',
146
+ 'error_description' => 'This is not a valid OAuth request'
147
+ )
148
+ end
149
+ end
150
+
151
+ describe "with a redirect_uri and no client_id" do
152
+ let(:params) { {'redirect_uri' => 'http://evilsite.com/callback'} }
153
+
154
+ it "renders an error page" do
155
+ response = get(params)
156
+ validate_json_response(response, 400,
157
+ 'error' => 'invalid_request',
158
+ 'error_description' => 'This is not a valid OAuth request'
159
+ )
160
+ end
161
+ end
162
+
163
+ describe "with a client_id and a bad redirect_uri" do
164
+ let(:params) { {'redirect_uri' => 'http://evilsite.com/callback',
165
+ 'client_id' => @client.client_id} }
166
+
167
+ it "redirects to the client's registered redirect_uri" do
168
+ response = get(params)
169
+ response.code.to_i.should == 302
170
+ response['location'].should == 'https://client.example.com/cb?error=invalid_request&error_description=Missing+required+parameter+response_type'
171
+ end
172
+ end
173
+
174
+ describe "with an invalid request" do
175
+ before { params.delete('response_type') }
176
+
177
+ it "redirects to the client's redirect_uri on error" do
178
+ response = get(params)
179
+ response.code.to_i.should == 302
180
+ response['location'].should == 'https://client.example.com/cb?error=invalid_request&error_description=Missing+required+parameter+response_type'
181
+ end
182
+
183
+ describe "with a state parameter" do
184
+ before { params['state'] = 'foo' }
185
+
186
+ it "redirects to the client, including the state param" do
187
+ response = get(params)
188
+ response.code.to_i.should == 302
189
+ response['location'].should == 'https://client.example.com/cb?error=invalid_request&error_description=Missing+required+parameter+response_type&state=foo'
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ describe "authorization confirmation from the user" do
196
+ let(:mock_auth) do
197
+ mock = mock(OAuth2::Provider::Authorization)
198
+ mock.stub(:redirect_uri).and_return('http://example.com/')
199
+ OAuth2::Provider::Authorization.stub(:new).and_return(mock)
200
+ mock
201
+ end
202
+
203
+ describe "without the user's permission" do
204
+ before { params['allow'] = '' }
205
+
206
+ it "does not grant access" do
207
+ mock_auth.should_receive(:deny_access!)
208
+ allow_or_deny(params)
209
+ end
210
+
211
+ it "redirects to the client with an error" do
212
+ response = allow_or_deny(params)
213
+ response.code.to_i.should == 302
214
+ response['location'].should == 'https://client.example.com/cb?error=access_denied&error_description=The+user+denied+you+access'
215
+ end
216
+ end
217
+
218
+ describe "with valid parameters and user permission" do
219
+ before { OAuth2.stub(:random_string).and_return('foo') }
220
+ before { params['allow'] = '1' }
221
+
222
+ describe "for code requests" do
223
+ it "grants access" do
224
+ mock_auth.should_receive(:grant_access!)
225
+ allow_or_deny(params)
226
+ end
227
+
228
+ it "redirects to the client with an authorization code" do
229
+ response = allow_or_deny(params)
230
+ response.code.to_i.should == 302
231
+ response['location'].should == 'https://client.example.com/cb?code=foo'
232
+ end
233
+
234
+ it "passes the state parameter through" do
235
+ params['state'] = 'illinois'
236
+ response = allow_or_deny(params)
237
+ response.code.to_i.should == 302
238
+ response['location'].should == 'https://client.example.com/cb?code=foo&state=illinois'
239
+ end
240
+
241
+ it "passes the scope parameter through" do
242
+ params['scope'] = 'foo bar'
243
+ response = allow_or_deny(params)
244
+ response.code.to_i.should == 302
245
+ response['location'].should == 'https://client.example.com/cb?code=foo&scope=foo+bar'
246
+ end
247
+ end
248
+
249
+ describe "for token requests" do
250
+ before { params['response_type'] = 'token' }
251
+
252
+ it "grants access" do
253
+ mock_auth.should_receive(:grant_access!)
254
+ allow_or_deny(params)
255
+ end
256
+
257
+ it "redirects to the client with an access token" do
258
+ response = allow_or_deny(params)
259
+ response.code.to_i.should == 302
260
+ response['location'].should == 'https://client.example.com/cb#access_token=foo'
261
+ end
262
+
263
+ it "passes the state parameter through" do
264
+ params['state'] = 'illinois'
265
+ response = allow_or_deny(params)
266
+ response.code.to_i.should == 302
267
+ response['location'].should == 'https://client.example.com/cb#access_token=foo&state=illinois'
268
+ end
269
+
270
+ it "passes the scope parameter through" do
271
+ params['scope'] = 'foo bar'
272
+ response = allow_or_deny(params)
273
+ response.code.to_i.should == 302
274
+ response['location'].should == 'https://client.example.com/cb#access_token=foo&scope=foo+bar'
275
+ end
276
+ end
277
+
278
+ describe "for code_and_token requests" do
279
+ before { params['response_type'] = 'code_and_token' }
280
+
281
+ it "grants access" do
282
+ mock_auth.should_receive(:grant_access!)
283
+ allow_or_deny(params)
284
+ end
285
+
286
+ it "redirects to the client with an access token" do
287
+ response = allow_or_deny(params)
288
+ response.code.to_i.should == 302
289
+ response['location'].should == 'https://client.example.com/cb?code=foo#access_token=foo'
290
+ end
291
+
292
+ it "passes the state parameter through" do
293
+ params['state'] = 'illinois'
294
+ response = allow_or_deny(params)
295
+ response.code.to_i.should == 302
296
+ response['location'].should == 'https://client.example.com/cb?code=foo&state=illinois#access_token=foo'
297
+ end
298
+
299
+ it "passes the scope parameter through" do
300
+ params['scope'] = 'foo bar'
301
+ response = allow_or_deny(params)
302
+ response.code.to_i.should == 302
303
+ response['location'].should == 'https://client.example.com/cb?code=foo#access_token=foo&scope=foo+bar'
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ describe "access token request" do
310
+ before do
311
+ @client = Factory(:client)
312
+ @authorization = Factory(:authorization, :client => @client, :owner => @owner)
313
+ end
314
+
315
+ let(:auth_params) { { 'client_id' => @client.client_id,
316
+ 'client_secret' => @client.client_secret }
317
+ }
318
+
319
+ describe "using authorization_code request" do
320
+ let(:query_params) { { 'client_id' => @client.client_id,
321
+ 'grant_type' => 'authorization_code',
322
+ 'code' => @authorization.code,
323
+ 'redirect_uri' => @client.redirect_uri }
324
+ }
325
+
326
+ let(:params) { auth_params.merge(query_params) }
327
+
328
+ describe "with valid parameters" do
329
+ it "does not respond to GET" do
330
+ OAuth2::Provider::Authorization.should_not_receive(:new)
331
+ OAuth2::Provider::Exchange.should_not_receive(:new)
332
+ response = get(params)
333
+ validate_json_response(response, 400,
334
+ 'error' => 'invalid_request',
335
+ 'error_description' => 'Bad request: should be a POST request'
336
+ )
337
+ end
338
+
339
+ describe "enforcing SSL" do
340
+ before { OAuth2::Provider.enforce_ssl = true }
341
+
342
+ it "does not allow non-SSL requests" do
343
+ response = get(params)
344
+ validate_json_response(response, 400,
345
+ 'error' => 'invalid_request',
346
+ 'error_description' => 'Bad request: must make requests using HTTPS'
347
+ )
348
+ end
349
+ end
350
+
351
+ it "creates a Token when using Basic Auth" do
352
+ token = mock_request(OAuth2::Provider::Exchange, :response_body => 'Hello')
353
+ OAuth2::Provider::Exchange.should_receive(:new).with(@owner, params).and_return(token)
354
+ post_basic_auth(auth_params, query_params)
355
+ end
356
+
357
+ it "creates a Token when passing params in the POST body" do
358
+ token = mock_request(OAuth2::Provider::Exchange, :response_body => 'Hello')
359
+ OAuth2::Provider::Exchange.should_receive(:new).with(@owner, params).and_return(token)
360
+ post(params)
361
+ end
362
+
363
+ it "returns a successful response" do
364
+ OAuth2.stub(:random_string).and_return('random_access_token')
365
+ response = post_basic_auth(auth_params, query_params)
366
+ validate_json_response(response, 200, 'access_token' => 'random_access_token')
367
+ end
368
+
369
+ describe "with a scope parameter" do
370
+ before do
371
+ @authorization.update_attribute(:scope, 'foo bar')
372
+ end
373
+
374
+ it "passes the scope back in the success response" do
375
+ OAuth2.stub(:random_string).and_return('random_access_token')
376
+ response = post_basic_auth(auth_params, query_params)
377
+ validate_json_response(response, 200,
378
+ 'access_token' => 'random_access_token',
379
+ 'scope' => 'foo bar'
380
+ )
381
+ end
382
+ end
383
+ end
384
+
385
+ describe "with invalid parameters" do
386
+ before { query_params.delete('code') }
387
+
388
+ it "returns an error response" do
389
+ response = post_basic_auth(auth_params, query_params)
390
+ validate_json_response(response, 400,
391
+ 'error' => 'invalid_request',
392
+ 'error_description' => 'Missing required parameter code'
393
+ )
394
+ end
395
+ end
396
+
397
+ describe "with mismatched client_id in POST params and Basic Auth params" do
398
+ before { query_params['client_id'] = 'foo' }
399
+
400
+ it "returns an error response" do
401
+ response = post_basic_auth(auth_params, query_params)
402
+ validate_json_response(response, 400,
403
+ 'error' => 'invalid_request',
404
+ 'error_description' => 'Bad request: client_id from Basic Auth and request body do not match'
405
+ )
406
+ end
407
+ end
408
+
409
+ describe "when there is an Authorization with code and token" do
410
+ before do
411
+ @authorization.update_attributes(:code => 'pending_code', :access_token => 'working_token')
412
+ OAuth2.stub(:random_string).and_return('random_access_token')
413
+ end
414
+
415
+ it "returns a new access token" do
416
+ response = post(params)
417
+ validate_json_response(response, 200,
418
+ 'access_token' => 'random_access_token'
419
+ )
420
+ end
421
+
422
+ it "exchanges the code for the new token on the existing Authorization" do
423
+ post(params)
424
+ @authorization.reload
425
+ @authorization.code.should be_nil
426
+ @authorization.access_token_hash.should == OAuth2.hashify('random_access_token')
427
+ end
428
+ end
429
+ end
430
+ end
431
+
432
+ describe "protected resource request" do
433
+ before do
434
+ @authorization = Factory(:authorization,
435
+ :owner => @owner,
436
+ :access_token => 'magic-key',
437
+ :scope => 'profile')
438
+ end
439
+
440
+ shared_examples_for "protected resource" do
441
+ it "creates an AccessToken response" do
442
+ mock_token = mock(OAuth2::Provider::AccessToken)
443
+ mock_token.should_receive(:response_headers).and_return({})
444
+ mock_token.should_receive(:response_status).and_return(200)
445
+ mock_token.should_receive(:valid?).and_return(true)
446
+ OAuth2::Provider::AccessToken.should_receive(:new).with(TestApp::User['Bob'], ['profile'], 'magic-key', nil).and_return(mock_token)
447
+ request('/user_profile', 'oauth_token' => 'magic-key')
448
+ end
449
+
450
+ it "allows access when the key is passed" do
451
+ response = request('/user_profile', 'oauth_token' => 'magic-key')
452
+ JSON.parse(response.body)['data'].should == 'Top secret'
453
+ response.code.to_i.should == 200
454
+ end
455
+
456
+ it "blocks access when the wrong key is passed" do
457
+ response = request('/user_profile', 'oauth_token' => 'is-the-password-books')
458
+ JSON.parse(response.body)['data'].should == 'No soup for you'
459
+ response.code.to_i.should == 401
460
+ response['WWW-Authenticate'].should == "OAuth realm='Demo App', error='invalid_token'"
461
+ end
462
+
463
+ it "blocks access when the no key is passed" do
464
+ response = request('/user_profile')
465
+ JSON.parse(response.body)['data'].should == 'No soup for you'
466
+ response.code.to_i.should == 401
467
+ response['WWW-Authenticate'].should == "OAuth realm='Demo App'"
468
+ end
469
+
470
+ describe "enforcing SSL" do
471
+ before { OAuth2::Provider.enforce_ssl = true }
472
+
473
+ let(:authorization) do
474
+ OAuth2::Model::Authorization.find_by_access_token_hash(OAuth2.hashify('magic-key'))
475
+ end
476
+
477
+ it "blocks access when not using HTTPS" do
478
+ response = request('/user_profile', 'oauth_token' => 'magic-key')
479
+ JSON.parse(response.body)['data'].should == 'No soup for you'
480
+ response.code.to_i.should == 401
481
+ response['WWW-Authenticate'].should == "OAuth realm='Demo App', error='invalid_request'"
482
+ end
483
+
484
+ it "destroys the access token since it's been leaked" do
485
+ authorization.access_token_hash.should == OAuth2.hashify('magic-key')
486
+ request('/user_profile', 'oauth_token' => 'magic-key')
487
+ authorization.reload
488
+ authorization.access_token_hash.should be_nil
489
+ end
490
+
491
+ it "keeps the access token if the wrong key is passed" do
492
+ authorization.access_token_hash.should == OAuth2.hashify('magic-key')
493
+ request('/user_profile', 'oauth_token' => 'is-the-password-books')
494
+ authorization.reload
495
+ authorization.access_token_hash.should == OAuth2.hashify('magic-key')
496
+ end
497
+ end
498
+ end
499
+
500
+ describe "for header-based requests" do
501
+ def request(path, params = {})
502
+ access_token = params.delete('oauth_token')
503
+ http = Net::HTTP.new('localhost', 8000)
504
+ qs = params.map { |k,v| "#{ CGI.escape k.to_s }=#{ CGI.escape v.to_s }" }.join('&')
505
+ header = {'Authorization' => "OAuth #{access_token}"}
506
+ http.request_get(path + '?' + qs, header)
507
+ end
508
+
509
+ it_should_behave_like "protected resource"
510
+ end
511
+
512
+ describe "for GET requests" do
513
+ def request(path, params = {})
514
+ qs = params.map { |k,v| "#{ CGI.escape k.to_s }=#{ CGI.escape v.to_s }" }.join('&')
515
+ uri = URI.parse('http://localhost:8000' + path + '?' + qs)
516
+ Net::HTTP.get_response(uri)
517
+ end
518
+
519
+ it_should_behave_like "protected resource"
520
+ end
521
+
522
+ describe "for POST requests" do
523
+ def request(path, params = {})
524
+ Net::HTTP.post_form(URI.parse('http://localhost:8000' + path), params)
525
+ end
526
+
527
+ it_should_behave_like "protected resource"
528
+ end
529
+ end
530
+ end
531
+