tpitale-rack-oauth2-server 2.2.1

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 (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