oauth2-provider-jonrowe 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.
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
+