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