pasaporte 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +2 -0
- data/Manifest.txt +41 -0
- data/README.txt +72 -0
- data/Rakefile +111 -0
- data/TODO.txt +2 -0
- data/bin/pasaporte-fcgi.rb +17 -0
- data/lib/pasaporte/.DS_Store +0 -0
- data/lib/pasaporte/assets/.DS_Store +0 -0
- data/lib/pasaporte/assets/bgbar.png +0 -0
- data/lib/pasaporte/assets/lock.png +0 -0
- data/lib/pasaporte/assets/mainbg_green.gif +0 -0
- data/lib/pasaporte/assets/mainbg_red.gif +0 -0
- data/lib/pasaporte/assets/openid.png +0 -0
- data/lib/pasaporte/assets/pasaporte.css +192 -0
- data/lib/pasaporte/assets/pasaporte.js +10 -0
- data/lib/pasaporte/assets/user.png +0 -0
- data/lib/pasaporte/auth/cascade.rb +16 -0
- data/lib/pasaporte/auth/remote_web_workplace.rb +61 -0
- data/lib/pasaporte/auth/yaml_digest_table.rb +23 -0
- data/lib/pasaporte/auth/yaml_table.rb +43 -0
- data/lib/pasaporte/faster_openid.rb +39 -0
- data/lib/pasaporte/iso_countries.yml +247 -0
- data/lib/pasaporte/julik_state.rb +42 -0
- data/lib/pasaporte/markaby_ext.rb +8 -0
- data/lib/pasaporte/pasaporte_store.rb +60 -0
- data/lib/pasaporte/timezones.yml +797 -0
- data/lib/pasaporte.rb +1214 -0
- data/test/fixtures/pasaporte_approvals.yml +12 -0
- data/test/fixtures/pasaporte_profiles.yml +45 -0
- data/test/fixtures/pasaporte_throttles.yml +4 -0
- data/test/helper.rb +66 -0
- data/test/mosquito.rb +596 -0
- data/test/test_approval.rb +33 -0
- data/test/test_auth_backends.rb +59 -0
- data/test/test_openid.rb +363 -0
- data/test/test_pasaporte.rb +326 -0
- data/test/test_profile.rb +165 -0
- data/test/test_settings.rb +27 -0
- data/test/test_throttle.rb +70 -0
- data/test/testable_openid_fetcher.rb +82 -0
- metadata +151 -0
data/test/test_openid.rb
ADDED
@@ -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
|