Commit | Line | Data |
---|---|---|
8e1e744d | 1 | # GNU MediaGoblin -- federated, autonomous media hosting |
12a100e4 | 2 | # Copyright (C) 2011 MediaGoblin contributors. See AUTHORS. |
4b5f4e87 CAW |
3 | # |
4 | # This program is free software: you can redistribute it and/or modify | |
5 | # it under the terms of the GNU Affero General Public License as published by | |
6 | # the Free Software Foundation, either version 3 of the License, or | |
7 | # (at your option) any later version. | |
8 | # | |
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU Affero General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU Affero General Public License | |
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
1972a888 | 17 | import urlparse |
65a83047 | 18 | import datetime |
4b5f4e87 | 19 | |
1972a888 | 20 | from nose.tools import assert_equal |
4b5f4e87 | 21 | |
1972a888 | 22 | from mediagoblin.auth import lib as auth_lib |
3aa4c668 | 23 | from mediagoblin.tests.tools import setup_fresh_app |
6e7ce8d1 | 24 | from mediagoblin import mg_globals |
152a3bfa | 25 | from mediagoblin.tools import template, mail |
460ce564 | 26 | |
4b5f4e87 CAW |
27 | |
28 | ######################## | |
29 | # Test bcrypt auth funcs | |
30 | ######################## | |
31 | ||
32 | def test_bcrypt_check_password(): | |
33 | # Check known 'lollerskates' password against check function | |
34 | assert auth_lib.bcrypt_check_password( | |
35 | 'lollerskates', | |
36 | '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO') | |
37 | ||
db780024 CAW |
38 | assert not auth_lib.bcrypt_check_password( |
39 | 'notthepassword', | |
40 | '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO') | |
41 | ||
42 | ||
4b5f4e87 | 43 | # Same thing, but with extra fake salt. |
db780024 CAW |
44 | assert not auth_lib.bcrypt_check_password( |
45 | 'notthepassword', | |
4b5f4e87 CAW |
46 | '$2a$12$ELVlnw3z1FMu6CEGs/L8XO8vl0BuWSlUHgh0rUrry9DUXGMUNWwl6', |
47 | '3><7R45417') | |
48 | ||
49 | ||
50 | def test_bcrypt_gen_password_hash(): | |
51 | pw = 'youwillneverguessthis' | |
52 | ||
53 | # Normal password hash generation, and check on that hash | |
54 | hashed_pw = auth_lib.bcrypt_gen_password_hash(pw) | |
55 | assert auth_lib.bcrypt_check_password( | |
56 | pw, hashed_pw) | |
db780024 CAW |
57 | assert not auth_lib.bcrypt_check_password( |
58 | 'notthepassword', hashed_pw) | |
59 | ||
4b5f4e87 CAW |
60 | |
61 | # Same thing, extra salt. | |
62 | hashed_pw = auth_lib.bcrypt_gen_password_hash(pw, '3><7R45417') | |
63 | assert auth_lib.bcrypt_check_password( | |
64 | pw, hashed_pw, '3><7R45417') | |
db780024 CAW |
65 | assert not auth_lib.bcrypt_check_password( |
66 | 'notthepassword', hashed_pw, '3><7R45417') | |
460ce564 CAW |
67 | |
68 | ||
3aa4c668 CAW |
69 | @setup_fresh_app |
70 | def test_register_views(test_app): | |
2fecc29d CAW |
71 | """ |
72 | Massive test function that all our registration-related views all work. | |
73 | """ | |
460ce564 | 74 | # Test doing a simple GET on the page |
651403f0 CAW |
75 | # ----------------------------------- |
76 | ||
460ce564 CAW |
77 | test_app.get('/auth/register/') |
78 | # Make sure it rendered with the appropriate template | |
ae3bc7fa | 79 | assert template.TEMPLATE_TEST_CONTEXT.has_key( |
460ce564 | 80 | 'mediagoblin/auth/register.html') |
757690cc | 81 | |
460ce564 | 82 | # Try to register without providing anything, should error |
651403f0 CAW |
83 | # -------------------------------------------------------- |
84 | ||
ae3bc7fa | 85 | template.clear_test_template_context() |
460ce564 CAW |
86 | test_app.post( |
87 | '/auth/register/', {}) | |
ae3bc7fa | 88 | context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html'] |
460ce564 CAW |
89 | form = context['register_form'] |
90 | assert form.username.errors == [u'This field is required.'] | |
91 | assert form.password.errors == [u'This field is required.'] | |
92 | assert form.confirm_password.errors == [u'This field is required.'] | |
93 | assert form.email.errors == [u'This field is required.'] | |
651403f0 CAW |
94 | |
95 | # Try to register with fields that are known to be invalid | |
96 | # -------------------------------------------------------- | |
97 | ||
98 | ## too short | |
ae3bc7fa | 99 | template.clear_test_template_context() |
651403f0 CAW |
100 | test_app.post( |
101 | '/auth/register/', { | |
102 | 'username': 'l', | |
103 | 'password': 'o', | |
104 | 'confirm_password': 'o', | |
105 | 'email': 'l'}) | |
ae3bc7fa | 106 | context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html'] |
651403f0 CAW |
107 | form = context['register_form'] |
108 | ||
109 | assert form.username.errors == [ | |
110 | u'Field must be between 3 and 30 characters long.'] | |
111 | assert form.password.errors == [ | |
112 | u'Field must be between 6 and 30 characters long.'] | |
113 | ||
114 | ## bad form | |
ae3bc7fa | 115 | template.clear_test_template_context() |
651403f0 CAW |
116 | test_app.post( |
117 | '/auth/register/', { | |
118 | 'username': '@_@', | |
119 | 'email': 'lollerskates'}) | |
ae3bc7fa | 120 | context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html'] |
651403f0 CAW |
121 | form = context['register_form'] |
122 | ||
123 | assert form.username.errors == [ | |
124 | u'Invalid input.'] | |
125 | assert form.email.errors == [ | |
126 | u'Invalid email address.'] | |
127 | ||
128 | ## mismatching passwords | |
ae3bc7fa | 129 | template.clear_test_template_context() |
651403f0 CAW |
130 | test_app.post( |
131 | '/auth/register/', { | |
132 | 'password': 'herpderp', | |
133 | 'confirm_password': 'derpherp'}) | |
ae3bc7fa | 134 | context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html'] |
651403f0 CAW |
135 | form = context['register_form'] |
136 | ||
137 | assert form.password.errors == [ | |
138 | u'Passwords must match.'] | |
139 | ||
140 | ## At this point there should be no users in the database ;) | |
6e7ce8d1 | 141 | assert not mg_globals.database.User.find().count() |
651403f0 CAW |
142 | |
143 | # Successful register | |
144 | # ------------------- | |
ae3bc7fa | 145 | template.clear_test_template_context() |
1972a888 CAW |
146 | response = test_app.post( |
147 | '/auth/register/', { | |
148 | 'username': 'happygirl', | |
149 | 'password': 'iamsohappy', | |
150 | 'confirm_password': 'iamsohappy', | |
151 | 'email': 'happygrrl@example.org'}) | |
152 | response.follow() | |
153 | ||
651403f0 | 154 | ## Did we redirect to the proper page? Use the right template? |
1972a888 CAW |
155 | assert_equal( |
156 | urlparse.urlsplit(response.location)[2], | |
0bc03620 | 157 | '/u/happygirl/') |
ae3bc7fa | 158 | assert template.TEMPLATE_TEST_CONTEXT.has_key( |
0bc03620 | 159 | 'mediagoblin/user_pages/user.html') |
1972a888 | 160 | |
651403f0 | 161 | ## Make sure user is in place |
6e7ce8d1 | 162 | new_user = mg_globals.database.User.find_one( |
1972a888 CAW |
163 | {'username': 'happygirl'}) |
164 | assert new_user | |
165 | assert new_user['status'] == u'needs_email_verification' | |
166 | assert new_user['email_verified'] == False | |
167 | ||
f73f4c4b | 168 | ## Make sure user is logged in |
ae3bc7fa | 169 | request = template.TEMPLATE_TEST_CONTEXT[ |
f73f4c4b CAW |
170 | 'mediagoblin/user_pages/user.html']['request'] |
171 | assert request.session['user_id'] == unicode(new_user['_id']) | |
172 | ||
1972a888 | 173 | ## Make sure we get email confirmation, and try verifying |
152a3bfa AW |
174 | assert len(mail.EMAIL_TEST_INBOX) == 1 |
175 | message = mail.EMAIL_TEST_INBOX.pop() | |
1972a888 | 176 | assert message['To'] == 'happygrrl@example.org' |
ae3bc7fa | 177 | email_context = template.TEMPLATE_TEST_CONTEXT[ |
1972a888 CAW |
178 | 'mediagoblin/auth/verification_email.txt'] |
179 | assert email_context['verification_url'] in message.get_payload(decode=True) | |
180 | ||
181 | path = urlparse.urlsplit(email_context['verification_url'])[2] | |
182 | get_params = urlparse.urlsplit(email_context['verification_url'])[3] | |
183 | assert path == u'/auth/verify_email/' | |
184 | parsed_get_params = urlparse.parse_qs(get_params) | |
185 | ||
186 | ### user should have these same parameters | |
187 | assert parsed_get_params['userid'] == [ | |
188 | unicode(new_user['_id'])] | |
189 | assert parsed_get_params['token'] == [ | |
190 | new_user['verification_key']] | |
757690cc | 191 | |
7b1e17ed | 192 | ## Try verifying with bs verification key, shouldn't work |
ae3bc7fa | 193 | template.clear_test_template_context() |
a656ccd5 | 194 | response = test_app.get( |
7b1e17ed CAW |
195 | "/auth/verify_email/?userid=%s&token=total_bs" % unicode( |
196 | new_user['_id'])) | |
a656ccd5 | 197 | response.follow() |
ae3bc7fa | 198 | context = template.TEMPLATE_TEST_CONTEXT[ |
e054ae9b | 199 | 'mediagoblin/user_pages/user.html'] |
a656ccd5 CAW |
200 | # assert context['verification_successful'] == True |
201 | # TODO: Would be good to test messages here when we can do so... | |
6e7ce8d1 | 202 | new_user = mg_globals.database.User.find_one( |
7b1e17ed CAW |
203 | {'username': 'happygirl'}) |
204 | assert new_user | |
205 | assert new_user['status'] == u'needs_email_verification' | |
206 | assert new_user['email_verified'] == False | |
207 | ||
208 | ## Verify the email activation works | |
ae3bc7fa | 209 | template.clear_test_template_context() |
a656ccd5 CAW |
210 | response = test_app.get("%s?%s" % (path, get_params)) |
211 | response.follow() | |
ae3bc7fa | 212 | context = template.TEMPLATE_TEST_CONTEXT[ |
e054ae9b | 213 | 'mediagoblin/user_pages/user.html'] |
a656ccd5 CAW |
214 | # assert context['verification_successful'] == True |
215 | # TODO: Would be good to test messages here when we can do so... | |
6e7ce8d1 | 216 | new_user = mg_globals.database.User.find_one( |
7b1e17ed CAW |
217 | {'username': 'happygirl'}) |
218 | assert new_user | |
219 | assert new_user['status'] == u'active' | |
220 | assert new_user['email_verified'] == True | |
1972a888 | 221 | |
cb9bac0c CAW |
222 | # Uniqueness checks |
223 | # ----------------- | |
224 | ## We shouldn't be able to register with that user twice | |
ae3bc7fa | 225 | template.clear_test_template_context() |
8a869db8 CAW |
226 | response = test_app.post( |
227 | '/auth/register/', { | |
228 | 'username': 'happygirl', | |
229 | 'password': 'iamsohappy2', | |
230 | 'confirm_password': 'iamsohappy2', | |
231 | 'email': 'happygrrl2@example.org'}) | |
757690cc | 232 | |
ae3bc7fa | 233 | context = template.TEMPLATE_TEST_CONTEXT[ |
8a869db8 CAW |
234 | 'mediagoblin/auth/register.html'] |
235 | form = context['register_form'] | |
236 | assert form.username.errors == [ | |
237 | u'Sorry, a user with that name already exists.'] | |
651403f0 | 238 | |
8a869db8 | 239 | ## TODO: Also check for double instances of an email address? |
757690cc | 240 | |
65a83047 CFD |
241 | ### Oops, forgot the password |
242 | # ------------------- | |
ae3bc7fa | 243 | template.clear_test_template_context() |
f03fef4e CAW |
244 | response = test_app.post( |
245 | '/auth/forgot_password/', | |
246 | {'username': 'happygirl'}) | |
65a83047 CFD |
247 | response.follow() |
248 | ||
249 | ## Did we redirect to the proper page? Use the right template? | |
250 | assert_equal( | |
251 | urlparse.urlsplit(response.location)[2], | |
f03fef4e | 252 | '/auth/forgot_password/email_sent/') |
ae3bc7fa | 253 | assert template.TEMPLATE_TEST_CONTEXT.has_key( |
65a83047 CFD |
254 | 'mediagoblin/auth/fp_email_sent.html') |
255 | ||
256 | ## Make sure link to change password is sent by email | |
152a3bfa AW |
257 | assert len(mail.EMAIL_TEST_INBOX) == 1 |
258 | message = mail.EMAIL_TEST_INBOX.pop() | |
65a83047 | 259 | assert message['To'] == 'happygrrl@example.org' |
ae3bc7fa | 260 | email_context = template.TEMPLATE_TEST_CONTEXT[ |
65a83047 CFD |
261 | 'mediagoblin/auth/fp_verification_email.txt'] |
262 | #TODO - change the name of verification_url to something forgot-password-ish | |
263 | assert email_context['verification_url'] in message.get_payload(decode=True) | |
264 | ||
265 | path = urlparse.urlsplit(email_context['verification_url'])[2] | |
266 | get_params = urlparse.urlsplit(email_context['verification_url'])[3] | |
f03fef4e | 267 | assert path == u'/auth/forgot_password/verify/' |
65a83047 CFD |
268 | parsed_get_params = urlparse.parse_qs(get_params) |
269 | ||
270 | # user should have matching parameters | |
271 | new_user = mg_globals.database.User.find_one({'username': 'happygirl'}) | |
272 | assert parsed_get_params['userid'] == [unicode(new_user['_id'])] | |
273 | assert parsed_get_params['token'] == [new_user['fp_verification_key']] | |
274 | ||
275 | ### The forgotten password token should be set to expire in ~ 10 days | |
276 | # A few ticks have expired so there are only 9 full days left... | |
277 | assert (new_user['fp_token_expire'] - datetime.datetime.now()).days == 9 | |
278 | ||
279 | ## Try using a bs password-changing verification key, shouldn't work | |
ae3bc7fa | 280 | template.clear_test_template_context() |
65a83047 | 281 | response = test_app.get( |
f03fef4e | 282 | "/auth/forgot_password/verify/?userid=%s&token=total_bs" % unicode( |
65a83047 CFD |
283 | new_user['_id']), status=400) |
284 | assert response.status == '400 Bad Request' | |
285 | ||
4bcaf9f3 | 286 | ## Try using an expired token to change password, shouldn't work |
ae3bc7fa | 287 | template.clear_test_template_context() |
4bcaf9f3 CFD |
288 | real_token_expiration = new_user['fp_token_expire'] |
289 | new_user['fp_token_expire'] = datetime.datetime.now() | |
290 | new_user.save() | |
291 | response = test_app.get("%s?%s" % (path, get_params), status=400) | |
292 | assert response.status == '400 Bad Request' | |
293 | new_user['fp_token_expire'] = real_token_expiration | |
294 | new_user.save() | |
295 | ||
65a83047 | 296 | ## Verify step 1 of password-change works -- can see form to change password |
ae3bc7fa | 297 | template.clear_test_template_context() |
65a83047 | 298 | response = test_app.get("%s?%s" % (path, get_params)) |
ae3bc7fa | 299 | assert template.TEMPLATE_TEST_CONTEXT.has_key('mediagoblin/auth/change_fp.html') |
65a83047 CFD |
300 | |
301 | ## Verify step 2.1 of password-change works -- report success to user | |
ae3bc7fa | 302 | template.clear_test_template_context() |
65a83047 | 303 | response = test_app.post( |
f03fef4e | 304 | '/auth/forgot_password/verify/', { |
65a83047 CFD |
305 | 'userid': parsed_get_params['userid'], |
306 | 'password': 'iamveryveryhappy', | |
307 | 'confirm_password': 'iamveryveryhappy', | |
308 | 'token': parsed_get_params['token']}) | |
309 | response.follow() | |
ae3bc7fa | 310 | assert template.TEMPLATE_TEST_CONTEXT.has_key( |
f03fef4e | 311 | 'mediagoblin/auth/fp_changed_success.html') |
65a83047 CFD |
312 | |
313 | ## Verify step 2.2 of password-change works -- login w/ new password success | |
ae3bc7fa | 314 | template.clear_test_template_context() |
65a83047 CFD |
315 | response = test_app.post( |
316 | '/auth/login/', { | |
317 | 'username': u'happygirl', | |
318 | 'password': 'iamveryveryhappy'}) | |
319 | ||
320 | # User should be redirected | |
321 | response.follow() | |
322 | assert_equal( | |
323 | urlparse.urlsplit(response.location)[2], | |
324 | '/') | |
ae3bc7fa | 325 | assert template.TEMPLATE_TEST_CONTEXT.has_key( |
65a83047 CFD |
326 | 'mediagoblin/root.html') |
327 | ||
757690cc CM |
328 | |
329 | @setup_fresh_app | |
330 | def test_authentication_views(test_app): | |
331 | """ | |
332 | Test logging in and logging out | |
333 | """ | |
334 | # Make a new user | |
335 | test_user = mg_globals.database.User() | |
336 | test_user['username'] = u'chris' | |
337 | test_user['email'] = u'chris@example.com' | |
338 | test_user['pw_hash'] = auth_lib.bcrypt_gen_password_hash('toast') | |
339 | test_user.save() | |
340 | ||
341 | # Get login | |
0a4cecdc | 342 | # --------- |
757690cc | 343 | test_app.get('/auth/login/') |
ae3bc7fa | 344 | assert template.TEMPLATE_TEST_CONTEXT.has_key( |
757690cc CM |
345 | 'mediagoblin/auth/login.html') |
346 | ||
0a4cecdc CM |
347 | # Failed login - blank form |
348 | # ------------------------- | |
ae3bc7fa | 349 | template.clear_test_template_context() |
0a4cecdc | 350 | response = test_app.post('/auth/login/') |
ae3bc7fa | 351 | context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] |
0a4cecdc CM |
352 | form = context['login_form'] |
353 | assert form.username.errors == [u'This field is required.'] | |
354 | assert form.password.errors == [u'This field is required.'] | |
355 | ||
356 | # Failed login - blank user | |
357 | # ------------------------- | |
ae3bc7fa | 358 | template.clear_test_template_context() |
0a4cecdc CM |
359 | response = test_app.post( |
360 | '/auth/login/', { | |
361 | 'password': u'toast'}) | |
ae3bc7fa | 362 | context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] |
0a4cecdc CM |
363 | form = context['login_form'] |
364 | assert form.username.errors == [u'This field is required.'] | |
365 | ||
366 | # Failed login - blank password | |
367 | # ----------------------------- | |
ae3bc7fa | 368 | template.clear_test_template_context() |
0a4cecdc CM |
369 | response = test_app.post( |
370 | '/auth/login/', { | |
371 | 'username': u'chris'}) | |
ae3bc7fa | 372 | context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] |
0a4cecdc CM |
373 | form = context['login_form'] |
374 | assert form.password.errors == [u'This field is required.'] | |
375 | ||
376 | # Failed login - bad user | |
377 | # ----------------------- | |
ae3bc7fa | 378 | template.clear_test_template_context() |
0a4cecdc CM |
379 | response = test_app.post( |
380 | '/auth/login/', { | |
381 | 'username': u'steve', | |
382 | 'password': 'toast'}) | |
ae3bc7fa | 383 | context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] |
0a4cecdc CM |
384 | assert context['login_failed'] |
385 | ||
386 | # Failed login - bad password | |
387 | # --------------------------- | |
ae3bc7fa | 388 | template.clear_test_template_context() |
0a4cecdc CM |
389 | response = test_app.post( |
390 | '/auth/login/', { | |
391 | 'username': u'chris', | |
392 | 'password': 'jam'}) | |
ae3bc7fa | 393 | context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] |
0a4cecdc CM |
394 | assert context['login_failed'] |
395 | ||
396 | # Successful login | |
397 | # ---------------- | |
ae3bc7fa | 398 | template.clear_test_template_context() |
757690cc CM |
399 | response = test_app.post( |
400 | '/auth/login/', { | |
401 | 'username': u'chris', | |
402 | 'password': 'toast'}) | |
0a4cecdc CM |
403 | |
404 | # User should be redirected | |
757690cc CM |
405 | response.follow() |
406 | assert_equal( | |
407 | urlparse.urlsplit(response.location)[2], | |
408 | '/') | |
ae3bc7fa | 409 | assert template.TEMPLATE_TEST_CONTEXT.has_key( |
757690cc CM |
410 | 'mediagoblin/root.html') |
411 | ||
0a4cecdc | 412 | # Make sure user is in the session |
ae3bc7fa | 413 | context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html'] |
0a4cecdc | 414 | session = context['request'].session |
757690cc CM |
415 | assert session['user_id'] == unicode(test_user['_id']) |
416 | ||
0a4cecdc CM |
417 | # Successful logout |
418 | # ----------------- | |
ae3bc7fa | 419 | template.clear_test_template_context() |
0a4cecdc CM |
420 | response = test_app.get('/auth/logout/') |
421 | ||
422 | # Should be redirected to index page | |
423 | response.follow() | |
424 | assert_equal( | |
425 | urlparse.urlsplit(response.location)[2], | |
426 | '/') | |
ae3bc7fa | 427 | assert template.TEMPLATE_TEST_CONTEXT.has_key( |
0a4cecdc CM |
428 | 'mediagoblin/root.html') |
429 | ||
430 | # Make sure the user is not in the session | |
ae3bc7fa | 431 | context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html'] |
0a4cecdc CM |
432 | session = context['request'].session |
433 | assert session.has_key('user_id') == False | |
757690cc | 434 | |
12c231c8 CM |
435 | # User is redirected to custom URL if POST['next'] is set |
436 | # ------------------------------------------------------- | |
ae3bc7fa | 437 | template.clear_test_template_context() |
12c231c8 CM |
438 | response = test_app.post( |
439 | '/auth/login/', { | |
440 | 'username': u'chris', | |
441 | 'password': 'toast', | |
442 | 'next' : '/u/chris/'}) | |
443 | assert_equal( | |
444 | urlparse.urlsplit(response.location)[2], | |
445 | '/u/chris/') | |
446 |