tpitale-rack-oauth2-server 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/CHANGELOG +202 -0
  2. data/Gemfile +16 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +604 -0
  5. data/Rakefile +90 -0
  6. data/VERSION +1 -0
  7. data/bin/oauth2-server +206 -0
  8. data/lib/rack-oauth2-server.rb +4 -0
  9. data/lib/rack/oauth2/admin/css/screen.css +347 -0
  10. data/lib/rack/oauth2/admin/images/loading.gif +0 -0
  11. data/lib/rack/oauth2/admin/images/oauth-2.png +0 -0
  12. data/lib/rack/oauth2/admin/js/application.coffee +220 -0
  13. data/lib/rack/oauth2/admin/js/jquery.js +166 -0
  14. data/lib/rack/oauth2/admin/js/jquery.tmpl.js +414 -0
  15. data/lib/rack/oauth2/admin/js/protovis-r3.2.js +277 -0
  16. data/lib/rack/oauth2/admin/js/sammy.js +5 -0
  17. data/lib/rack/oauth2/admin/js/sammy.json.js +5 -0
  18. data/lib/rack/oauth2/admin/js/sammy.oauth2.js +142 -0
  19. data/lib/rack/oauth2/admin/js/sammy.storage.js +5 -0
  20. data/lib/rack/oauth2/admin/js/sammy.title.js +5 -0
  21. data/lib/rack/oauth2/admin/js/sammy.tmpl.js +5 -0
  22. data/lib/rack/oauth2/admin/js/underscore.js +722 -0
  23. data/lib/rack/oauth2/admin/views/client.tmpl +58 -0
  24. data/lib/rack/oauth2/admin/views/clients.tmpl +52 -0
  25. data/lib/rack/oauth2/admin/views/edit.tmpl +80 -0
  26. data/lib/rack/oauth2/admin/views/index.html +39 -0
  27. data/lib/rack/oauth2/admin/views/no_access.tmpl +4 -0
  28. data/lib/rack/oauth2/models.rb +27 -0
  29. data/lib/rack/oauth2/models/access_grant.rb +54 -0
  30. data/lib/rack/oauth2/models/access_token.rb +129 -0
  31. data/lib/rack/oauth2/models/auth_request.rb +61 -0
  32. data/lib/rack/oauth2/models/client.rb +93 -0
  33. data/lib/rack/oauth2/rails.rb +105 -0
  34. data/lib/rack/oauth2/server.rb +458 -0
  35. data/lib/rack/oauth2/server/admin.rb +250 -0
  36. data/lib/rack/oauth2/server/errors.rb +104 -0
  37. data/lib/rack/oauth2/server/helper.rb +147 -0
  38. data/lib/rack/oauth2/server/practice.rb +79 -0
  39. data/lib/rack/oauth2/server/railtie.rb +24 -0
  40. data/lib/rack/oauth2/server/utils.rb +30 -0
  41. data/lib/rack/oauth2/sinatra.rb +71 -0
  42. data/rack-oauth2-server.gemspec +24 -0
  43. data/rails/init.rb +11 -0
  44. data/test/admin/api_test.rb +228 -0
  45. data/test/admin/ui_test.rb +38 -0
  46. data/test/oauth/access_grant_test.rb +276 -0
  47. data/test/oauth/access_token_test.rb +311 -0
  48. data/test/oauth/authorization_test.rb +298 -0
  49. data/test/oauth/server_methods_test.rb +292 -0
  50. data/test/rails2/app/controllers/api_controller.rb +40 -0
  51. data/test/rails2/app/controllers/application_controller.rb +2 -0
  52. data/test/rails2/app/controllers/oauth_controller.rb +17 -0
  53. data/test/rails2/config/environment.rb +19 -0
  54. data/test/rails2/config/environments/test.rb +0 -0
  55. data/test/rails2/config/routes.rb +13 -0
  56. data/test/rails3/app/controllers/api_controller.rb +40 -0
  57. data/test/rails3/app/controllers/application_controller.rb +2 -0
  58. data/test/rails3/app/controllers/oauth_controller.rb +17 -0
  59. data/test/rails3/config/application.rb +19 -0
  60. data/test/rails3/config/environment.rb +2 -0
  61. data/test/rails3/config/routes.rb +12 -0
  62. data/test/setup.rb +120 -0
  63. data/test/sinatra/my_app.rb +69 -0
  64. metadata +145 -0
