Merge branch 'stable'
[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, LocalUser
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.LocalUser.query.filter(
102 LocalUser.username==u'angrygirl'
103 ).first()
104 assert new_user
105
106 ## Make sure that the proper privileges are granted on registration
107
108 assert new_user.has_privilege(u'commenter')
109 assert new_user.has_privilege(u'uploader')
110 assert new_user.has_privilege(u'reporter')
111 assert not new_user.has_privilege(u'active')
112 ## Make sure user is logged in
113 request = template.TEMPLATE_TEST_CONTEXT[
114 'mediagoblin/user_pages/user_nonactive.html']['request']
115 assert request.session['user_id'] == six.text_type(new_user.id)
116
117 ## Make sure we get email confirmation, and try verifying
118 assert len(mail.EMAIL_TEST_INBOX) == 1
119 message = mail.EMAIL_TEST_INBOX.pop()
120 assert message['To'] == 'angrygrrl@example.org'
121 email_context = template.TEMPLATE_TEST_CONTEXT[
122 'mediagoblin/auth/verification_email.txt']
123 assert email_context['verification_url'].encode('ascii') in message.get_payload(decode=True)
124
125 path = urlparse.urlsplit(email_context['verification_url'])[2]
126 get_params = urlparse.urlsplit(email_context['verification_url'])[3]
127 assert path == u'/auth/verify_email/'
128 parsed_get_params = urlparse.parse_qs(get_params)
129
130 ## Try verifying with bs verification key, shouldn't work
131 template.clear_test_template_context()
132 response = test_app.get(
133 "/auth/verify_email/?token=total_bs")
134 response.follow()
135
136 # Correct redirect?
137 assert urlparse.urlsplit(response.location)[2] == '/'
138
139 # assert context['verification_successful'] == True
140 # TODO: Would be good to test messages here when we can do so...
141 new_user = mg_globals.database.LocalUser.query.filter(
142 LocalUser.username==u'angrygirl'
143 ).first()
144 assert new_user
145
146 ## Verify the email activation works
147 template.clear_test_template_context()
148 response = test_app.get("%s?%s" % (path, get_params))
149 response.follow()
150 context = template.TEMPLATE_TEST_CONTEXT[
151 'mediagoblin/user_pages/user.html']
152 # assert context['verification_successful'] == True
153 # TODO: Would be good to test messages here when we can do so...
154 new_user = mg_globals.database.LocalUser.query.filter(
155 LocalUser.username==u'angrygirl'
156 ).first()
157 assert new_user
158
159 # Uniqueness checks
160 # -----------------
161 ## We shouldn't be able to register with that user twice
162 template.clear_test_template_context()
163 response = test_app.post(
164 '/auth/register/', {
165 'username': u'angrygirl',
166 'password': 'iamsoangry2',
167 'email': 'angrygrrl2@example.org'})
168
169 context = template.TEMPLATE_TEST_CONTEXT[
170 'mediagoblin/auth/register.html']
171 form = context['register_form']
172 assert form.username.errors == [
173 u'Sorry, a user with that name already exists.']
174
175 ## TODO: Also check for double instances of an email address?
176
177 ### Oops, forgot the password
178 # -------------------
179 template.clear_test_template_context()
180 response = test_app.post(
181 '/auth/forgot_password/',
182 {'username': u'angrygirl'})
183 response.follow()
184
185 ## Did we redirect to the proper page? Use the right template?
186 assert urlparse.urlsplit(response.location)[2] == '/auth/login/'
187 assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
188
189 ## Make sure link to change password is sent by email
190 assert len(mail.EMAIL_TEST_INBOX) == 1
191 message = mail.EMAIL_TEST_INBOX.pop()
192 assert message['To'] == 'angrygrrl@example.org'
193 email_context = template.TEMPLATE_TEST_CONTEXT[
194 'mediagoblin/plugins/basic_auth/fp_verification_email.txt']
195 #TODO - change the name of verification_url to something forgot-password-ish
196 assert email_context['verification_url'].encode('ascii') in message.get_payload(decode=True)
197
198 path = urlparse.urlsplit(email_context['verification_url'])[2]
199 get_params = urlparse.urlsplit(email_context['verification_url'])[3]
200 parsed_get_params = urlparse.parse_qs(get_params)
201 assert path == u'/auth/forgot_password/verify/'
202
203 ## Try using a bs password-changing verification key, shouldn't work
204 template.clear_test_template_context()
205 response = test_app.get(
206 "/auth/forgot_password/verify/?token=total_bs")
207 response.follow()
208
209 # Correct redirect?
210 assert urlparse.urlsplit(response.location)[2] == '/'
211
212 ## Verify step 1 of password-change works -- can see form to change password
213 template.clear_test_template_context()
214 response = test_app.get("%s?%s" % (path, get_params))
215 assert 'mediagoblin/plugins/basic_auth/change_fp.html' in \
216 template.TEMPLATE_TEST_CONTEXT
217
218 ## Verify step 2.1 of password-change works -- report success to user
219 template.clear_test_template_context()
220 response = test_app.post(
221 '/auth/forgot_password/verify/', {
222 'password': 'iamveryveryangry',
223 'token': parsed_get_params['token']})
224 response.follow()
225 assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
226
227 ## Verify step 2.2 of password-change works -- login w/ new password success
228 template.clear_test_template_context()
229 response = test_app.post(
230 '/auth/login/', {
231 'username': u'angrygirl',
232 'password': 'iamveryveryangry'})
233
234 # User should be redirected
235 response.follow()
236 assert urlparse.urlsplit(response.location)[2] == '/'
237 assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
238
239 def test_authentication_views(test_app):
240 """
241 Test logging in and logging out
242 """
243 # Make a new user
244 test_user = fixture_add_user()
245
246
247 # Get login
248 # ---------
249 test_app.get('/auth/login/')
250 assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
251
252 # Failed login - blank form
253 # -------------------------
254 template.clear_test_template_context()
255 response = test_app.post('/auth/login/')
256 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
257 form = context['login_form']
258 assert form.username.errors == [u'This field is required.']
259
260 # Failed login - blank user
261 # -------------------------
262 template.clear_test_template_context()
263 response = test_app.post(
264 '/auth/login/', {
265 'password': u'toast'})
266 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
267 form = context['login_form']
268 assert form.username.errors == [u'This field is required.']
269
270 # Failed login - blank password
271 # -----------------------------
272 template.clear_test_template_context()
273 response = test_app.post(
274 '/auth/login/', {
275 'username': u'chris'})
276 assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
277
278 # Failed login - bad user
279 # -----------------------
280 template.clear_test_template_context()
281 response = test_app.post(
282 '/auth/login/', {
283 'username': u'steve',
284 'password': 'toast'})
285 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
286 assert context['login_failed']
287
288 # Failed login - bad password
289 # ---------------------------
290 template.clear_test_template_context()
291 response = test_app.post(
292 '/auth/login/', {
293 'username': u'chris',
294 'password': 'jam_and_ham'})
295 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
296 assert context['login_failed']
297
298 # Successful login
299 # ----------------
300 template.clear_test_template_context()
301 response = test_app.post(
302 '/auth/login/', {
303 'username': u'chris',
304 'password': 'toast'})
305
306 # User should be redirected
307 response.follow()
308 assert urlparse.urlsplit(response.location)[2] == '/'
309 assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
310
311 # Make sure user is in the session
312 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
313 session = context['request'].session
314 assert session['user_id'] == six.text_type(test_user.id)
315
316 # Successful logout
317 # -----------------
318 template.clear_test_template_context()
319 response = test_app.get('/auth/logout/')
320
321 # Should be redirected to index page
322 response.follow()
323 assert urlparse.urlsplit(response.location)[2] == '/'
324 assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
325
326 # Make sure the user is not in the session
327 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
328 session = context['request'].session
329 assert 'user_id' not in session
330
331 # User is redirected to custom URL if POST['next'] is set
332 # -------------------------------------------------------
333 template.clear_test_template_context()
334 response = test_app.post(
335 '/auth/login/', {
336 'username': u'chris',
337 'password': 'toast',
338 'next' : '/u/chris/'})
339 assert urlparse.urlsplit(response.location)[2] == '/u/chris/'
340
341 ## Verify that username is lowercased on login attempt
342 template.clear_test_template_context()
343 response = test_app.post(
344 '/auth/login/', {
345 'username': u'ANDREW',
346 'password': 'fuselage'})
347 context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
348 form = context['login_form']
349
350 # Username should no longer be uppercased; it should be lowercased
351 assert not form.username.data == u'ANDREW'
352 assert form.username.data == u'andrew'
353
354 @pytest.fixture()
355 def authentication_disabled_app(request):
356 return get_app(
357 request,
358 mgoblin_config=pkg_resources.resource_filename(
359 'mediagoblin.tests.auth_configs',
360 'authentication_disabled_appconfig.ini'))
361
362
363 def test_authentication_disabled_app(authentication_disabled_app):
364 # app.auth should = false
365 assert mg_globals
366 assert mg_globals.app.auth is False
367
368 # Try to visit register 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
376
377 # Try to vist login page
378 template.clear_test_template_context()
379 response = authentication_disabled_app.get('/auth/login/')
380 response.follow()
381
382 # Correct redirect?
383 assert urlparse.urlsplit(response.location)[2] == '/'
384 assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
385
386 ## Test check_login_simple should return None
387 assert auth_tools.check_login_simple('test', 'simple') is None
388
389 # Try to visit the forgot password page
390 template.clear_test_template_context()
391 response = authentication_disabled_app.get('/auth/register/')
392 response.follow()
393
394 # Correct redirect?
395 assert urlparse.urlsplit(response.location)[2] == '/'
396 assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT