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