1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
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.
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.
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/>.
20 from nose
.tools
import assert_equal
22 from mediagoblin
import mg_globals
23 from mediagoblin
.auth
import lib
as auth_lib
24 from mediagoblin
.db
.models
import User
25 from mediagoblin
.tests
.tools
import setup_fresh_app
, get_app
, fixture_add_user
26 from mediagoblin
.tools
import template
, mail
29 ########################
30 # Test bcrypt auth funcs
31 ########################
33 def test_bcrypt_check_password():
34 # Check known 'lollerskates' password against check function
35 assert auth_lib
.bcrypt_check_password(
37 '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
39 assert not auth_lib
.bcrypt_check_password(
41 '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
44 # Same thing, but with extra fake salt.
45 assert not auth_lib
.bcrypt_check_password(
47 '$2a$12$ELVlnw3z1FMu6CEGs/L8XO8vl0BuWSlUHgh0rUrry9DUXGMUNWwl6',
51 def test_bcrypt_gen_password_hash():
52 pw
= 'youwillneverguessthis'
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(
58 assert not auth_lib
.bcrypt_check_password(
59 'notthepassword', hashed_pw
)
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')
66 assert not auth_lib
.bcrypt_check_password(
67 'notthepassword', hashed_pw
, '3><7R45417')
71 def test_register_views(test_app
):
73 Massive test function that all our registration-related views all work.
75 # Test doing a simple GET on the page
76 # -----------------------------------
78 test_app
.get('/auth/register/')
79 # Make sure it rendered with the appropriate template
80 assert template
.TEMPLATE_TEST_CONTEXT
.has_key(
81 'mediagoblin/auth/register.html')
83 # Try to register without providing anything, should error
84 # --------------------------------------------------------
86 template
.clear_test_template_context()
88 '/auth/register/', {})
89 context
= template
.TEMPLATE_TEST_CONTEXT
['mediagoblin/auth/register.html']
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.']
93 assert form
.email
.errors
== [u
'This field is required.']
95 # Try to register with fields that are known to be invalid
96 # --------------------------------------------------------
99 template
.clear_test_template_context()
105 context
= template
.TEMPLATE_TEST_CONTEXT
['mediagoblin/auth/register.html']
106 form
= context
['register_form']
108 assert_equal (form
.username
.errors
, [u
'Field must be between 3 and 30 characters long.'])
109 assert_equal (form
.password
.errors
, [u
'Field must be between 5 and 1024 characters long.'])
112 template
.clear_test_template_context()
116 'email': 'lollerskates'})
117 context
= template
.TEMPLATE_TEST_CONTEXT
['mediagoblin/auth/register.html']
118 form
= context
['register_form']
120 assert_equal (form
.username
.errors
, [u
'This field does not take email addresses.'])
121 assert_equal (form
.email
.errors
, [u
'This field requires an email address.'])
123 ## At this point there should be no users in the database ;)
124 assert_equal(User
.query
.count(), 0)
126 # Successful register
127 # -------------------
128 template
.clear_test_template_context()
129 response
= test_app
.post(
131 'username': u
'happygirl',
132 'password': 'iamsohappy',
133 'email': 'happygrrl@example.org'})
136 ## Did we redirect to the proper page? Use the right template?
138 urlparse
.urlsplit(response
.location
)[2],
140 assert template
.TEMPLATE_TEST_CONTEXT
.has_key(
141 'mediagoblin/user_pages/user.html')
143 ## Make sure user is in place
144 new_user
= mg_globals
.database
.User
.find_one(
145 {'username': u
'happygirl'})
147 assert new_user
.status
== u
'needs_email_verification'
148 assert new_user
.email_verified
== False
150 ## Make sure user is logged in
151 request
= template
.TEMPLATE_TEST_CONTEXT
[
152 'mediagoblin/user_pages/user.html']['request']
153 assert request
.session
['user_id'] == unicode(new_user
.id)
155 ## Make sure we get email confirmation, and try verifying
156 assert len(mail
.EMAIL_TEST_INBOX
) == 1
157 message
= mail
.EMAIL_TEST_INBOX
.pop()
158 assert message
['To'] == 'happygrrl@example.org'
159 email_context
= template
.TEMPLATE_TEST_CONTEXT
[
160 'mediagoblin/auth/verification_email.txt']
161 assert email_context
['verification_url'] in message
.get_payload(decode
=True)
163 path
= urlparse
.urlsplit(email_context
['verification_url'])[2]
164 get_params
= urlparse
.urlsplit(email_context
['verification_url'])[3]
165 assert path
== u
'/auth/verify_email/'
166 parsed_get_params
= urlparse
.parse_qs(get_params
)
168 ### user should have these same parameters
169 assert parsed_get_params
['userid'] == [
170 unicode(new_user
.id)]
171 assert parsed_get_params
['token'] == [
172 new_user
.verification_key
]
174 ## Try verifying with bs verification key, shouldn't work
175 template
.clear_test_template_context()
176 response
= test_app
.get(
177 "/auth/verify_email/?userid=%s&token=total_bs" % unicode(
180 context
= template
.TEMPLATE_TEST_CONTEXT
[
181 'mediagoblin/user_pages/user.html']
182 # assert context['verification_successful'] == True
183 # TODO: Would be good to test messages here when we can do so...
184 new_user
= mg_globals
.database
.User
.find_one(
185 {'username': u
'happygirl'})
187 assert new_user
.status
== u
'needs_email_verification'
188 assert new_user
.email_verified
== False
190 ## Verify the email activation works
191 template
.clear_test_template_context()
192 response
= test_app
.get("%s?%s" % (path
, get_params
))
194 context
= template
.TEMPLATE_TEST_CONTEXT
[
195 'mediagoblin/user_pages/user.html']
196 # assert context['verification_successful'] == True
197 # TODO: Would be good to test messages here when we can do so...
198 new_user
= mg_globals
.database
.User
.find_one(
199 {'username': u
'happygirl'})
201 assert new_user
.status
== u
'active'
202 assert new_user
.email_verified
== True
206 ## We shouldn't be able to register with that user twice
207 template
.clear_test_template_context()
208 response
= test_app
.post(
210 'username': u
'happygirl',
211 'password': 'iamsohappy2',
212 'email': 'happygrrl2@example.org'})
214 context
= template
.TEMPLATE_TEST_CONTEXT
[
215 'mediagoblin/auth/register.html']
216 form
= context
['register_form']
217 assert form
.username
.errors
== [
218 u
'Sorry, a user with that name already exists.']
220 ## TODO: Also check for double instances of an email address?
222 ### Oops, forgot the password
223 # -------------------
224 template
.clear_test_template_context()
225 response
= test_app
.post(
226 '/auth/forgot_password/',
227 {'username': u
'happygirl'})
230 ## Did we redirect to the proper page? Use the right template?
232 urlparse
.urlsplit(response
.location
)[2],
234 assert template
.TEMPLATE_TEST_CONTEXT
.has_key(
235 'mediagoblin/auth/login.html')
237 ## Make sure link to change password is sent by email
238 assert len(mail
.EMAIL_TEST_INBOX
) == 1
239 message
= mail
.EMAIL_TEST_INBOX
.pop()
240 assert message
['To'] == 'happygrrl@example.org'
241 email_context
= template
.TEMPLATE_TEST_CONTEXT
[
242 'mediagoblin/auth/fp_verification_email.txt']
243 #TODO - change the name of verification_url to something forgot-password-ish
244 assert email_context
['verification_url'] in message
.get_payload(decode
=True)
246 path
= urlparse
.urlsplit(email_context
['verification_url'])[2]
247 get_params
= urlparse
.urlsplit(email_context
['verification_url'])[3]
248 assert path
== u
'/auth/forgot_password/verify/'
249 parsed_get_params
= urlparse
.parse_qs(get_params
)
251 # user should have matching parameters
252 new_user
= mg_globals
.database
.User
.find_one({'username': u
'happygirl'})
253 assert parsed_get_params
['userid'] == [unicode(new_user
.id)]
254 assert parsed_get_params
['token'] == [new_user
.fp_verification_key
]
256 ### The forgotten password token should be set to expire in ~ 10 days
257 # A few ticks have expired so there are only 9 full days left...
258 assert (new_user
.fp_token_expire
- datetime
.datetime
.now()).days
== 9
260 ## Try using a bs password-changing verification key, shouldn't work
261 template
.clear_test_template_context()
262 response
= test_app
.get(
263 "/auth/forgot_password/verify/?userid=%s&token=total_bs" % unicode(
264 new_user
.id), status
=404)
265 assert_equal(response
.status
.split()[0], u
'404') # status="404 NOT FOUND"
267 ## Try using an expired token to change password, shouldn't work
268 template
.clear_test_template_context()
269 new_user
= mg_globals
.database
.User
.find_one({'username': u
'happygirl'})
270 real_token_expiration
= new_user
.fp_token_expire
271 new_user
.fp_token_expire
= datetime
.datetime
.now()
273 response
= test_app
.get("%s?%s" % (path
, get_params
), status
=404)
274 assert_equal(response
.status
.split()[0], u
'404') # status="404 NOT FOUND"
275 new_user
.fp_token_expire
= real_token_expiration
278 ## Verify step 1 of password-change works -- can see form to change password
279 template
.clear_test_template_context()
280 response
= test_app
.get("%s?%s" % (path
, get_params
))
281 assert template
.TEMPLATE_TEST_CONTEXT
.has_key('mediagoblin/auth/change_fp.html')
283 ## Verify step 2.1 of password-change works -- report success to user
284 template
.clear_test_template_context()
285 response
= test_app
.post(
286 '/auth/forgot_password/verify/', {
287 'userid': parsed_get_params
['userid'],
288 'password': 'iamveryveryhappy',
289 'token': parsed_get_params
['token']})
291 assert template
.TEMPLATE_TEST_CONTEXT
.has_key(
292 'mediagoblin/auth/login.html')
294 ## Verify step 2.2 of password-change works -- login w/ new password success
295 template
.clear_test_template_context()
296 response
= test_app
.post(
298 'username': u
'happygirl',
299 'password': 'iamveryveryhappy'})
301 # User should be redirected
304 urlparse
.urlsplit(response
.location
)[2],
306 assert template
.TEMPLATE_TEST_CONTEXT
.has_key(
307 'mediagoblin/root.html')
310 def test_authentication_views():
312 Test logging in and logging out
314 test_app
= get_app(dump_old_app
=False)
316 test_user
= fixture_add_user(active_user
=False)
320 test_app
.get('/auth/login/')
321 assert template
.TEMPLATE_TEST_CONTEXT
.has_key(
322 'mediagoblin/auth/login.html')
324 # Failed login - blank form
325 # -------------------------
326 template
.clear_test_template_context()
327 response
= test_app
.post('/auth/login/')
328 context
= template
.TEMPLATE_TEST_CONTEXT
['mediagoblin/auth/login.html']
329 form
= context
['login_form']
330 assert form
.username
.errors
== [u
'This field is required.']
331 assert form
.password
.errors
== [u
'This field is required.']
333 # Failed login - blank user
334 # -------------------------
335 template
.clear_test_template_context()
336 response
= test_app
.post(
338 'password': u
'toast'})
339 context
= template
.TEMPLATE_TEST_CONTEXT
['mediagoblin/auth/login.html']
340 form
= context
['login_form']
341 assert form
.username
.errors
== [u
'This field is required.']
343 # Failed login - blank password
344 # -----------------------------
345 template
.clear_test_template_context()
346 response
= test_app
.post(
348 'username': u
'chris'})
349 context
= template
.TEMPLATE_TEST_CONTEXT
['mediagoblin/auth/login.html']
350 form
= context
['login_form']
351 assert form
.password
.errors
== [u
'This field is required.']
353 # Failed login - bad user
354 # -----------------------
355 template
.clear_test_template_context()
356 response
= test_app
.post(
358 'username': u
'steve',
359 'password': 'toast'})
360 context
= template
.TEMPLATE_TEST_CONTEXT
['mediagoblin/auth/login.html']
361 assert context
['login_failed']
363 # Failed login - bad password
364 # ---------------------------
365 template
.clear_test_template_context()
366 response
= test_app
.post(
368 'username': u
'chris',
369 'password': 'jam_and_ham'})
370 context
= template
.TEMPLATE_TEST_CONTEXT
['mediagoblin/auth/login.html']
371 assert context
['login_failed']
375 template
.clear_test_template_context()
376 response
= test_app
.post(
378 'username': u
'chris',
379 'password': 'toast'})
381 # User should be redirected
384 urlparse
.urlsplit(response
.location
)[2],
386 assert template
.TEMPLATE_TEST_CONTEXT
.has_key(
387 'mediagoblin/root.html')
389 # Make sure user is in the session
390 context
= template
.TEMPLATE_TEST_CONTEXT
['mediagoblin/root.html']
391 session
= context
['request'].session
392 assert session
['user_id'] == unicode(test_user
.id)
396 template
.clear_test_template_context()
397 response
= test_app
.get('/auth/logout/')
399 # Should be redirected to index page
402 urlparse
.urlsplit(response
.location
)[2],
404 assert template
.TEMPLATE_TEST_CONTEXT
.has_key(
405 'mediagoblin/root.html')
407 # Make sure the user is not in the session
408 context
= template
.TEMPLATE_TEST_CONTEXT
['mediagoblin/root.html']
409 session
= context
['request'].session
410 assert session
.has_key('user_id') == False
412 # User is redirected to custom URL if POST['next'] is set
413 # -------------------------------------------------------
414 template
.clear_test_template_context()
415 response
= test_app
.post(
417 'username': u
'chris',
419 'next' : '/u/chris/'})
421 urlparse
.urlsplit(response
.location
)[2],