pasaporte 0.0.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 (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