Fix another tests.
[mediagoblin: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
237 def test_authentication_views(test_app):
238     """
239     Test logging in and logging out
240     """
241     # Make a new user
242     test_user = fixture_add_user()
243
244
245     # Get login
246     # ---------
247     test_app.get('/auth/login/')
248     assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
249
250     # Failed login - blank form
251     # -------------------------
252     template.clear_test_template_context()
253     response = test_app.post('/auth/login/')
254     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
255     form = context['login_form']
256     assert form.username.errors == [u'This field is required.']
257
258     # Failed login - blank user
259     # -------------------------
260     template.clear_test_template_context()
261     response = test_app.post(
262         '/auth/login/', {
263             'password': u'toast'})
264     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
265     form = context['login_form']
266     assert form.username.errors == [u'This field is required.']
267
268     # Failed login - blank password
269     # -----------------------------
270     template.clear_test_template_context()
271     response = test_app.post(
272         '/auth/login/', {
273             'username': u'chris'})
274     assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
275
276     # Failed login - bad user
277     # -----------------------
278     template.clear_test_template_context()
279     response = test_app.post(
280         '/auth/login/', {
281             'username': u'steve',
282             'password': 'toast'})
283     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
284     assert context['login_failed']
285
286     # Failed login - bad password
287     # ---------------------------
288     template.clear_test_template_context()
289     response = test_app.post(
290         '/auth/login/', {
291             'username': u'chris',
292             'password': 'jam_and_ham'})
293     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
294     assert context['login_failed']
295
296     # Successful login
297     # ----------------
298     template.clear_test_template_context()
299     response = test_app.post(
300         '/auth/login/', {
301             'username': u'chris',
302             'password': 'toast'})
303
304     # User should be redirected
305     response.follow()
306     assert urlparse.urlsplit(response.location)[2] == '/'
307     assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
308
309     # Make sure user is in the session
310     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
311     session = context['request'].session
312     assert session['user_id'] == six.text_type(test_user.id)
313
314     # Successful logout
315     # -----------------
316     template.clear_test_template_context()
317     response = test_app.get('/auth/logout/')
318
319     # Should be redirected to index page
320     response.follow()
321     assert urlparse.urlsplit(response.location)[2] == '/'
322     assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
323
324     # Make sure the user is not in the session
325     context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
326     session = context['request'].session
327     assert 'user_id' not in session
328
329     # User is redirected to custom URL if POST['next'] is set
330     # -------------------------------------------------------
331     template.clear_test_template_context()
332     response = test_app.post(
333         '/auth/login/', {
334             'username': u'chris',
335             'password': 'toast',
336             'next' : '/u/chris/'})
337     assert urlparse.urlsplit(response.location)[2] == '/u/chris/'
338
339 @pytest.fixture()
340 def authentication_disabled_app(request):
341     return get_app(
342         request,
343         mgoblin_config=pkg_resources.resource_filename(
344             'mediagoblin.tests.auth_configs',
345             'authentication_disabled_appconfig.ini'))
346
347
348 def test_authentication_disabled_app(authentication_disabled_app):
349     # app.auth should = false
350     assert mg_globals
351     assert mg_globals.app.auth is False
352
353     # Try to visit register page
354     template.clear_test_template_context()
355     response = authentication_disabled_app.get('/auth/register/')
356     response.follow()
357
358     # Correct redirect?
359     assert urlparse.urlsplit(response.location)[2] == '/'
360     assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
361
362     # Try to vist login page
363     template.clear_test_template_context()
364     response = authentication_disabled_app.get('/auth/login/')
365     response.follow()
366
367     # Correct redirect?
368     assert urlparse.urlsplit(response.location)[2] == '/'
369     assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
370
371     ## Test check_login_simple should return None
372     assert auth_tools.check_login_simple('test', 'simple') is None
373
374     # Try to visit the forgot password page
375     template.clear_test_template_context()
376     response = authentication_disabled_app.get('/auth/register/')
377     response.follow()
378
379     # Correct redirect?
380     assert urlparse.urlsplit(response.location)[2] == '/'
381     assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT