This was a very small update, I'm hoping to rebase after this to solve some
[mediagoblin.git] / mediagoblin / tests / test_auth.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
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 import urlparse
17 import pkg_resources
18 import pytest
19
20 from mediagoblin import mg_globals
21 from mediagoblin.db.models import User
22 from mediagoblin.tests.tools import get_app, fixture_add_user
23 from mediagoblin.tools import template, mail
24 from mediagoblin.auth import tools as auth_tools
25
26
27 def test_register_views(test_app):
28 """
29 Massive test function that all our registration-related views all work.
30 """
31 # Test doing a simple GET on the page
32 # -----------------------------------
33
34 test_app.get('/auth/register/')
35 # Make sure it rendered with the appropriate template
36 assert 'mediagoblin/auth/register.html' in template.TEMPLATE_TEST_CONTEXT
37
38 # Try to register without providing anything, should error
39 # --------------------------------------------------------
40
41 template.clear_test_template_context()
42 test_app.post(
43 '/auth/register/', {})
44 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
45 form = context['register_form']
46 assert form.username.errors == [u'This field is required.']
47 assert form.password.errors == [u'This field is required.']
48 assert form.email.errors == [u'This field is required.']
49
50 # Try to register with fields that are known to be invalid
51 # --------------------------------------------------------
52
53 ## too short
54 template.clear_test_template_context()
55 test_app.post(
56 '/auth/register/', {
57 'username': 'l',
58 'password': 'o',
59 'email': 'l'})
60 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
61 form = context['register_form']
62
63 assert form.username.errors == [u'Field must be between 3 and 30 characters long.']
64 assert form.password.errors == [u'Field must be between 5 and 1024 characters long.']
65
66 ## bad form
67 template.clear_test_template_context()
68 test_app.post(
69 '/auth/register/', {
70 'username': '@_@',
71 'email': 'lollerskates'})
72 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
73 form = context['register_form']
74
75 assert form.username.errors == [u'This field does not take email addresses.']
76 assert form.email.errors == [u'This field requires an email address.']
77
78 ## At this point there should be no users in the database ;)
79 assert User.query.count() == 0
80
81 # Successful register
82 # -------------------
83 template.clear_test_template_context()
84 response = test_app.post(
85 '/auth/register/', {
86 'username': u'angrygirl',
87 'password': 'iamsoangry',
88 'email': 'angrygrrl@example.org'})
89 response.follow()
90
91 ## Did we redirect to the proper page? Use the right template?
92 assert urlparse.urlsplit(response.location)[2] == '/u/angrygirl/'
93 assert 'mediagoblin/user_pages/user.html' in template.TEMPLATE_TEST_CONTEXT
94
95 ## Make sure user is in place
96 new_user = mg_globals.database.User.query.filter_by(
97 username=u'angrygirl').first()
98 assert new_user
99 assert new_user.status == u'needs_email_verification'
100 assert new_user.email_verified == False
101
102 ## Make sure user is logged in
103 request = template.TEMPLATE_TEST_CONTEXT[
104 'mediagoblin/user_pages/user.html']['request']
105 assert request.session['user_id'] == unicode(new_user.id)
106
107 ## Make sure we get email confirmation, and try verifying
108 assert len(mail.EMAIL_TEST_INBOX) == 1
109 message = mail.EMAIL_TEST_INBOX.pop()
110 assert message['To'] == 'angrygrrl@example.org'
111 email_context = template.TEMPLATE_TEST_CONTEXT[
112 'mediagoblin/auth/verification_email.txt']
113 assert email_context['verification_url'] in message.get_payload(decode=True)
114
115 path = urlparse.urlsplit(email_context['verification_url'])[2]
116 get_params = urlparse.urlsplit(email_context['verification_url'])[3]
117 assert path == u'/auth/verify_email/'
118 parsed_get_params = urlparse.parse_qs(get_params)
119
120 ## Try verifying with bs verification key, shouldn't work
121 template.clear_test_template_context()
122 response = test_app.get(
123 "/auth/verify_email/?token=total_bs")
124 response.follow()
125
126 # Correct redirect?
127 assert urlparse.urlsplit(response.location)[2] == '/'
128
129 # assert context['verification_successful'] == True
130 # TODO: Would be good to test messages here when we can do so...
131 new_user = mg_globals.database.User.query.filter_by(
132 username=u'angrygirl').first()
133 assert new_user
134 assert new_user.status == u'needs_email_verification'
135 assert new_user.email_verified == False
136
137 ## Verify the email activation works
138 template.clear_test_template_context()
139 response = test_app.get("%s?%s" % (path, get_params))
140 response.follow()
141 context = template.TEMPLATE_TEST_CONTEXT[
142 'mediagoblin/user_pages/user.html']
143 # assert context['verification_successful'] == True
144 # TODO: Would be good to test messages here when we can do so...
145 new_user = mg_globals.database.User.query.filter_by(
146 username=u'angrygirl').first()
147 assert new_user
148 assert new_user.status == u'active'
149 assert new_user.email_verified == True
150
151 # Uniqueness checks
152 # -----------------
153 ## We shouldn't be able to register with that user twice
154 template.clear_test_template_context()
155 response = test_app.post(
156 '/auth/register/', {
157 'username': u'angrygirl',
158 'password': 'iamsoangry2',
159 'email': 'angrygrrl2@example.org'})
160
161 context = template.TEMPLATE_TEST_CONTEXT[
162 'mediagoblin/auth/register.html']
163 form = context['register_form']
164 assert form.username.errors == [
165 u'Sorry, a user with that name already exists.']
166
167 ## TODO: Also check for double instances of an email address?
168
169 ### Oops, forgot the password
170 # -------------------
171 template.clear_test_template_context()
172 response = test_app.post(
173 '/auth/forgot_password/',
174 {'username': u'angrygirl'})
175 response.follow()
176
177 ## Did we redirect to the proper page? Use the right template?
178 assert urlparse.urlsplit(response.location)[2] == '/auth/login/'
179 assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
180
181 ## Make sure link to change password is sent by email
182 assert len(mail.EMAIL_TEST_INBOX) == 1
183 message = mail.EMAIL_TEST_INBOX.pop()
184 assert message['To'] == 'angrygrrl@example.org'
185 email_context = template.TEMPLATE_TEST_CONTEXT[
186 'mediagoblin/auth/fp_verification_email.txt']
187 #TODO - change the name of verification_url to something forgot-password-ish
188 assert email_context['verification_url'] in message.get_payload(decode=True)
189
190 path = urlparse.urlsplit(email_context['verification_url'])[2]
191 get_params = urlparse.urlsplit(email_context['verification_url'])[3]
192 parsed_get_params = urlparse.parse_qs(get_params)
193 assert path == u'/auth/forgot_password/verify/'
194
195 ## Try using a bs password-changing verification key, shouldn't work
196 template.clear_test_template_context()
197 response = test_app.get(
198 "/auth/forgot_password/verify/?token=total_bs")
199 response.follow()
200
201 # Correct redirect?
202 assert urlparse.urlsplit(response.location)[2] == '/'
203
204 ## Verify step 1 of password-change works -- can see form to change password
205 template.clear_test_template_context()
206 response = test_app.get("%s?%s" % (path, get_params))
207 assert 'mediagoblin/auth/change_fp.html' in template.TEMPLATE_TEST_CONTEXT
208
209 ## Verify step 2.1 of password-change works -- report success to user
210 template.clear_test_template_context()
211 response = test_app.post(
212 '/auth/forgot_password/verify/', {
213 'password': 'iamveryveryangry',
214 'token': parsed_get_params['token']})
215 response.follow()
216 assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
217
218 ## Verify step 2.2 of password-change works -- login w/ new password success
219 template.clear_test_template_context()
220 response = test_app.post(
221 '/auth/login/', {
222 'username': u'angrygirl',
223 'password': 'iamveryveryangry'})
224
225 # User should be redirected
226 response.follow()
227 assert urlparse.urlsplit(response.location)[2] == '/'
228 assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
229
230
231 def test_authentication_views(test_app):
232 """
233 Test logging in and logging out
234 """
235 # Make a new user
236 test_user = fixture_add_user()
237
238
239 # Get login
240 # ---------
241 test_app.get('/auth/login/')
242 assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
243
244 # Failed login - blank form
245 # -------------------------
246 template.clear_test_template_context()
247 response = test_app.post('/auth/login/')
248 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
249 form = context['login_form']
250 assert form.username.errors == [u'This field is required.']
251
252 # Failed login - blank user
253 # -------------------------
254 template.clear_test_template_context()
255 response = test_app.post(
256 '/auth/login/', {
257 'password': u'toast'})
258 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
259 form = context['login_form']
260 assert form.username.errors == [u'This field is required.']
261
262 # Failed login - blank password
263 # -----------------------------
264 template.clear_test_template_context()
265 response = test_app.post(
266 '/auth/login/', {
267 'username': u'chris'})
268 assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
269
270 # Failed login - bad user
271 # -----------------------
272 template.clear_test_template_context()
273 response = test_app.post(
274 '/auth/login/', {
275 'username': u'steve',
276 'password': 'toast'})
277 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
278 assert context['login_failed']
279
280 # Failed login - bad password
281 # ---------------------------
282 template.clear_test_template_context()
283 response = test_app.post(
284 '/auth/login/', {
285 'username': u'chris',
286 'password': 'jam_and_ham'})
287 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
288 assert context['login_failed']
289
290 # Successful login
291 # ----------------
292 template.clear_test_template_context()
293 response = test_app.post(
294 '/auth/login/', {
295 'username': u'chris',
296 'password': 'toast'})
297
298 # User should be redirected
299 response.follow()
300 assert urlparse.urlsplit(response.location)[2] == '/'
301 assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
302
303 # Make sure user is in the session
304 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
305 session = context['request'].session
306 assert session['user_id'] == unicode(test_user.id)
307
308 # Successful logout
309 # -----------------
310 template.clear_test_template_context()
311 response = test_app.get('/auth/logout/')
312
313 # Should be redirected to index page
314 response.follow()
315 assert urlparse.urlsplit(response.location)[2] == '/'
316 assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
317
318 # Make sure the user is not in the session
319 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
320 session = context['request'].session
321 assert 'user_id' not in session
322
323 # User is redirected to custom URL if POST['next'] is set
324 # -------------------------------------------------------
325 template.clear_test_template_context()
326 response = test_app.post(
327 '/auth/login/', {
328 'username': u'chris',
329 'password': 'toast',
330 'next' : '/u/chris/'})
331 assert urlparse.urlsplit(response.location)[2] == '/u/chris/'
332
333
334 @pytest.fixture()
335 def authentication_disabled_app(request):
336 return get_app(
337 request,
338 mgoblin_config=pkg_resources.resource_filename(
339 'mediagoblin.tests.auth_configs',
340 'authentication_disabled_appconfig.ini'))
341
342
343 def test_authentication_disabled_app(authentication_disabled_app):
344 # app.auth should = false
345 assert mg_globals.app.auth is False
346
347 # Try to visit register page
348 template.clear_test_template_context()
349 response = authentication_disabled_app.get('/auth/register/')
350 response.follow()
351
352 # Correct redirect?
353 assert urlparse.urlsplit(response.location)[2] == '/'
354 assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
355
356 # Try to vist login page
357 template.clear_test_template_context()
358 response = authentication_disabled_app.get('/auth/login/')
359 response.follow()
360
361 # Correct redirect?
362 assert urlparse.urlsplit(response.location)[2] == '/'
363 assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
364
365 ## Test check_login_simple should return None
366 assert auth_tools.check_login_simple('test', 'simple') is None
367
368 # Try to visit the forgot password page
369 template.clear_test_template_context()
370 response = authentication_disabled_app.get('/auth/register/')
371 response.follow()
372
373 # Correct redirect?
374 assert urlparse.urlsplit(response.location)[2] == '/'
375 assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT