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
@@ -4,26 +4,26 @@ describe Songkick::OAuth2::Provider::AccessToken do
4
4
  before do
5
5
  @alice = TestApp::User['Alice']
6
6
  @bob = TestApp::User['Bob']
7
-
7
+
8
8
  create_authorization(
9
9
  :owner => @alice,
10
10
  :client => Factory(:client),
11
11
  :scope => 'profile',
12
12
  :access_token => 'sesame')
13
-
13
+
14
14
  @authorization = create_authorization(
15
15
  :owner => @bob,
16
16
  :client => Factory(:client),
17
17
  :scope => 'profile',
18
18
  :access_token => 'magic-key')
19
-
19
+
20
20
  Songkick::OAuth2::Provider.realm = 'Demo App'
21
21
  end
22
-
22
+
23
23
  let :token do
24
24
  Songkick::OAuth2::Provider::AccessToken.new(@bob, ['profile'], 'magic-key')
25
25
  end
26
-
26
+
27
27
  shared_examples_for "valid token" do
28
28
  it "is valid" do
29
29
  token.should be_valid
@@ -38,7 +38,7 @@ describe Songkick::OAuth2::Provider::AccessToken do
38
38
  token.owner.should == @bob
39
39
  end
40
40
  end
41
-
41
+
42
42
  shared_examples_for "invalid token" do
43
43
  it "is not valid" do
44
44
  token.should_not be_valid
@@ -47,77 +47,89 @@ describe Songkick::OAuth2::Provider::AccessToken do
47
47
  token.owner.should be_nil
48
48
  end
49
49
  end
50
-
50
+
51
51
  describe "with the right user, scope and token" do
52
52
  it_should_behave_like "valid token"
53
53
  end
54
-
54
+
55
+ describe "with an implicit user" do
56
+ let :token do
57
+ Songkick::OAuth2::Provider::AccessToken.new(:implicit, ['profile'], 'magic-key')
58
+ end
59
+ it_should_behave_like "valid token"
60
+ end
61
+
55
62
  describe "with no user" do
56
63
  let :token do
57
64
  Songkick::OAuth2::Provider::AccessToken.new(nil, ['profile'], 'magic-key')
58
65
  end
59
- it_should_behave_like "valid token"
66
+ it_should_behave_like "invalid token"
67
+
68
+ it "returns an error response" do
69
+ token.response_headers['WWW-Authenticate'].should == "OAuth realm='Demo App', error='invalid_token'"
70
+ token.response_status.should == 401
71
+ end
60
72
  end
61
-
73
+
62
74
  describe "with less scope than was granted" do
63
75
  let :token do
64
76
  Songkick::OAuth2::Provider::AccessToken.new(@bob, [], 'magic-key')
65
77
  end
66
78
  it_should_behave_like "valid token"
67
79
  end
68
-
80
+
69
81
  describe "when the authorization has expired" do
70
82
  before { @authorization.update_attribute(:expires_at, 1.hour.ago) }
71
83
  it_should_behave_like "invalid token"
72
-
84
+
73
85
  it "returns an error response" do
74
86
  token.response_headers['WWW-Authenticate'].should == "OAuth realm='Demo App', error='expired_token'"
75
87
  token.response_status.should == 401
76
88
  end
77
89
  end
78
-
90
+
79
91
  describe "with a non-existent token" do
80
92
  let :token do
81
93
  Songkick::OAuth2::Provider::AccessToken.new(@bob, ['profile'], 'is-the-password-books')
82
94
  end
83
95
  it_should_behave_like "invalid token"
84
-
96
+
85
97
  it "returns an error response" do
86
98
  token.response_headers['WWW-Authenticate'].should == "OAuth realm='Demo App', error='invalid_token'"
87
99
  token.response_status.should == 401
88
100
  end
89
101
  end
90
-
102
+
91
103
  describe "with a token for the wrong user" do
92
104
  let :token do
93
105
  Songkick::OAuth2::Provider::AccessToken.new(@bob, ['profile'], 'sesame')
94
106
  end
95
107
  it_should_behave_like "invalid token"
96
-
108
+
97
109
  it "returns an error response" do
98
110
  token.response_headers['WWW-Authenticate'].should == "OAuth realm='Demo App', error='insufficient_scope'"
99
111
  token.response_status.should == 403
100
112
  end
101
113
  end
102
-
114
+
103
115
  describe "with a token for an ungranted scope" do
104
116
  let :token do
105
117
  Songkick::OAuth2::Provider::AccessToken.new(@bob, ['offline_access'], 'magic-key')
106
118
  end
107
119
  it_should_behave_like "invalid token"
108
-
120
+
109
121
  it "returns an error response" do
110
122
  token.response_headers['WWW-Authenticate'].should == "OAuth realm='Demo App', error='insufficient_scope'"
