pasaporte 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/History.txt +2 -0
  2. data/Manifest.txt +41 -0
  3. data/README.txt +72 -0
  4. data/Rakefile +111 -0
  5. data/TODO.txt +2 -0
  6. data/bin/pasaporte-fcgi.rb +17 -0
  7. data/lib/pasaporte/.DS_Store +0 -0
  8. data/lib/pasaporte/assets/.DS_Store +0 -0
  9. data/lib/pasaporte/assets/bgbar.png +0 -0
  10. data/lib/pasaporte/assets/lock.png +0 -0
  11. data/lib/pasaporte/assets/mainbg_green.gif +0 -0
  12. data/lib/pasaporte/assets/mainbg_red.gif +0 -0
  13. data/lib/pasaporte/assets/openid.png +0 -0
  14. data/lib/pasaporte/assets/pasaporte.css +192 -0
  15. data/lib/pasaporte/assets/pasaporte.js +10 -0
  16. data/lib/pasaporte/assets/user.png +0 -0
  17. data/lib/pasaporte/auth/cascade.rb +16 -0
  18. data/lib/pasaporte/auth/remote_web_workplace.rb +61 -0
  19. data/lib/pasaporte/auth/yaml_digest_table.rb +23 -0
  20. data/lib/pasaporte/auth/yaml_table.rb +43 -0
  21. data/lib/pasaporte/faster_openid.rb +39 -0
  22. data/lib/pasaporte/iso_countries.yml +247 -0
  23. data/lib/pasaporte/julik_state.rb +42 -0
  24. data/lib/pasaporte/markaby_ext.rb +8 -0
  25. data/lib/pasaporte/pasaporte_store.rb +60 -0
  26. data/lib/pasaporte/timezones.yml +797 -0
  27. data/lib/pasaporte.rb +1214 -0
  28. data/test/fixtures/pasaporte_approvals.yml +12 -0
  29. data/test/fixtures/pasaporte_profiles.yml +45 -0
  30. data/test/fixtures/pasaporte_throttles.yml +4 -0
  31. data/test/helper.rb +66 -0
  32. data/test/mosquito.rb +596 -0
  33. data/test/test_approval.rb +33 -0
  34. data/test/test_auth_backends.rb +59 -0
  35. data/test/test_openid.rb +363 -0
  36. data/test/test_pasaporte.rb +326 -0
  37. data/test/test_profile.rb +165 -0
  38. data/test/test_settings.rb +27 -0
  39. data/test/test_throttle.rb +70 -0
  40. data/test/testable_openid_fetcher.rb +82 -0
  41. metadata +151 -0
