At this point, I am very close to done with this code! I made one big change at
[mediagoblin:mediagoblin.git] / mediagoblin / tests / test_openid.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
17 import urlparse
18 import pkg_resources
19 import pytest
20 import mock
21
22 openid_consumer = pytest.importorskip(
23     "openid.consumer.consumer")
24
25 from mediagoblin import mg_globals
26 from mediagoblin.db.base import Session
27 from mediagoblin.db.models import User
28 from mediagoblin.plugins.openid.models import OpenIDUserURL
29 from mediagoblin.tests.tools import get_app, fixture_add_user
30 from mediagoblin.tools import template
31
32 # App with plugin enabled
33 @pytest.fixture()
34 def openid_plugin_app(request):
35     return get_app(
36         request,
37         mgoblin_config=pkg_resources.resource_filename(
38             'mediagoblin.tests.auth_configs',
39             'openid_appconfig.ini'))
40
41
42 class TestOpenIDPlugin(object):
43     def _setup(self, openid_plugin_app, value=True, edit=False, delete=False):
44         if value:
45             response = openid_consumer.SuccessResponse(mock.Mock(), mock.Mock())
46             if edit or delete:
47                 response.identity_url = u'http://add.myopenid.com'
48             else:
49                 response.identity_url = u'http://real.myopenid.com'
50             self._finish_verification = mock.Mock(return_value=response)
51         else:
52             self._finish_verification = mock.Mock(return_value=False)
53
54         @mock.patch('mediagoblin.plugins.openid.views._response_email', mock.Mock(return_value=None))
55         @mock.patch('mediagoblin.plugins.openid.views._response_nickname', mock.Mock(return_value=None))
56         @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
57         def _setup_start(self, openid_plugin_app, edit, delete):
58             if edit:
59                 self._start_verification = mock.Mock(return_value=openid_plugin_app.post(
60                     '/edit/openid/finish/'))
61             elif delete:
62                 self._start_verification = mock.Mock(return_value=openid_plugin_app.post(
63                     '/edit/openid/delete/finish/'))
64             else:
65                 self._start_verification = mock.Mock(return_value=openid_plugin_app.post(
66                     '/auth/openid/login/finish/'))
67         _setup_start(self, openid_plugin_app, edit, delete)
68
69     def test_bad_login(self, openid_plugin_app):
70         """ Test that attempts to login with invalid paramaters"""
71
72         # Test GET request for auth/register page
73         res = openid_plugin_app.get('/auth/register/').follow()
74
75         # Make sure it redirected to the correct place
76         assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
77
78         # Test GET request for auth/login page
79         res = openid_plugin_app.get('/auth/login/')
80         res.follow()
81
82         # Correct redirect?
83         assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
84
85         # Test GET request for auth/openid/register page
86         res = openid_plugin_app.get('/auth/openid/register/')
87         res.follow()
88
89         # Correct redirect?
90         assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
91
92         # Test GET request for auth/openid/login/finish page
93         res = openid_plugin_app.get('/auth/openid/login/finish/')
94         res.follow()
95
96         # Correct redirect?
97         assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
98
99         # Test GET request for auth/openid/login page
100         res = openid_plugin_app.get('/auth/openid/login/')
101
102         # Correct place?
103         assert 'mediagoblin/plugins/openid/login.html' in template.TEMPLATE_TEST_CONTEXT
104
105         # Try to login with an empty form
106         template.clear_test_template_context()
107         openid_plugin_app.post(
108             '/auth/openid/login/', {})
109         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/login.html']
110         form = context['login_form']
111         assert form.openid.errors == [u'This field is required.']
112
113         # Try to login with wrong form values
114         template.clear_test_template_context()
115         openid_plugin_app.post(
116             '/auth/openid/login/', {
117                 'openid': 'not_a_url.com'})
118         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/login.html']
119         form = context['login_form']
120         assert form.openid.errors == [u'Please enter a valid url.']
121
122         # Should be no users in the db
123         assert User.query.count() == 0
124
125         # Phony OpenID URl
126         template.clear_test_template_context()
127         openid_plugin_app.post(
128             '/auth/openid/login/', {
129                 'openid': 'http://phoney.myopenid.com/'})
130         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/login.html']
131         form = context['login_form']
132         assert form.openid.errors == [u'Sorry, the OpenID server could not be found']
133
134     def test_login(self, openid_plugin_app):
135         """Tests that test login and registion with openid"""
136         # Test finish_login redirects correctly when response = False
137         self._setup(openid_plugin_app, False)
138
139         @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
140         @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
141         def _test_non_response():
142             template.clear_test_template_context()
143             res = openid_plugin_app.post(
144                 '/auth/openid/login/', {
145                     'openid': 'http://phoney.myopenid.com/'})
146             res.follow()
147
148             # Correct Place?
149             assert urlparse.urlsplit(res.location)[2] == '/auth/openid/login/'
150             assert 'mediagoblin/plugins/openid/login.html' in template.TEMPLATE_TEST_CONTEXT
151         _test_non_response()
152
153         # Test login with new openid
154         # Need to clear_test_template_context before calling _setup
155         template.clear_test_template_context()
156         self._setup(openid_plugin_app)
157
158         @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
159         @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
160         def _test_new_user():
161             openid_plugin_app.post(
162                 '/auth/openid/login/', {
163                     'openid': u'http://real.myopenid.com'})
164
165             # Right place?
166             assert 'mediagoblin/auth/register.html' in template.TEMPLATE_TEST_CONTEXT
167             context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
168             register_form = context['register_form']
169
170             # Register User
171             res = openid_plugin_app.post(
172                 '/auth/openid/register/', {
173                     'openid': register_form.openid.data,
174                     'username': u'chris',
175                     'email': u'chris@example.com'})
176             res.follow()
177
178             # Correct place?
179             assert urlparse.urlsplit(res.location)[2] == '/u/chris/'
180             assert 'mediagoblin/user_pages/user.html' in template.TEMPLATE_TEST_CONTEXT
181
182             # No need to test if user is in logged in and verification email
183             # awaits, since openid uses the register_user function which is
184             # tested in test_auth
185
186             # Logout User
187             openid_plugin_app.get('/auth/logout')
188
189             # Get user and detach from session
190             test_user = mg_globals.database.User.query.filter_by(
191                 username=u'chris').first()
192             Session.expunge(test_user)
193
194             # Log back in
195             # Could not get it to work by 'POST'ing to /auth/openid/login/
196             template.clear_test_template_context()
197             res = openid_plugin_app.post(
198                 '/auth/openid/login/finish/', {
199                     'openid': u'http://real.myopenid.com'})
200             res.follow()
201
202             assert urlparse.urlsplit(res.location)[2] == '/'
203             assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
204
205             # Make sure user is in the session
206             context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
207             session = context['request'].session
208             assert session['user_id'] == unicode(test_user.id)
209
210         _test_new_user()
211
212         # Test register with empty form
213         template.clear_test_template_context()
214         openid_plugin_app.post(
215             '/auth/openid/register/', {})
216         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
217         register_form = context['register_form']
218
219         assert register_form.openid.errors == [u'This field is required.']
220         assert register_form.email.errors == [u'This field is required.']
221         assert register_form.username.errors == [u'This field is required.']
222
223         # Try to register with existing username and email
224         template.clear_test_template_context()
225         openid_plugin_app.post(
226             '/auth/openid/register/', {
227                 'openid': 'http://real.myopenid.com',
228                 'email': 'chris@example.com',
229                 'username': 'chris'})
230         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
231         register_form = context['register_form']
232
233         assert register_form.username.errors == [u'Sorry, a user with that name already exists.']
234         assert register_form.email.errors == [u'Sorry, a user with that email address already exists.']
235         assert register_form.openid.errors == [u'Sorry, an account is already registered to that OpenID.']
236
237     def test_add_delete(self, openid_plugin_app):
238         """Test adding and deleting openids"""
239         # Add user
240         test_user = fixture_add_user(password='', privileges=[u'active'])
241         openid = OpenIDUserURL()
242         openid.openid_url = 'http://real.myopenid.com'
243         openid.user_id = test_user.id
244         openid.save()
245
246         # Log user in
247         template.clear_test_template_context()
248         self._setup(openid_plugin_app)
249
250         @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
251         @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
252         def _login_user():
253             openid_plugin_app.post(
254                 '/auth/openid/login/finish/', {
255                     'openid': u'http://real.myopenid.com'})
256
257         _login_user()
258
259         # Try and delete only OpenID url
260         template.clear_test_template_context()
261         res = openid_plugin_app.post(
262             '/edit/openid/delete/', {
263                 'openid': 'http://real.myopenid.com'})
264         assert 'mediagoblin/plugins/openid/delete.html' in template.TEMPLATE_TEST_CONTEXT
265
266         # Add OpenID to user
267         # Empty form
268         template.clear_test_template_context()
269         res = openid_plugin_app.post(
270             '/edit/openid/', {})
271         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/add.html']
272         form = context['form']
273         assert form.openid.errors == [u'This field is required.']
274
275         # Try with a bad url
276         template.clear_test_template_context()
277         openid_plugin_app.post(
278             '/edit/openid/', {
279                 'openid': u'not_a_url.com'})
280         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/add.html']
281         form = context['form']
282         assert form.openid.errors == [u'Please enter a valid url.']
283
284         # Try with a url that's already registered
285         template.clear_test_template_context()
286         openid_plugin_app.post(
287             '/edit/openid/', {
288                 'openid': 'http://real.myopenid.com'})
289         context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/add.html']
290         form = context['form']
291         assert form.openid.errors == [u'Sorry, an account is already registered to that OpenID.']
292
293         # Test adding openid to account
294         # Need to clear_test_template_context before calling _setup
295         template.clear_test_template_context()
296         self._setup(openid_plugin_app, edit=True)
297
298         # Need to remove openid_url from db because it was added at setup
299         openid = OpenIDUserURL.query.filter_by(
300             openid_url=u'http://add.myopenid.com')
301         openid.delete()
302
303         @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
304         @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
305         def _test_add():
306             # Successful add
307             template.clear_test_template_context()
308             res = openid_plugin_app.post(
309                 '/edit/openid/', {
310                     'openid': u'http://add.myopenid.com'})
311             res.follow()
312
313             # Correct place?
314             assert urlparse.urlsplit(res.location)[2] == '/edit/account/'
315             assert 'mediagoblin/edit/edit_account.html' in template.TEMPLATE_TEST_CONTEXT
316
317             # OpenID Added?
318             new_openid = mg_globals.database.OpenIDUserURL.query.filter_by(
319                 openid_url=u'http://add.myopenid.com').first()
320             assert new_openid
321
322         _test_add()
323
324         # Test deleting openid from account
325         # Need to clear_test_template_context before calling _setup
326         template.clear_test_template_context()
327         self._setup(openid_plugin_app, delete=True)
328
329         # Need to add OpenID back to user because it was deleted during
330         # patch
331         openid = OpenIDUserURL()
332         openid.openid_url = 'http://add.myopenid.com'
333         openid.user_id = test_user.id
334         openid.save()
335
336         @mock.patch('mediagoblin.plugins.openid.views._finish_verification', self._finish_verification)
337         @mock.patch('mediagoblin.plugins.openid.views._start_verification', self._start_verification)
338         def _test_delete(self, test_user):
339             # Delete openid from user
340             # Create another user to test deleting OpenID that doesn't belong to them
341             new_user = fixture_add_user(username='newman')
342             openid = OpenIDUserURL()
343             openid.openid_url = 'http://realfake.myopenid.com/'
344             openid.user_id = new_user.id
345             openid.save()
346
347             # Try and delete OpenID url that isn't the users
348             template.clear_test_template_context()
349             res = openid_plugin_app.post(
350                 '/edit/openid/delete/', {
351                     'openid': 'http://realfake.myopenid.com/'})
352             context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/plugins/openid/delete.html']
353             form = context['form']
354             assert form.openid.errors == [u'That OpenID is not registered to this account.']
355
356             # Delete OpenID
357             # Kind of weird to POST to delete/finish
358             template.clear_test_template_context()
359             res = openid_plugin_app.post(
360                 '/edit/openid/delete/finish/', {
361                     'openid': u'http://add.myopenid.com'})
362             res.follow()
363
364             # Correct place?
365             assert urlparse.urlsplit(res.location)[2] == '/edit/account/'
366             assert 'mediagoblin/edit/edit_account.html' in template.TEMPLATE_TEST_CONTEXT
367
368             # OpenID deleted?
369             new_openid = mg_globals.database.OpenIDUserURL.query.filter_by(
370                 openid_url=u'http://add.myopenid.com').first()
371             assert not new_openid
372
373         _test_delete(self, test_user)