111
123
  token.response_status.should == 403
112
124
  end
113
125
  end
114
-
126
+
115
127
  describe "with no token string" do
116
128
  let :token do
117
129
  Songkick::OAuth2::Provider::AccessToken.new(@bob, ['profile'], nil)
118
130
  end
119
131
  it_should_behave_like "invalid token"
120
-
132
+
121
133
  it "returns an error response" do
122
134
  token.response_headers['WWW-Authenticate'].should == "OAuth realm='Demo App'"
123
135
  token.response_status.should == 401
@@ -2,157 +2,168 @@ require 'spec_helper'
2
2
 
3
3
  describe Songkick::OAuth2::Provider::Authorization do
4
4
  let(:resource_owner) { TestApp::User['Bob'] }
5
-
5
+
6
6
  let(:authorization) { Songkick::OAuth2::Provider::Authorization.new(resource_owner, params) }
7
-
7
+
8
8
  let(:params) { { 'response_type' => 'code',
9
9
  'client_id' => @client.client_id,
10
10
  'redirect_uri' => @client.redirect_uri }
11
11
  }
12
-
12
+
13
13
  before do
14
14
  @client = Factory(:client)
15
15
  Songkick::OAuth2.stub(:random_string).and_return('s1', 's2', 's3')
16
16
  end
17
-
17
+
18
18
  describe "with valid parameters" do
19
19
  it "is valid" do
20
20
  authorization.error.should be_nil
21
21
  end
22
22
  end
23
-
23
+
24
24
  describe "with the scope parameter" do
25
25
  before { params['scope'] = 'foo bar qux' }
26
-
26
+
27
27
  it "exposes the scope as a list of strings" do
28
28
  authorization.scopes.should == Set.new(%w[foo bar qux])
29
29
  end
30
-
30
+
31
31
  it "exposes the scopes the client has not yet granted" do
32
32
  authorization.unauthorized_scopes.should == Set.new(%w[foo bar qux])
33
33
  end
34
-
34
+
35
35
  describe "when the owner has already authorized the client" do
36
36
  before do
37
37
  create_authorization(:owner => resource_owner, :client => @client, :scope => 'foo bar')
38
38
  end
39
-
39
+
40
40
  it "exposes the scope as a list of strings" do
41
41
  authorization.scopes.should == Set.new(%w[foo bar qux])
42
42
  end
43
-
43
+
44
44
  it "exposes the scopes the client has not yet granted" do
45
45
  authorization.unauthorized_scopes.should == %w[qux]
46
46
  end
47
47
  end
48
48
  end
49
-
49
+
50
50
  describe "missing response_type" do
51
51
  before { params.delete('response_type') }
52
-
52
+
53
53
  it "is invalid" do
54
54
  authorization.error.should == "invalid_request"
55
55
  authorization.error_description.should == "Missing required parameter response_type"
56
56
  end
57
57
  end
58
-
58
+
59
59
  describe "with a bad response_type" do
60
60
  before { params['response_type'] = "no_such_type" }
61
-
61
+
62
62
  it "is invalid" do
63
63
  authorization.error.should == "unsupported_response_type"
64
64
  authorization.error_description.should == "Response type no_such_type is not supported"
65
65
  end
66
-
66
+
67
67
  it "causes a redirect" do
68
68
  authorization.should be_redirect
69
69
  authorization.redirect_uri.should == "https://client.example.com/cb?error=unsupported_response_type&error_description=Response+type+no_such_type+is+not+supported"
70
70
  end
71
71
  end
72
-
72
+
73
73
  describe "missing client_id" do
74
74
  before { params.delete('client_id') }
75
-
75
+
76
76
  it "is invalid" do
77
77
  authorization.error.should == "invalid_request"
78
78
  authorization.error_description.should == "Missing required parameter client_id"
79
79
  end
80
-
80
+
81
81
  it "does not cause a redirect" do
82
82
  authorization.should_not be_redirect
83
83
  end
84
84
  end
85
-
85
+
86
86
  describe "with an unknown client_id" do
87
87
  before { params['client_id'] = "unknown" }
88
-
88
+
89
89
  it "is invalid" do
90
90
  authorization.error.should == "invalid_client"
91
91
  authorization.error_description.should == "Unknown client ID unknown"
92
92
  end
93
-
93
+
94
94
  it "does not cause a redirect" do
95
95
  authorization.should_not be_redirect
96
96
  end
97
97
  end
98
-
98
+
99
99
  describe "missing redirect_uri" do
100
100
  before { params.delete('redirect_uri') }
101
-
101
+
102
102
  it "is invalid" do
103
103
  authorization.error.should == "invalid_request"
104
104
  authorization.error_description.should == "Missing required parameter redirect_uri"
105
105
  end
106
-
106
+
107
107
  it "causes a redirect to the client's registered redirect_uri" do
108
108
  authorization.should be_redirect
109
109
  authorization.redirect_uri.should == "https://client.example.com/cb?error=invalid_request&error_description=Missing+required+parameter+redirect_uri"
110
110
  end
111
111
  end
112
-
112
+
113
113
  describe "with a mismatched redirect_uri" do
114
114
  before { params['redirect_uri'] = "http://songkick.com" }
115
-
115
+
116
116
  it "is invalid" do
117
117
  authorization.error.should == "redirect_uri_mismatch"
118
118
  authorization.error_description.should == "Parameter redirect_uri does not match registered URI"
119
119
  end
120
-
120
+
121
121
  it "causes a redirect to the client's registered redirect_uri" do
122
122
  authorization.should be_redirect
123
123
  authorization.redirect_uri.should == "https://client.example.com/cb?error=redirect_uri_mismatch&error_description=Parameter+redirect_uri+does+not+match+registered+URI"
124
124
  end
125
-
125
+
126
126
  describe "when the client has not registered a redirect_uri" do
127
127
  before { @client.update_attribute(:redirect_uri, nil) }
128
-
128
+
129
129
  it "is valid" do
130
130
  authorization.error.should be_nil
131
131
  end
132
132
  end
133
133
  end
134
-
134
+
135
+ describe "with a redirect_uri with parameters" do
136
+ before do
137
+ authorization.client.redirect_uri = "http://songkick.com?some_parameter"
138
+ params['redirect_uri'] = "http://songkick.com?some_parameter"
139
+ end
140
+
141
+ it "adds the extra parameters with & instead of ?" do
142
+ authorization.redirect_uri.should == "http://songkick.com?some_parameter&"
143
+ end
144
+ end
145
+
135
146
  # http://en.wikipedia.org/wiki/HTTP_response_splitting
136
147
  # scope and state values are passed back in the redirect
137
-
148
+
138
149
  describe "with an illegal scope" do
139
150
  before { params['scope'] = "http\r\nsplitter" }
140
-
151
+
141
152
  it "is invalid" do
142
153
  authorization.error.should == "invalid_request"
143
154
  authorization.error_description.should == "Illegal value for scope parameter"
144
155
  end
145
156
  end
146
-
157
+
147
158
  describe "with an illegal state" do
148
159
  before { params['state'] = "http\r\nsplitter" }
149
-
160
+
150
161
  it "is invalid" do
151
162
  authorization.error.should == "invalid_request"
152
163
  authorization.error_description.should == "Illegal value for state parameter"
153
164
  end
154
165
  end
155
-
166
+
156
167
  describe "#grant_access!" do
157
168
  describe "when there is an existing authorization with no code" do
158
169
  before do
@@ -161,7 +172,7 @@ describe Songkick::OAuth2::Provider::Authorization do
161
172
  :client => @client,
162
173
  :code => nil)
163
174
  end
164
-
175
+
165
176
  it "generates and returns a code to the client" do
166
177
  authorization.grant_access!
167
178
  @model.reload
@@ -169,7 +180,7 @@ describe Songkick::OAuth2::Provider::Authorization do
169
180
  authorization.code.should == "s1"
170
181
  end
171
182
  end
172
-
183
+
173
184
  describe "when there is an existing authorization with scopes" do
174
185
  before do
175
186
  @model = create_authorization(
@@ -177,17 +188,17 @@ describe Songkick::OAuth2::Provider::Authorization do
177
188
  :client => @client,
178
189
  :code => nil,
179
190
  :scope => 'foo bar')
180
-
191
+
181
192
  params['scope'] = 'qux'
182
193
  end
183
-
194
+
184
195
  it "merges the new scopes with the existing ones" do
185
196
  authorization.grant_access!
186
197
  @model.reload
187
198
  @model.scopes.should == Set.new(%w[foo bar qux])
188
199
  end
189
200
  end
190
-
201
+
191
202
  describe "when there is an existing expired authorization" do
192
203
  before do
193
204
  @model = create_authorization(
@@ -197,26 +208,26 @@ describe Songkick::OAuth2::Provider::Authorization do
197
208
  :code => 'existing_code',
198
209
  :scope => 'foo bar')
199
210
  end
200
-
211
+
201
212
  it "renews the authorization" do
202
213
  authorization.grant_access!
203
214
  @model.reload
204
215
  @model.expires_at.should be_nil
205
216
  end
206
-
217
+
207
218
  it "returns a code to the client" do
208
219
  authorization.grant_access!
209
220
  authorization.code.should == "existing_code"
210
221
  authorization.access_token.should be_nil
211
222
  end
212
-
223
+
213
224
  it "sets the expiry time if a duration is given" do
214
225
  authorization.grant_access!(:duration => 1.hour)
215
226
  @model.reload
216
227
  @model.expires_in.should == 3600
