songkick-oauth2-provider 0.10.2 → 0.10.3

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