Merge branch 'master' into processing
[mediagoblin.git] / mediagoblin / tests / test_auth.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011 Free Software Foundation, Inc
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
17 import urlparse
18
19 from nose.tools import assert_equal
20
21 from mediagoblin.auth import lib as auth_lib
22 from mediagoblin.tests.tools import setup_fresh_app
23 from mediagoblin import mg_globals
24 from mediagoblin import util
25
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
37 assert not auth_lib.bcrypt_check_password(
38 'notthepassword',
39 '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
40
41
42 # Same thing, but with extra fake salt.
43 assert not auth_lib.bcrypt_check_password(
44 'notthepassword',
45 '$2a$12$ELVlnw3z1FMu6CEGs/L8XO8vl0BuWSlUHgh0rUrry9DUXGMUNWwl6',
46 '3><7R45417')
47
48
49 def test_bcrypt_gen_password_hash():
50 pw = 'youwillneverguessthis'
51
52 # Normal password hash generation, and check on that hash
53 hashed_pw = auth_lib.bcrypt_gen_password_hash(pw)
54 assert auth_lib.bcrypt_check_password(
55 pw, hashed_pw)
56 assert not auth_lib.bcrypt_check_password(
57 'notthepassword', hashed_pw)
58
59
60 # Same thing, extra salt.
61 hashed_pw = auth_lib.bcrypt_gen_password_hash(pw, '3><7R45417')
62 assert auth_lib.bcrypt_check_password(
63 pw, hashed_pw, '3><7R45417')
64 assert not auth_lib.bcrypt_check_password(
65 'notthepassword', hashed_pw, '3><7R45417')
66
67
68 @setup_fresh_app
69 def test_register_views(test_app):
70 """
71 Massive test function that all our registration-related views all work.
72 """
73 # Test doing a simple GET on the page
74 # -----------------------------------
75
76 test_app.get('/auth/register/')
77 # Make sure it rendered with the appropriate template
78 assert util.TEMPLATE_TEST_CONTEXT.has_key(
79 'mediagoblin/auth/register.html')
80
81 # Try to register without providing anything, should error
82 # --------------------------------------------------------
83
84 util.clear_test_template_context()
85 test_app.post(
86 '/auth/register/', {})
87 context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
88 form = context['register_form']
89 assert form.username.errors == [u'This field is required.']
90 assert form.password.errors == [u'This field is required.']
91 assert form.confirm_password.errors == [u'This field is required.']
92 assert form.email.errors == [u'This field is required.']
93
94 # Try to register with fields that are known to be invalid
95 # --------------------------------------------------------
96
97 ## too short
98 util.clear_test_template_context()
99 test_app.post(
100 '/auth/register/', {
101 'username': 'l',
102 'password': 'o',
103 'confirm_password': 'o',
104 'email': 'l'})
105 context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
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
114 util.clear_test_template_context()
115 test_app.post(
116 '/auth/register/', {
117 'username': '@_@',
118 'email': 'lollerskates'})
119 context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
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
127 ## mismatching passwords
128 util.clear_test_template_context()
129 test_app.post(
130 '/auth/register/', {
131 'password': 'herpderp',
132 'confirm_password': 'derpherp'})
133 context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
134 form = context['register_form']
135
136 assert form.password.errors == [
137 u'Passwords must match.']
138
139 ## At this point there should be no users in the database ;)
140 assert not mg_globals.database.User.find().count()
141
142 # Successful register
143 # -------------------
144 util.clear_test_template_context()
145 response = test_app.post(
146 '/auth/register/', {
147 'username': 'happygirl',
148 'password': 'iamsohappy',
149 'confirm_password': 'iamsohappy',
150 'email': 'happygrrl@example.org'})
151 response.follow()
152
153 ## Did we redirect to the proper page? Use the right template?
154 assert_equal(
155 urlparse.urlsplit(response.location)[2],
156 '/u/happygirl/')
157 assert util.TEMPLATE_TEST_CONTEXT.has_key(
158 'mediagoblin/user_pages/user.html')
159
160 ## Make sure user is in place
161 new_user = mg_globals.database.User.find_one(
162 {'username': 'happygirl'})
163 assert new_user
164 assert new_user['status'] == u'needs_email_verification'
165 assert new_user['email_verified'] == False
166
167 ## Make sure user is logged in
168 request = util.TEMPLATE_TEST_CONTEXT[
169 'mediagoblin/user_pages/user.html']['request']
170 assert request.session['user_id'] == unicode(new_user['_id'])
171
172 ## Make sure we get email confirmation, and try verifying
173 assert len(util.EMAIL_TEST_INBOX) == 1
174 message = util.EMAIL_TEST_INBOX.pop()
175 assert message['To'] == 'happygrrl@example.org'
176 email_context = util.TEMPLATE_TEST_CONTEXT[
177 'mediagoblin/auth/verification_email.txt']
178 assert email_context['verification_url'] in message.get_payload(decode=True)
179
180 path = urlparse.urlsplit(email_context['verification_url'])[2]
181 get_params = urlparse.urlsplit(email_context['verification_url'])[3]
182 assert path == u'/auth/verify_email/'
183 parsed_get_params = urlparse.parse_qs(get_params)
184
185 ### user should have these same parameters
186 assert parsed_get_params['userid'] == [
187 unicode(new_user['_id'])]
188 assert parsed_get_params['token'] == [
189 new_user['verification_key']]
190
191 ## Try verifying with bs verification key, shouldn't work
192 util.clear_test_template_context()
193 response = test_app.get(
194 "/auth/verify_email/?userid=%s&token=total_bs" % unicode(
195 new_user['_id']))
196 response.follow()
197 context = util.TEMPLATE_TEST_CONTEXT[
198 'mediagoblin/user_pages/user.html']
199 # assert context['verification_successful'] == True
200 # TODO: Would be good to test messages here when we can do so...
201 new_user = mg_globals.database.User.find_one(
202 {'username': 'happygirl'})
203 assert new_user
204 assert new_user['status'] == u'needs_email_verification'
205 assert new_user['email_verified'] == False
206
207 ## Verify the email activation works
208 util.clear_test_template_context()
209 response = test_app.get("%s?%s" % (path, get_params))
210 response.follow()
211 context = util.TEMPLATE_TEST_CONTEXT[
212 'mediagoblin/user_pages/user.html']
213 # assert context['verification_successful'] == True
214 # TODO: Would be good to test messages here when we can do so...
215 new_user = mg_globals.database.User.find_one(
216 {'username': 'happygirl'})
217 assert new_user
218 assert new_user['status'] == u'active'
219 assert new_user['email_verified'] == True
220
221 # Uniqueness checks
222 # -----------------
223 ## We shouldn't be able to register with that user twice
224 util.clear_test_template_context()
225 response = test_app.post(
226 '/auth/register/', {
227 'username': 'happygirl',
228 'password': 'iamsohappy2',
229 'confirm_password': 'iamsohappy2',
230 'email': 'happygrrl2@example.org'})
231
232 context = util.TEMPLATE_TEST_CONTEXT[
233 'mediagoblin/auth/register.html']
234 form = context['register_form']
235 assert form.username.errors == [
236 u'Sorry, a user with that name already exists.']
237
238 ## TODO: Also check for double instances of an email address?
239
240
241 @setup_fresh_app
242 def test_authentication_views(test_app):
243 """
244 Test logging in and logging out
245 """
246 # Make a new user
247 test_user = mg_globals.database.User()
248 test_user['username'] = u'chris'
249 test_user['email'] = u'chris@example.com'
250 test_user['pw_hash'] = auth_lib.bcrypt_gen_password_hash('toast')
251 test_user.save()
252
253 # Get login
254 # ---------
255 test_app.get('/auth/login/')
256 assert util.TEMPLATE_TEST_CONTEXT.has_key(
257 'mediagoblin/auth/login.html')
258
259 # Failed login - blank form
260 # -------------------------
261 util.clear_test_template_context()
262 response = test_app.post('/auth/login/')
263 context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
264 form = context['login_form']
265 assert form.username.errors == [u'This field is required.']
266 assert form.password.errors == [u'This field is required.']
267
268 # Failed login - blank user
269 # -------------------------
270 util.clear_test_template_context()
271 response = test_app.post(
272 '/auth/login/', {
273 'password': u'toast'})
274 context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
275 form = context['login_form']
276 assert form.username.errors == [u'This field is required.']
277
278 # Failed login - blank password
279 # -----------------------------
280 util.clear_test_template_context()
281 response = test_app.post(
282 '/auth/login/', {
283 'username': u'chris'})
284 context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
285 form = context['login_form']
286 assert form.password.errors == [u'This field is required.']
287
288 # Failed login - bad user
289 # -----------------------
290 util.clear_test_template_context()
291 response = test_app.post(
292 '/auth/login/', {
293 'username': u'steve',
294 'password': 'toast'})
295 context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
296 assert context['login_failed']
297
298 # Failed login - bad password
299 # ---------------------------
300 util.clear_test_template_context()
301 response = test_app.post(
302 '/auth/login/', {
303 'username': u'chris',
304 'password': 'jam'})
305 context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
306 assert context['login_failed']
307
308 # Successful login
309 # ----------------
310 util.clear_test_template_context()
311 response = test_app.post(
312 '/auth/login/', {
313 'username': u'chris',
314 'password': 'toast'})
315
316 # User should be redirected
317 response.follow()
318 assert_equal(
319 urlparse.urlsplit(response.location)[2],
320 '/')
321 assert util.TEMPLATE_TEST_CONTEXT.has_key(
322 'mediagoblin/root.html')
323
324 # Make sure user is in the session
325 context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
326 session = context['request'].session
327 assert session['user_id'] == unicode(test_user['_id'])
328
329 # Successful logout
330 # -----------------
331 util.clear_test_template_context()
332 response = test_app.get('/auth/logout/')
333
334 # Should be redirected to index page
335 response.follow()
336 assert_equal(
337 urlparse.urlsplit(response.location)[2],
338 '/')
339 assert util.TEMPLATE_TEST_CONTEXT.has_key(
340 'mediagoblin/root.html')
341
342 # Make sure the user is not in the session
343 context = util.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
344 session = context['request'].session
345 assert session.has_key('user_id') == False
346
347 # User is redirected to custom URL if POST['next'] is set
348 # -------------------------------------------------------
349 util.clear_test_template_context()
350 response = test_app.post(
351 '/auth/login/', {
352 'username': u'chris',
353 'password': 'toast',
354 'next' : '/u/chris/'})
355 assert_equal(
356 urlparse.urlsplit(response.location)[2],
357 '/u/chris/')
358