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