@@ -0,0 +1,298 @@
1
+ require "test/setup"
2
+
3
+
4
+ # 3. Obtaining End-User Authorization
5
+ class AuthorizationTest < Test::Unit::TestCase
6
+ module Helpers
7
+
8
+ def should_redirect_with_error(error)
9
+ should "respond with status code 302 (Found)" do
10
+ assert_equal 302, last_response.status
11
+ end
12
+ should "redirect back to redirect_uri" do
13
+ assert_equal URI.parse(last_response["Location"]).host, "uberclient.dot"
14
+ end
15
+ should "redirect with error code #{error}" do
16
+ assert_equal error.to_s, Rack::Utils.parse_query(URI.parse(last_response["Location"]).query)["error"]
17
+ end
18
+ should "redirect with state parameter" do
19
+ assert_equal "bring this back", Rack::Utils.parse_query(URI.parse(last_response["Location"]).query)["state"]
20
+ end
21
+ end
22
+
23
+ def should_ask_user_for_authorization(&block)
24
+ should "inform user about client" do
25
+ response = last_response.body.split("\n").inject({}) { |h,l| n,v = l.split(/:\s*/) ; h[n.downcase] = v ; h }
26
+ assert_equal "UberClient", response["client"]
27
+ end
28
+ should "inform user about scope" do
29
+ response = last_response.body.split("\n").inject({}) { |h,l| n,v = l.split(/:\s*/) ; h[n.downcase] = v ; h }
30
+ assert_equal "read, write", response["scope"]
31
+ end
32
+ end
33
+
34
+ end
35
+ extend Helpers
36
+
37
+ def setup
38
+ super
39
+ @params = { :redirect_uri=>client.redirect_uri, :client_id=>client.id, :client_secret=>client.secret, :response_type=>"code",
40
+ :scope=>"read write", :state=>"bring this back" }
41
+ end
42
+
43
+ def request_authorization(changes = nil)
44
+ get "/oauth/authorize?" + Rack::Utils.build_query(@params.merge(changes || {}))
45
+ get last_response["Location"] if last_response.status == 303
46
+ end
47
+
48
+ def authorization
49
+ last_response.body[/authorization:\s*(\S+)/, 1]
50
+ end
51
+
52
+
53
+ # Checks before we request user for authorization.
54
+ # 3.2. Error Response
55
+
56
+ context "no redirect URI" do
57
+ setup { request_authorization :redirect_uri=>nil }
58
+ should "return status 400" do
59
+ assert_equal 400, last_response.status
60
+ end
61
+ end
62
+
63
+ context "invalid redirect URI" do
64
+ setup { request_authorization :redirect_uri=>"http:not-valid" }
65
+ should "return status 400" do
66
+ assert_equal 400, last_response.status
67
+ end
68
+ end
69
+
70
+ context "no client ID" do
71
+ setup { request_authorization :client_id=>nil }
72
+ should_redirect_with_error :invalid_client
73
+ end
74
+
75
+ context "invalid client ID" do
76
+ setup { request_authorization :client_id=>"foobar" }
77
+ should_redirect_with_error :invalid_client
78
+ end
79
+
80
+ context "client ID but no such client" do
81
+ setup { request_authorization :client_id=>"4cc7bc483321e814b8000000" }
82
+ should_redirect_with_error :invalid_client
83
+ end
84
+
85
+ context "mismatched redirect URI" do
86
+ setup { request_authorization :redirect_uri=>"http://uberclient.dot/oz" }
87
+ should_redirect_with_error :redirect_uri_mismatch
88
+ end
89
+
90
+ context "revoked client" do
91
+ setup do
92
+ client.revoke!
93
+ request_authorization
94
+ end
95
+ should_redirect_with_error :invalid_client
96
+ end
97
+
98
+ context "no response type" do
99
+ setup { request_authorization :response_type=>nil }
100
+ should_redirect_with_error :unsupported_response_type
101
+ end
102
+
103
+ context "unknown response type" do
104
+ setup { request_authorization :response_type=>"foobar" }
105
+ should_redirect_with_error :unsupported_response_type
106
+ end
107
+
108
+ context "unsupported scope" do
109
+ setup do
110
+ request_authorization :scope=>"read write math"
111
+ end
112
+ should_redirect_with_error :invalid_scope
113
+ end
114
+
115
+
116
+ # 3.1. Authorization Response
117
+
118
+ context "expecting authorization code" do
119
+ setup do
120
+ @params[:response_type] = "code"
121
+ request_authorization
122
+ end
123
+ should_ask_user_for_authorization
124
+
125
+ context "and granted" do
126
+ setup { post "/oauth/grant", :authorization=>authorization }
127
+
128
+ should "redirect" do
129
+ assert_equal 302, last_response.status
130
+ end
131
+ should "redirect back to client" do
132
+ uri = URI.parse(last_response["Location"])
133
+ assert_equal "uberclient.dot", uri.host
134
+ assert_equal "/callback", uri.path
135
+ end
136
+
137
+ context "redirect URL query parameters" do
138
+ setup { @return = Rack::Utils.parse_query(URI.parse(last_response["Location"]).query) }
139
+
140
+ should "include authorization code" do
141
+ assert_match /[a-f0-9]{32}/i, @return["code"]
142
+ end
143
+
144
+ should "include original scope" do
145
+ assert_equal "read write", @return["scope"]
146
+ end
147
+
148
+ should "include state from requet" do
149
+ assert_equal "bring this back", @return["state"]
150
+ end
151
+ end
152
+ end
153
+
154
+ context "and denied" do
155
+ setup { post "/oauth/deny", :authorization=>authorization }
156
+
157
+ should "redirect" do
158
+ assert_equal 302, last_response.status
159
+ end
160
+ should "redirect back to client" do
161
+ uri = URI.parse(last_response["Location"])
162
+ assert_equal "uberclient.dot", uri.host
163
+ assert_equal "/callback", uri.path
164
+ end
165
+
166
+ context "redirect URL" do
167
+ setup { @return = Rack::Utils.parse_query(URI.parse(last_response["Location"]).query) }
168
+
169
+ should "not include authorization code" do
170
+ assert !@return["code"]
171
+ end
172
+
173
+ should "include error code" do
174
+ assert_equal "access_denied", @return["error"]
175
+ end
176
+
177
+ should "include state from requet" do
178
+ assert_equal "bring this back", @return["state"]
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+
185
+ context "expecting access token" do
186
+ setup do
187
+ @params[:response_type] = "token"
188
+ request_authorization
189
+ end
190
+ should_ask_user_for_authorization
191
+
192
+ context "and granted" do
193
+ setup { post "/oauth/grant", :authorization=>authorization }
194
+
195
+ should "redirect" do
196
+ assert_equal 302, last_response.status
197
+ end
198
+ should "redirect back to client" do
199
+ uri = URI.parse(last_response["Location"])
200
+ assert_equal "uberclient.dot", uri.host
201
+ assert_equal "/callback", uri.path
202
+ end
203
+
204
+ context "redirect URL fragment identifier" do
205
+ setup { @return = Rack::Utils.parse_query(URI.parse(last_response["Location"]).fragment) }
206
+
207
+ should "include access token" do
208
+ assert_match /[a-f0-9]{32}/i, @return["access_token"]
209
+ end
210
+
211
+ should "include original scope" do
212
+ assert_equal "read write", @return["scope"]
213
+ end
214
+
215
+ should "include state from requet" do
216
+ assert_equal "bring this back", @return["state"]
217
+ end
218
+ end
219
+ end
220
+
221
+ context "and denied" do
222
+ setup { post "/oauth/deny", :authorization=>authorization }
223
+
224
+ should "redirect" do
225
+ assert_equal 302, last_response.status
226
+ end
227
+ should "redirect back to client" do
228
+ uri = URI.parse(last_response["Location"])
229
+ assert_equal "uberclient.dot", uri.host
230
+ assert_equal "/callback", uri.path
231
+ end
232
+
233
+ context "redirect URL" do
234
+ setup { @return = Rack::Utils.parse_query(URI.parse(last_response["Location"]).fragment) }
235
+
236
+ should "not include authorization code" do
237
+ assert !@return["code"]
238
+ end
239
+
240
+ should "include error code" do
241
+ assert_equal "access_denied", @return["error"]
242
+ end
243
+
244
+ should "include state from requet" do
245
+ assert_equal "bring this back", @return["state"]
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+
252
+ # Using existing authorization request
253
+
254
+ context "with authorization request" do
255
+ setup do
256
+ request_authorization
257
+ get "/oauth/authorize?" + Rack::Utils.build_query(:authorization=>authorization)
258
+ end
259
+
260
+ should_ask_user_for_authorization
261
+ end
262
+
263
+ context "with invalid authorization request" do
264
+ setup do
265
+ request_authorization
266
+ get "/oauth/authorize?" + Rack::Utils.build_query(:authorization=>"foobar")
267
+ end
268
+
269
+ should "return status 400" do
270
+ assert_equal 400, last_response.status
271
+ end
272
+ end
273
+
274
+ context "with revoked authorization request" do
275
+ setup do
276
+ request_authorization
277
+ response = last_response.body.split("\n").inject({}) { |h,l| n,v = l.split(/:\s*/) ; h[n.downcase] = v ; h }
278
+ client.revoke!
279
+ get "/oauth/authorize?" + Rack::Utils.build_query(:authorization=>response["authorization"])
280
+ end
281
+
282
+ should "return status 400" do
283
+ assert_equal 400, last_response.status
284
+ end
285
+ end
286
+
287
+
288
+ # Edge cases
289
+
290
+ context "unregistered redirect URI" do
291
+ setup do
292
+ Rack::OAuth2::Server::Client.collection.update({ :_id=>client._id }, { :$set=>{ :redirect_uri=>nil } })
293
+ request_authorization :redirect_uri=>"http://uberclient.dot/oz"
294
+ end
295
+ should_ask_user_for_authorization
296
+ end
297
+
298
+ end
@@ -0,0 +1,292 @@
1
+ require "test/setup"
2
+
3
+
4
+ # Tests the Server API
5
+ class ServerTest < Test::Unit::TestCase
6
+ def setup
7
+ super
8
+ end
9
+
10
+ context "get_auth_request" do
11
+ setup { @request = Server::AuthRequest.create(client, client.scope.join(" "), client.redirect_uri, "token", nil) }
12
+ should "return authorization request" do
13
+ assert_equal @request.id, Server.get_auth_request(@request.id).id
14
+ end
15
+
16
+ should "return nil if no request found" do
17
+ assert !Server.get_auth_request("4ce2488e3321e87ac1000004")
18
+ end
19
+ end
20
+
21
+
22
+ context "get_client" do
23
+ should "return authorization request" do
24
+ assert_equal client.display_name, Server.get_client(client.id).display_name
25
+ end
26
+
27
+ should "return nil if no client found" do
28
+ assert !Server.get_client("4ce2488e3321e87ac1000004")
29
+ end
30
+ end
31
+
32
+
33
+ context "register" do
34
+ context "no client ID" do
35
+ setup do
36
+ @client = Server.register(:display_name=>"MyApp", :link=>"http://example.org", :image_url=>"http://example.org/favicon.ico",
37
+ :redirect_uri=>"http://example.org/oauth/callback", :scope=>%w{read write})
38
+ end
39
+
40
+ should "create new client" do
41
+ assert_equal 2, Server::Client.collection.count
42
+ assert_contains Server::Client.all.map(&:id), @client.id
43
+ end
44
+
45
+ should "set display name" do
46
+ assert_equal "MyApp", Server.get_client(@client.id).display_name
47
+ end
48
+
49
+ should "set link" do
50
+ assert_equal "http://example.org", Server.get_client(@client.id).link
51
+ end
52
+
53
+ should "set image URL" do
54
+ assert_equal "http://example.org/favicon.ico", Server.get_client(@client.id).image_url
55
+ end
56
+
57
+ should "set redirect URI" do
58
+ assert_equal "http://example.org/oauth/callback", Server.get_client(@client.id).redirect_uri
59
+ end
60
+
61
+ should "set scope" do
62
+ assert_equal %w{read write}, Server.get_client(@client.id).scope
63
+ end
64
+
65
+ should "assign client an ID" do
66
+ assert_match /[0-9a-f]{24}/, @client.id.to_s
67
+ end
68
+
69
+ should "assign client a secret" do
70
+ assert_match /[0-9a-f]{64}/, @client.secret
71
+ end
72
+ end
73
+
74
+ context "with client ID" do
75
+
76
+ context "no such client" do
77
+ setup do
78
+ @client = Server.register(:id=>"4ce24c423321e88ac5000015", :secret=>"foobar", :display_name=>"MyApp")
79
+ end
80
+
81
+ should "create new client" do
82
+ assert_equal 2, Server::Client.collection.count
83
+ end
84
+
85
+ should "should assign it the client identifier" do
86
+ assert_equal "4ce24c423321e88ac5000015", @client.id.to_s
87
+ end
88
+
89
+ should "should assign it the client secret" do
90
+ assert_equal "foobar", @client.secret
91
+ end
92
+
93
+ should "should assign it the other properties" do
94
+ assert_equal "MyApp", @client.display_name
95
+ end
96
+ end
97
+
98
+ context "existing client" do
99
+ setup do
100
+ Server.register(:id=>"4ce24c423321e88ac5000015", :secret=>"foobar", :display_name=>"MyApp")
101
+ @client = Server.register(:id=>"4ce24c423321e88ac5000015", :secret=>"foobar", :display_name=>"Rock Star")
102
+ end
103
+
104
+ should "not create new client" do
105
+ assert_equal 2, Server::Client.collection.count
106
+ end
107
+
108
+ should "should not change the client identifier" do
109
+ assert_equal "4ce24c423321e88ac5000015", @client.id.to_s
110
+ end
111
+
112
+ should "should not change the client secret" do
113
+ assert_equal "foobar", @client.secret
114
+ end
115
+
116
+ should "should change all the other properties" do
117
+ assert_equal "Rock Star", @client.display_name
118
+ end
119
+ end
120
+
121
+ context "secret mismatch" do
122
+ setup do
123
+ Server.register(:id=>"4ce24c423321e88ac5000015", :secret=>"foobar", :display_name=>"MyApp")
124
+ end
125
+
126
+ should "raise error" do
127
+ assert_raises RuntimeError do
128
+ Server.register(:id=>"4ce24c423321e88ac5000015", :secret=>"wrong", :display_name=>"MyApp")
129
+ end
130
+ end
131
+ end
132
+
133
+ end
134
+ end
135
+
136
+
137
+ context "access_grant" do
138
+ setup do
139
+ code = Server.access_grant("Batman", client.id, %w{read})
140
+ basic_authorize client.id, client.secret
141
+ post "/oauth/access_token", :scope=>"read", :grant_type=>"authorization_code", :code=>code, :redirect_uri=>client.redirect_uri
142
+ @token = JSON.parse(last_response.body)["access_token"]
143
+ end
144
+
145
+ should "resolve into an access token" do
146
+ assert Server.get_access_token(@token)
147
+ end
148
+
149
+ should "resolve into access token with grant identity" do
150
+ assert_equal "Batman", Server.get_access_token(@token).identity
151
+ end
152
+
153
+ should "resolve into access token with grant scope" do
154
+ assert_equal %w{read}, Server.get_access_token(@token).scope
155
+ end
156
+
157
+ should "resolve into access token with grant client" do
158
+ assert_equal client.id, Server.get_access_token(@token).client_id
159
+ end
160
+
161
+ context "with no scope" do
162
+ setup { @code = Server.access_grant("Batman", client.id) }
163
+
164
+ should "pick client scope" do
165
+ assert_equal %w{oauth-admin read write}, Server::AccessGrant.from_code(@code).scope
166
+ end
167
+ end
168
+
169
+ context "no expiration" do
170
+ setup do
171
+ @code = Server.access_grant("Batman", client.id)
172
+ end
173
+
174
+ should "not expire in a minute" do
175
+ Timecop.travel 60 do
176
+ basic_authorize client.id, client.secret
177
+ post "/oauth/access_token", :scope=>"read", :grant_type=>"authorization_code", :code=>@code, :redirect_uri=>client.redirect_uri
178
+ assert_equal 200, last_response.status
179
+ end
180
+ end
181
+
182
+ should "expire after 5 minutes" do
183
+ Timecop.travel 300 do
184
+ basic_authorize client.id, client.secret
185
+ post "/oauth/access_token", :scope=>"read", :grant_type=>"authorization_code", :code=>@code, :redirect_uri=>client.redirect_uri
186
+ assert_equal 400, last_response.status
187
+ end
188
+ end
189
+ end
190
+
191
+ context "expiration set" do
192
+ setup do
193
+ @code = Server.access_grant("Batman", client.id, nil, 1800)
194
+ end
195
+
196
+ should "not expire prematurely" do
197
+ Timecop.travel 1750 do
198
+ basic_authorize client.id, client.secret
199
+ post "/oauth/access_token", :scope=>"read", :grant_type=>"authorization_code", :code=>@code, :redirect_uri=>client.redirect_uri
200
+ assert_equal 200, last_response.status
201
+ end
202
+ end
203
+
204
+ should "expire after specified seconds" do
205
+ Timecop.travel 1800 do
206
+ basic_authorize client.id, client.secret
207
+ post "/oauth/access_token", :scope=>"read", :grant_type=>"authorization_code", :code=>@code, :redirect_uri=>client.redirect_uri
208
+ assert_equal 400, last_response.status
209
+ end
210
+ end
211
+ end
212
+
213
+ end
214
+
215
+
216
+ context "get_access_token" do
217
+ setup { @token = Server.token_for("Batman", client.id, %w{read}) }
218
+ should "return authorization request" do
219
+ assert_equal @token, Server.get_access_token(@token).token
220
+ end
221
+
222
+ should "return nil if no client found" do
223
+ assert !Server.get_access_token("4ce2488e3321e87ac1000004")
224
+ end
225
+
226
+ context "with no scope" do
227
+ setup { @token = Server.token_for("Batman", client.id) }
228
+
229
+ should "pick client scope" do
230
+ assert_equal %w{oauth-admin read write}, Server::AccessToken.from_token(@token).scope
231
+ end
232
+ end
233
+ end
234
+
235
+
236
+ context "token_for" do
237
+ setup { @token = Server.token_for("Batman", client.id, %w{read write}) }
238
+
239
+ should "return access token" do
240
+ assert_match /[0-9a-f]{32}/, @token
241
+ end
242
+
243
+ should "associate token with client" do
244
+ assert_equal client.id, Server.get_access_token(@token).client_id
245
+ end
246
+
247
+ should "associate token with identity" do
248
+ assert_equal "Batman", Server.get_access_token(@token).identity
249
+ end
250
+
251
+ should "associate token with scope" do
252
+ assert_equal %w{read write}, Server.get_access_token(@token).scope
253
+ end
254
+
255
+ should "return same token for same parameters" do
256
+ assert_equal @token, Server.token_for("Batman", client.id, %w{write read})
257
+ end
258
+
259
+ should "return different token for different identity" do
260
+ assert @token != Server.token_for("Superman", client.id, %w{read write})
261
+ end
262
+
263
+ should "return different token for different client" do
264
+ client = Server.register(:display_name=>"MyApp")
265
+ assert @token != Server.token_for("Batman", client.id, %w{read write})
266
+ end
267
+
268
+ should "return different token for different scope" do
269
+ assert @token != Server.token_for("Batman", client.id, %w{read})
270
+ end
271
+ end
272
+
273
+
274
+ context "list access tokens" do
275
+ setup do
276
+ @one = Server.token_for("Batman", client.id, %w{read})
277
+ @two = Server.token_for("Superman", client.id, %w{read})
278
+ @three = Server.token_for("Batman", client.id, %w{write})
279
+ end
280
+
281
+ should "return all tokens for identity" do
282
+ assert_contains Server.list_access_tokens("Batman").map(&:token), @one
283
+ assert_contains Server.list_access_tokens("Batman").map(&:token), @three
284
+ end
285
+
286
+ should "not return tokens for other identities" do
287
+ assert !Server.list_access_tokens("Batman").map(&:token).include?(@two)
288
+ end
289
+
290
+ end
291
+
292
+ end