@@ -0,0 +1,363 @@
1
+ $:.reject! { |e| e.include? 'TextMate' }
2
+ require File.dirname(__FILE__) + '/helper'
3
+ require 'fileutils'
4
+ require File.dirname(__FILE__) + '/testable_openid_fetcher'
5
+ require 'openid/store/memory'
6
+ require 'openid/extensions/sreg'
7
+
8
+ class Object
9
+ def own_methods
10
+ raise (methods - Object.instance_methods).inspect
11
+ end
12
+ end
13
+
14
+ class TestOpenid < Pasaporte::WebTest
15
+ # We have to open up because it's the Fecther that's going
16
+ # to make requests
17
+ attr_reader :request, :response
18
+ fixtures :pasaporte_profiles
19
+
20
+ def setup
21
+ super
22
+ @fetcher = TestableOpenidFetcher.new(self)
23
+
24
+ OpenID.fetcher = @fetcher
25
+ OpenID::Util.logger = Pasaporte::LOGGER
26
+
27
+ @store = OpenID::Store::Memory.new
28
+ @openid_session = {}
29
+ init_consumer
30
+
31
+ @request.domain = 'test.host'
32
+
33
+ @trust_root = 'http://tativille.fr/wiki'
34
+ @return_to = 'http://tativille.fr/wiki/signup'
35
+ @hulot = Profile.find(1)
36
+ end
37
+
38
+ def teardown
39
+ # Delete all the associations created during the test case
40
+ JulikState::State.delete_all; Association.delete_all; Approval.delete_all; Throttle.delete_all
41
+ # Delete the store
42
+ FileUtils.rm_rf('openid-consumer-store')
43
+ # Call super for flexmock a.o.
44
+ super
45
+ end
46
+
47
+ def test_default_discovery_page_sports_right_server_url
48
+ get '/monsieur-hulot'
49
+ assert_response :success
50
+ assert_select 'link[rel=openid.server]', true do | s |
51
+ s = s.pop
52
+ assert_equal "http://test.host/pasaporte/monsieur-hulot/openid", s.attributes["href"],
53
+ "Should contain the delegate address for Monsieur Hulot"
54
+ end
55
+ assert_select 'link[rel=openid.delegate]', true do | s |
56
+ s = s.pop
57
+ assert_equal "http://test.host/pasaporte/monsieur-hulot/openid", s.attributes["href"],
58
+ "Should contain the endpoint address for Monsieur Hulot"
59
+ end
60
+ end
61
+
62
+ def test_in_which_tativille_begins_association_and_gets_a_proper_next_step_url
63
+ assert_equal 'test.host', @request['SERVER_NAME']
64
+
65
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
66
+ assert_kind_of OpenID::Consumer::CheckIDRequest, req
67
+ assert_kind_of OpenID::OpenIDServiceEndpoint, req.endpoint
68
+ assert_equal "http://test.host/pasaporte/monsieur-hulot/openid", req.endpoint.server_url
69
+ assert_equal "http://test.host/pasaporte/monsieur-hulot", req.endpoint.claimed_id
70
+
71
+ # In this test we only get a next step URL
72
+ next_step_url = req.redirect_url(@trust_root, @return_to, immediate=false)
73
+ assert_nothing_raised("The URL received from the server should be parseable") do
74
+ next_step_url = URI.parse(next_step_url)
75
+ end
76
+
77
+ assert_equal "http", next_step_url.scheme
78
+ assert_equal "test.host", next_step_url.host
79
+
80
+ response_params = decode_qs(next_step_url.query)
81
+
82
+ assert_match(/#{Regexp.escape(@return_to + '?openid1_claimed_id=')}(.+)#{Regexp.escape('&rp_nonce=')}(.+)/,
83
+ response_params["openid.return_to"],
84
+ "The return_to should be the one of the signup with a nonce")
85
+ assert_equal "http://test.host/pasaporte/monsieur-hulot/openid",
86
+ response_params["openid.identity"], "The identity is the server URL in this case"
87
+ assert_equal "checkid_setup", response_params['openid.mode'],
88
+ "The current mode is checkid_setup"
89
+ end
90
+
91
+ def test_in_which_monsieur_hulot_is_not_logged_in_and_asked_for_login_after_setup
92
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
93
+ assert_kind_of OpenID::Consumer::CheckIDRequest, req
94
+ assert_nothing_raised { get_with_verbatim_url req.redirect_url(@trust_root, @return_to) }
95
+ assert_response :redirect
96
+ assert_redirected_to '/monsieur-hulot/signon'
97
+ assert_not_nil @state.pending_openid, "A pending OpenID request should have been "+
98
+ "placed in the session"
99
+ assert_kind_of OpenID::Server::CheckIDRequest, @state.pending_openid
100
+ end
101
+
102
+ def test_in_which_monsieur_hulot_is_asked_for_setup_decisions_after_logging_in
103
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
104
+ assert_nothing_raised { get_with_verbatim_url req.redirect_url(@trust_root, @return_to) }
105
+ assert_response :redirect
106
+ assert_redirected_to '/monsieur-hulot/signon', "Monsieur should be asked to login"
107
+
108
+ prelogin!
109
+ assert_response :redirect
110
+ assert_redirected_to '/monsieur-hulot/openid',
111
+ "The redirection should be to the continuation of the OpenID procedure"
112
+
113
+ assert_nothing_raised { follow_redirect }
114
+ assert_response :redirect
115
+
116
+ assert_redirected_to '/monsieur-hulot/decide',
117
+ "Should send Monsieur Hulot to the page where he will confirm himself trusting the Tativille"
118
+ assert_not_nil @state.pending_openid,
119
+ "The state should contain the OpenID request to be processed"
120
+ end
121
+
122
+ def test_in_which_monsieur_hulot_blindly_trusts_tativille
123
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
124
+ assert_nothing_raised { get_with_verbatim_url req.redirect_url(@trust_root, @return_to) }
125
+ prelogin!
126
+
127
+ follow_redirect # back to openid
128
+ assert_redirected_to '/monsieur-hulot/decide', "Should send Monsieur Hulot to the page " +
129
+ "where he will confirm himself trusting the Tativille"
130
+ follow_redirect # to decide
131
+
132
+ assert_select 'h2' do | e |
133
+ assert_equal "<h2>Please approve <b>http://tativille.fr/wiki</b></h2>", e.to_s
134
+ end
135
+
136
+ post '/monsieur-hulot/decide'
137
+ assert_redirected_to '/monsieur-hulot/openid',
138
+ "Monsieur Hulot should be redirected back to the openid workflow page after approving"
139
+
140
+ @hulot.reload
141
+ approval = @hulot.approvals[0]
142
+ assert_kind_of Approval, approval, "An approval should have been made"
143
+ assert_equal @trust_root, approval.trust_root, "The approval approves tativille"
144
+ end
145
+
146
+ def test_in_which_monsieur_hulot_decides_not_to_trust_tativille
147
+ prelogin!
148
+
149
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
150
+ assert_nothing_raised { get_with_verbatim_url req.redirect_url(@trust_root, @return_to) }
151
+
152
+ assert_redirected_to '/monsieur-hulot/decide',
153
+ "Should send Monsieur Hulot to the page where he will confirm himself trusting the Tativille"
154
+ follow_redirect # to decide
155
+
156
+ assert_select 'h2' do | e |
157
+ assert_equal "<h2>Please approve <b>http://tativille.fr/wiki</b></h2>", e.to_s
158
+ end
159
+
160
+ post '/monsieur-hulot/decide', :nope => "Oh Non!"
161
+ red, path, qs = redirect_url_path_and_params
162
+
163
+ assert_equal '/wiki/signup', path,
164
+ "The taken decision should immediately send Monsieur Hulot back to the signup page"
165
+ assert_equal 0, Approval.count, "No Approvals should have been issued"
166
+ assert_kind_of OpenID::Consumer::CancelResponse, @consumer.complete(qs, red), "The response is negative"
167
+ end
168
+
169
+ def test_in_which_monsieur_hulot_already_approved_tativille_and_is_logged_in
170
+ prelogin!; preapprove!
171
+
172
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot", immediate = false)
173
+ assert_nothing_raised { get_with_verbatim_url req.redirect_url(@trust_root, @return_to) }
174
+
175
+ redir_url, path, qs = redirect_url_path_and_params
176
+
177
+ assert_kind_of OpenID::Consumer::SuccessResponse, @consumer.complete(qs, redir_url), "The response is positive"
178
+ assert_equal '/wiki/signup', path, "This should be the path to the wiki signup"
179
+ end
180
+
181
+ def test_in_which_monsieur_hulot_uses_immediate_mode_and_the_mode_totally_works
182
+ prelogin!; preapprove!
183
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
184
+ assert_nothing_raised do
185
+ get_with_verbatim_url req.redirect_url(@trust_root, @return_to, true)
186
+ end
187
+ red, path, qs = redirect_url_path_and_params
188
+
189
+ openid_resp = @consumer.complete(qs, red)
190
+ assert_kind_of OpenID::Consumer::SuccessResponse, openid_resp
191
+ end
192
+
193
+ def test_in_which_monsieur_hulot_uses_immediate_mode_but_needs_to_login_first
194
+ preapprove!
195
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
196
+ assert_nothing_raised do
197
+ get_with_verbatim_url req.redirect_url(@trust_root, @return_to, true)
198
+ end
199
+ red, path, qs = redirect_url_path_and_params
200
+ openid_resp = @consumer.complete(qs, red)
201
+
202
+ assert_kind_of OpenID::Consumer::SetupNeededResponse, openid_resp, "Setup is needed for this action"
203
+ assert_not_nil qs["openid.user_setup_url"], "The setup URL should be passed"
204
+
205
+ setup_path, setup_qs = redirect_path_and_params(qs["openid.user_setup_url"])
206
+ assert_equal "/pasaporte/monsieur-hulot/signon", setup_path, "The setup path is the signon"
207
+
208
+ post '/monsieur-hulot/signon', {:pass => 'monsieur-hulot'.reverse}.merge(setup_qs)
209
+ assert_response :redirect
210
+ assert_redirected_to "/monsieur-hulot/openid",
211
+ "Monsieur is now out of the immediate mode so we continue on to the openid process"
212
+ follow_redirect
213
+ red, path, qs = redirect_url_path_and_params
214
+
215
+ assert_kind_of OpenID::Consumer::SuccessResponse, @consumer.complete(qs, red),
216
+ "Monsieur has now authorized Tativille, albeit not immediately"
217
+ end
218
+
219
+ def test_in_which_monsieur_hulot_uses_immediate_mode_but_needs_to_approve_first
220
+ prelogin!
221
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
222
+ assert_nothing_raised do
223
+ get_with_verbatim_url req.redirect_url(@trust_root, @return_to, true)
224
+ end
225
+ redir, path, qs = redirect_url_path_and_params
226
+ openid_resp = @consumer.complete(qs, redir)
227
+
228
+ assert_kind_of OpenID::Consumer::SetupNeededResponse, openid_resp, "Setup is needed for this action"
229
+ assert_not_nil qs["openid.user_setup_url"], "The setup URL should be passed"
230
+
231
+ setup_url, setup_path, setup_qs = redirect_url_path_and_params(qs["openid.user_setup_url"])
232
+ assert_equal "/pasaporte/monsieur-hulot/decide", setup_path,
233
+ "The setup path is /decide because Hulot never logged in with Tativille before"
234
+
235
+ get '/monsieur-hulot/decide', setup_qs
236
+ assert_response :success
237
+ post '/monsieur-hulot/decide'
238
+ assert_response :redirect
239
+ assert_redirected_to '/monsieur-hulot/openid'
240
+ follow_redirect
241
+
242
+ redir, path, qs = redirect_url_path_and_params
243
+
244
+ assert_kind_of OpenID::Consumer::SuccessResponse, @consumer.complete(qs, redir),
245
+ "Monsieur has now authorized Tativille, albeit not immediately"
246
+ end
247
+
248
+ def test_in_which_monsieur_hulot_forgets_his_password_and_tativille_gets_a_refusal_after_the_last_failed_attempt
249
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
250
+ assert_nothing_raised { get_with_verbatim_url req.redirect_url(@trust_root, @return_to) }
251
+ assert_redirected_to '/monsieur-hulot/signon'
252
+ (Pasaporte::MAX_FAILED_LOGIN_ATTEMPTS).times do
253
+ post '/monsieur-hulot/signon', :pass => 'cartouche'
254
+ end
255
+
256
+ assert_response :redirect
257
+ redir, path, qs = redirect_url_path_and_params
258
+
259
+ assert_kind_of OpenID::Consumer::CancelResponse, @consumer.complete(qs, redir),
260
+ "Monsieur Hulot is denied authorization with Tativille after failing so miserably"
261
+ end
262
+
263
+ def test_in_which_monsieur_hulot_has_delegated
264
+ d = "http://leopenid.fr/endpoint"
265
+
266
+ @hulot.update_attributes :openid_delegate => d, :openid_server => d
267
+
268
+ err = assert_raise(OpenID::KVPostNetworkError, "Should go out looking for openid") do
269
+ @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
270
+ end
271
+ assert_match /Called out to external resource/, err.message, "The external resource exception should bubble up"
272
+ end
273
+
274
+ def test_in_which_monsieur_hulot_is_throttled_and_gets_rejected_at_once
275
+ Throttle.set!(@request.to_hash)
276
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
277
+ assert_nothing_raised { get_with_verbatim_url req.redirect_url(@trust_root, @return_to) }
278
+ assert_response :redirect
279
+ path, qs = redirect_path_and_params
280
+ assert_kind_of OpenID::Consumer::CancelResponse, @consumer.complete(qs, path),
281
+ "Monsieur Hulot is instantly denied authorization with Tativille because he is throttled"
282
+ assert_nil @state.pending_openid, "No OpenID request should be left dangling at this point"
283
+ end
284
+
285
+ def test_in_which_tativille_uses_dumb_mode
286
+ prelogin!; preapprove!
287
+ @consumer = OpenID::Consumer.new(@openid_session, nil)
288
+
289
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
290
+ assert_nothing_raised { get_with_verbatim_url req.redirect_url(@trust_root, @return_to) }
291
+ redir, path, qs = redirect_url_path_and_params
292
+ assert_equal '/wiki/signup', path, "This should be the path to the wiki signup"
293
+ assert_kind_of OpenID::Consumer::SuccessResponse, @consumer.complete(qs, redir), "The response is positive"
294
+ end
295
+
296
+ def test_in_which_tativille_requests_sreg
297
+ prelogin!; preapprove!
298
+
299
+ req = @consumer.begin("http://test.host/pasaporte/monsieur-hulot")
300
+ sreg_request = OpenID::SReg::Request.new
301
+ sreg_request.request_fields(['email', 'dob'], true) # required
302
+ sreg_request.request_fields(['nickname', 'fullname'], false) # optional
303
+ req.add_extension(sreg_request)
304
+
305
+ assert_nothing_raised { get_with_verbatim_url req.redirect_url(@trust_root, @return_to) }
306
+
307
+ red, path, qs = redirect_url_path_and_params
308
+ openid_resp = @consumer.complete(qs, red)
309
+
310
+ assert_kind_of OpenID::Consumer::SuccessResponse, openid_resp, "Should complete succesfully"
311
+
312
+ sreg_response = OpenID::SReg::Response.from_success_response(openid_resp)
313
+ assert_equal 'monsieur-hulot', sreg_response['nickname']
314
+ assert_equal "1953-01-12", sreg_response['dob']
315
+ assert_equal "Monsieur Hulot", sreg_response['fullname']
316
+ end
317
+
318
+ private
319
+ # Prelogins Monsieur Hulot into Pasaporte
320
+ def prelogin!
321
+ post '/monsieur-hulot/signon', :pass => 'monsieur-hulot'.reverse
322
+ end
323
+
324
+ # Preapproves tativille.fr as a site Monsieur Hulot trusts
325
+ def preapprove!
326
+ @hulot.approvals.create! :trust_root=>@trust_root
327
+ end
328
+
329
+ def init_consumer
330
+ @consumer = OpenID::Consumer.new(@openid_session, @store)
331
+ end
332
+ def decode_qs(qs)
333
+ qs.to_s.split(/\&/).inject({}) do | params, segment |
334
+ k, v = segment.split(/\=/).map{|s| Camping.un(s)}
335
+ params[k] = v; params
336
+ end
337
+ end
338
+ def response_to_hash
339
+ Hash[*@response.body.split(/\n/).map{|p| p.split(/\:/)}.flatten].with_indifferent_access
340
+ end
341
+ def redirect_path_and_params(t = nil)
342
+ # Camping conveniently places a URI object in the location header
343
+ uri = (t ? URI.parse(t) : @response.headers["Location"])
344
+ [uri.path, decode_qs(uri.query)]
345
+ end
346
+
347
+ def redirect_url_path_and_params(t = nil)
348
+ # Camping conveniently places a URI object in the location header
349
+ uri = (t ? URI.parse(t) : @response.headers["Location"])
350
+ uri_with_scheme, qry = uri.to_s.split(/\?/)
351
+
352
+ [uri_with_scheme, uri.path, decode_qs(qry)]
353
+ end
354
+
355
+ def get_with_verbatim_url(url)
356
+ get(*recompose(url))
357
+ end
358
+
359
+ def recompose(url)
360
+ u = URI.parse(url)
361
+ [u.path.gsub(/^\/pasaporte/, ''), decode_qs(u.query)]
362
+ end
363
+ end
@@ -0,0 +1,326 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+ require File.dirname(__FILE__) + '/testable_openid_fetcher'
3
+
4
+ require 'flexmock'
5
+
6
+ class TestProfilePage < Pasaporte::WebTest
7
+ fixtures :pasaporte_profiles
8
+
9
+ def test_user_info_page_for_user_who_never_logged_in
10
+ get '/unknown-stranger'
11
+ assert_response :success
12
+ assert_select "h3", true do | h |
13
+ assert_equal "<h3>This is <b>unknown-stranger's</b> page</h3>", h.to_s
14
+ end
15
+ end
16
+
17
+ def test_user_info_page_for_a_user_who_is_in_hiding
18
+ get '/julik'
19
+ assert_response :success
20
+ assert_select "h3", true do | h |
21
+ assert_equal "<h3>This is <b>julik's</b> page</h3>", h.to_s
22
+ end
23
+ end
24
+
25
+ def test_user_info_page_for_a_user_who_shows_his_profile
26
+ max = Profile.find(3)
27
+ assert max.shared?, "Max shares his profile"
28
+ get '/max'
29
+ assert_equal max.domain_name, @request.domain
30
+ assert_response :success
31
+ assert_not_nil @assigns.profile
32
+
33
+ assert_select "h2", true do | h |
34
+ assert_equal "<h2>Max Lapshin</h2>", h.to_s
35
+ end
36
+ assert_match_body /Promised to/
37
+ end
38
+
39
+ def test_user_info_depends_on_domain
40
+ get '/max'
41
+ assert_response :success
42
+ assert_not_nil @assigns.profile
43
+
44
+ @request.domain = "trashcan.dot"
45
+ get '/max'
46
+ assert_response :success
47
+ assert_nil @assigns.profile, "A profile page for someone who never logged in does not show any details"
48
+ end
49
+
50
+ def test_user_info_sets_yadis_headers
51
+ get '/julik'
52
+ assert_not_nil @response.headers['X-XRDS-Location']
53
+ assert_equal "http://test.host/pasaporte/julik/yadis", @response.headers['X-XRDS-Location']
54
+ end
55
+ end
56
+
57
+ class TestSignon < Pasaporte::WebTest
58
+ fixtures :pasaporte_profiles
59
+
60
+ def teardown
61
+ returning(super) {Throttle.delete_all }
62
+ end
63
+
64
+ def test_signon_displays_a_password_field
65
+ get '/julik/signon'
66
+ assert_response :success
67
+ assert_select "input[type=password]", true
68
+ end
69
+
70
+ def test_posting_to_signon_should_call_auth_and_fail
71
+ flexmock(Pasaporte::AUTH).
72
+ should_receive(:call).with("julik", "trance", "test.host").
73
+ at_least.once.and_return(false)
74
+
75
+ post '/julik/signon', :pass => "trance"
76
+ assert_response :success
77
+ assert_not_nil @assigns.msg, "Something should be put into msg"
78
+ assert_nil @state.nickname, "Nickname should not have been set in the session"
79
+ assert_not_nil @state.failed_logins, "The signon should start counting failed logins"
80
+ assert_equal 1, @state.failed_logins, "The signon should have counted 1 failed login"
81
+ end
82
+
83
+ def test_posting_false_login_many_times_winds_the_failed_login_counter
84
+ flexmock(Pasaporte::AUTH).
85
+ should_receive(:call).with("julik", "trance", "test.host").
86
+ at_least.twice.and_return(false)
87
+ post '/julik/signon', :pass => "trance"
88
+ post '/julik/signon', :pass => "trance"
89
+ assert_equal 2, @state.failed_logins, "The failed login counter should have been wound"
90
+ end
91
+
92
+ def test_past_the_failed_login_threshold_should_trottle
93
+ flexmock(Pasaporte::AUTH).
94
+ should_receive(:call).with("julik", "trance", "test.host").at_least.once.and_return(false)
95
+
96
+ Pasaporte::MAX_FAILED_LOGIN_ATTEMPTS.times { post '/julik/signon', :pass => "trance" }
97
+ assert_response :success
98
+ assert_match_body /I am stopping you/, "The throttling message should appear"
99
+ assert_equal 1, Throttle.count, "A throttle should have been made"
100
+ end
101
+
102
+ def test_post_with_good_auth_shold_fetch_profile_munge_session_and_redirect
103
+ flexmock(Pasaporte::AUTH).
104
+ should_receive(:call).with("julik", "junkman", "test.host").once.and_return(true)
105
+
106
+ post '/julik/signon', :pass => 'junkman'
107
+
108
+ assert_response :redirect
109
+ assert_redirected_to '/julik/prefs'
110
+
111
+ assert_not_nil @assigns.profile, "The profile should have been hooked up"
112
+ assert_equal 2, @assigns.profile.id, "This is Julik's profile"
113
+ assert_equal 'julik', @state.nickname, "The nickname should be set to 'julik' so that the flag is present"
114
+ end
115
+
116
+ def test_post_with_good_auth_should_create_profiles_if_necessary
117
+ flexmock(Pasaporte::AUTH).
118
+ should_receive(:call).with("gemanges", "tairn", "test.host").once.and_return(true)
119
+
120
+ post '/gemanges/signon', :pass => 'tairn'
121
+ assert_response :redirect
122
+
123
+ assert_not_nil @assigns.profile, "The profile should have been hooked up"
124
+ assert_equal 'gemanges', @assigns.profile.nickname
125
+ deny @assigns.profile.new_record?
126
+ end
127
+ end
128
+
129
+ class TestSignout < Pasaporte::WebTest
130
+ def test_signout_should_silently_redirect_unless_signed_in
131
+ get '/somebody/signout'
132
+ assert_response :redirect
133
+ assert_redirected_to '/somebody/signon'
134
+ end
135
+
136
+ def test_signout_should_erase_session_and_redirect
137
+ flexmock(Pasaporte::AUTH).
138
+ should_receive(:call).with("gemanges", "tairn", "test.host").once.and_return(true)
139
+ post '/gemanges/signon', :pass => 'tairn'
140
+ assert_response :redirect
141
+ assert_equal 'gemanges', @state.nickname
142
+
143
+ get '/gemanges/signout'
144
+ assert_response :redirect
145
+ assert_redirected_to '/gemanges/signon'
146
+ assert_kind_of Camping::H, @state, "State should be reset with Camping::H"
147
+ assert_equal %w( msg ), @state.keys, "State should only contain the message"
148
+ end
149
+ end
150
+
151
+ class TestApprovalsPage < Pasaporte::WebTest
152
+ fixtures :pasaporte_profiles, :pasaporte_approvals
153
+ def test_approvals_page_requires_login
154
+ get '/julik/approvals'
155
+ assert_response :redirect
156
+ end
157
+
158
+ def test_approvals_page_shows_useful_approvals_when_they_are_present
159
+ prelogin "julik"
160
+
161
+ get '/julik/approvals'
162
+ assert_response :success
163
+
164
+ assert_equal Profile.find_by_nickname('julik').approvals, @assigns.approvals
165
+ assert_match_body /The sites you trust/
166
+ end
167
+
168
+ def test_approvals_page_does_not_show_empty_lists
169
+ Profile.find_by_nickname('julik').approvals.destroy_all
170
+ prelogin 'julik'
171
+ get '/julik/approvals'
172
+ assert_redirected_to '/julik/prefs'
173
+ assert_not_nil @state.msg
174
+ end
175
+ end
176
+
177
+ class TestAssets < Pasaporte::WebTest
178
+ def test_get_nonexistent_url_sets_404
179
+ get '/assets/habduda'
180
+ assert_response 404
181
+ end
182
+
183
+ def test_get_with_dir_traversal
184
+ ref_path = File.dirname(Pasaporte::PATH) + '/pasaporte/assets/etc/passwd'
185
+
186
+ flexmock(File).should_receive(:exist?).with(ref_path).once.and_return(false)
187
+ get '/assets/../../../../../etc/passwd'
188
+ assert_response 404
189
+ end
190
+
191
+ def test_should_return_stylesheet
192
+ get '/assets/pasaporte.css'
193
+ assert_response :success
194
+ assert_equal 'text/css', @response.headers['Content-Type'], "Should set the content type to text/css"
195
+ assert_match_body /Superresetthemall/
196
+ end
197
+
198
+ def test_grabbing_an_asset_sets_magic_headers
199
+ get '/assets/pasaporte.css'
200
+ magic_headers = %w( Last-Modified Expires Cache-Control Last-Modified )
201
+ for h in magic_headers
202
+ assert_not_nil @response.headers[h], "The header #{h} should be set"
203
+ end
204
+ end
205
+
206
+ def test_should_return_304_on_conditional_get
207
+ @request.headers['HTTP_IF_MODIFIED_SINCE'] = Time.now.to_s(:http)
208
+ get '/assets/pasaporte.css'
209
+ assert_response 304
210
+ end
211
+ end
212
+
213
+ class TestProfileEditor < Pasaporte::WebTest
214
+ test 'should require login' do
215
+ get '/julik/edit'
216
+ assert_response :redirect
217
+ assert_redirected_to '/julik/signon'
218
+ end
219
+
220
+ test 'should preload profile' do
221
+ prelogin 'julik'
222
+ get '/julik/edit'
223
+ assert_response :success
224
+ assert_kind_of Profile, @assigns.profile
225
+ deny @assigns.profile.new_record?, "This is a presaved profile"
226
+ assert_match_body /Your profile/
227
+ end
228
+
229
+ test 'should show message and not save when posting faulty data' do
230
+ prelogin 'julik'
231
+ post '/julik/edit', :profile => {:openid_server => 'xyz'}
232
+
233
+ assert_response :success
234
+ assert_not_nil @assigns.err, "An error message should be assigned"
235
+ assert_match /Cannot save/, @assigns.err, "The error message should describe the problem"
236
+
237
+ prof = @assigns.profile
238
+ prof.reload
239
+ assert_nil prof.openid_server, "Nothing should have been assigned"
240
+ end
241
+
242
+ test 'should redirect with success message when changing the profile succeeds' do
243
+ prelogin 'julik'
244
+ post '/julik/edit', :profile => {:email => 'trof@groff.com'}
245
+ assert_response :redirect
246
+ assert_not_nil @state.msg
247
+ assert_match /Changed/i, @state.msg
248
+ end
249
+
250
+ test 'default country should be selected when loading the profile page for the first time' do
251
+ prelogin 'joe-boss'
252
+
253
+ begin
254
+ c = Pasaporte::DEFAULT_COUNTRY
255
+ silence_warnings { Pasaporte.const_set(:DEFAULT_COUNTRY, 'us')}
256
+ get '/joe-boss/edit'
257
+ assert_response :success
258
+ assert_select 'option[value="us"][selected]', true
259
+ ensure
260
+ silence_warnings { Pasaporte.const_set(:DEFAULT_COUNTRY, c) }
261
+ end
262
+ end
263
+
264
+ end
265
+
266
+ # class TestYadis < Pasaporte::WebTest
267
+ # attr_reader :request, :response
268
+ # fixtures :pasaporte_profiles
269
+ #
270
+ # def setup
271
+ # super; NetHTTPFetcher.requestor = self
272
+ # end
273
+ #
274
+ # test 'should return YADIS info with proper URLs' do
275
+ # get '/julik/yadis'
276
+ # assert_response :success
277
+ # assert_equal "application/xrds+xml", @response.headers["Content-type"]
278
+ # assert !@response.body.empty?, "Body cannot be empty"
279
+ # end
280
+ #
281
+ # test 'should be usable for yadis discovery' do
282
+ # assert_nothing_raised { @discovery = YADIS.new('http://test.host/julik/yadis') }
283
+ # assert_kind_of ServiceEndpoint, @discovery.services[0]
284
+ # assert_equal "http://test.host/pasaporte/julik/openid", @discovery.services[0].uri
285
+ # end
286
+ #
287
+ # test 'should redirect yadis discovery to the delegate' do
288
+ # assert_nothing_raised { @discovery = YADIS.new('http://test.host/hans/yadis') }
289
+ # assert_kind_of ServiceEndpoint, @discovery.services[0]
290
+ # assert_equal "http://hans.myopenid.com", @discovery.services[0].uri
291
+ # end
292
+ # end
293
+
294
+ class TestPublicSignon < Pasaporte::WebTest
295
+ fixtures :pasaporte_profiles
296
+ test 'should present a login screen without predefined nickname' do
297
+ get
298
+ assert_response :success
299
+ assert_select 'input[name="login"]', true
300
+
301
+ flexmock(Pasaporte::AUTH).should_receive(:call).with("schtwo", "foo", "test.host").and_return(false)
302
+
303
+ post '/', :login => 'schtwo', :pass => 'foo'
304
+ assert_response :success
305
+ assert_nil @state.nickname
306
+ assert_select 'input[type="hidden"]', true
307
+ "The response should include a hidden field now instead of a text field"
308
+ end
309
+
310
+ test 'should redirect to profile page for new users' do
311
+ flexmock(Pasaporte::AUTH).should_receive(:call).with("unknown", "pfew", "test.host").and_return(true)
312
+ post '/', :login => "unknown", :pass => "pfew"
313
+ assert_response :redirect
314
+ assert_not_nil Profile.find_by_nickname('unknown'), "A profile should have been created during login"
315
+ assert_redirected_to '/unknown/prefs'
316
+ end
317
+
318
+ end
319
+
320
+ # A littol auditte
321
+ at_exit do
322
+ missing = Pasaporte::Controllers.constants.reject do |c|
323
+ Object.constants.map{|e|e.to_s}.include?("Test#{c}")
324
+ end
325
+ puts "\nMissing tests for controllers #{missing.to_sentence}"
326
+ end