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