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