217
228
  authorization.expires_in.should == 3600
218
229
  end
219
-
230
+
220
231
  it "augments the scope" do
221
232
  params['scope'] = 'qux'
222
233
  authorization.grant_access!
@@ -224,29 +235,29 @@ describe Songkick::OAuth2::Provider::Authorization do
224
235
  @model.scopes.should == Set.new(%w[foo bar qux])
225
236
  end
226
237
  end
227
-
238
+
228
239
  describe "for code requests" do
229
240
  before do
230
241
  params['response_type'] = 'code'
231
242
  params['scope'] = 'foo bar'
232
243
  end
233
-
244
+
234
245
  it "makes the authorization redirect" do
235
246
  authorization.grant_access!
236
247
  authorization.client.should_not be_nil
237
248
  authorization.should be_redirect
238
249
  end
239
-
250
+
240
251
  it "creates a code for the authorization" do
241
252
  authorization.grant_access!
242
253
  authorization.code.should == "s1"
243
254
  authorization.access_token.should be_nil
244
255
  authorization.expires_in.should be_nil
245
256
  end
246
-
257
+
247
258
  it "creates an Authorization in the database" do
248
259
  authorization.grant_access!
249
-
260
+
250
261
  authorization = Songkick::OAuth2::Model::Authorization.first
251
262
  authorization.owner.should == resource_owner
252
263
  authorization.client.should == @client
@@ -254,10 +265,10 @@ describe Songkick::OAuth2::Provider::Authorization do
254
265
  authorization.scopes.should == Set.new(%w[foo bar])
255
266
  end
256
267
  end
257
-
268
+
258
269
  describe "for token requests" do
259
270
  before { params['response_type'] = 'token' }
260
-
271
+
261
272
  it "creates a token for the authorization" do
262
273
  authorization.grant_access!
263
274
  authorization.code.should be_nil
@@ -265,10 +276,10 @@ describe Songkick::OAuth2::Provider::Authorization do
265
276
  authorization.refresh_token.should == "s2"
266
277
  authorization.expires_in.should be_nil
267
278
  end
268
-
279
+
269
280
  it "creates an Authorization in the database" do
270
281
  authorization.grant_access!
271
-
282
+
272
283
  authorization = Songkick::OAuth2::Model::Authorization.first
273
284
  authorization.owner.should == resource_owner
274
285
  authorization.client.should == @client
@@ -277,10 +288,10 @@ describe Songkick::OAuth2::Provider::Authorization do
277
288
  authorization.refresh_token_hash.should == Songkick::OAuth2.hashify("s2")
278
289
  end
279
290
  end
280
-
291
+
281
292
  describe "for code_and_token requests" do
282
293
  before { params['response_type'] = 'code_and_token' }
283
-
294
+
284
295
  it "creates a code and token for the authorization" do
285
296
  authorization.grant_access!
286
297
  authorization.code.should == "s1"
@@ -288,10 +299,10 @@ describe Songkick::OAuth2::Provider::Authorization do
288
299
  authorization.refresh_token.should == "s3"
289
300
  authorization.expires_in.should be_nil
290
301
  end
291
-
302
+
292
303
  it "creates an Authorization in the database" do
293
304
  authorization.grant_access!
294
-
305
+
295
306
  authorization = Songkick::OAuth2::Model::Authorization.first
296
307
  authorization.owner.should == resource_owner
297
308
  authorization.client.should == @client
@@ -301,27 +312,27 @@ describe Songkick::OAuth2::Provider::Authorization do
301
312
  end
302
313
  end
303
314
  end
304
-
315
+
305
316
  describe "#deny_access!" do
306
317
  it "puts the authorization in an error state" do
307
318
  authorization.deny_access!
308
319
  authorization.error.should == "access_denied"
309
320
  authorization.error_description.should == "The user denied you access"
310
321
  end
311
-
322
+
312
323
  it "does not create an Authorization" do
313
324
  Songkick::OAuth2::Model::Authorization.should_not_receive(:create)
314
325
  Songkick::OAuth2::Model::Authorization.should_not_receive(:new)
315
326
  authorization.deny_access!
316
327
  end
317
328
  end
318
-
329
+
319
330
  describe "#params" do
320
331
  before do
321
332
  params['scope'] = params['state'] = 'valid'
322
333
  params['controller'] = 'invalid'
323
334
  end
324
-
335
+
325
336
  it "only exposes OAuth-related parameters" do
326
337
  authorization.params.should == {
327
338
  'response_type' => 'code',
@@ -331,7 +342,7 @@ describe Songkick::OAuth2::Provider::Authorization do
331
342
  'scope' => 'valid'
332
343
  }
333
344
  end
334
-
345
+
335
346
  it "does not expose parameters with no value" do
336
347
  params.delete('scope')
337
348
  authorization.params.should == {