[#927] Sets default language to en-US
[pixelated-user-agent.git] / service / test / unit / resources / test_login_resource.py
1 #
2 # Copyright (c) 2015 ThoughtWorks, Inc.
3 #
4 # Pixelated 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 # Pixelated 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 Pixelated. If not, see <http://www.gnu.org/licenses/>.
16
17 import os
18
19 from mock import patch, MagicMock
20 from mockito import mock, when, any as ANY
21 from twisted.cred.error import UnauthorizedLogin
22 from twisted.internet import defer
23 from twisted.trial import unittest
24 from twisted.web.test.requesthelper import DummyRequest
25
26 from pixelated.resources.login_resource import LoginResource, LoginStatusResource
27 from test.unit.resources import DummySite
28
29
30 class TestLoginResource(unittest.TestCase):
31     def setUp(self):
32         self.services_factory = mock()
33         self.portal = mock()
34         self.resource = LoginResource(self.services_factory, self.portal, authenticator=mock())
35         self.web = DummySite(self.resource)
36
37     def test_children_resources_are_unauthorized_when_not_logged_in(self):
38         request = DummyRequest(['/some_child_resource'])
39
40         d = self.web.get(request)
41
42         def assert_unauthorized_resources(_):
43             self.assertEqual(401, request.responseCode)
44             self.assertEqual("Unauthorized!", request.written[0])
45
46         d.addCallback(assert_unauthorized_resources)
47         return d
48
49     def test_account_recovery_resource_does_not_require_login(self):
50         request = DummyRequest(['account-recovery'])
51         d = self.web.get(request)
52
53         def assert_successful(_):
54             self.assertEqual(200, request.responseCode)
55
56         d.addCallback(assert_successful)
57         return d
58
59     @patch('pixelated.resources.session.PixelatedSession.is_logged_in')
60     def test_there_are_no_grand_children_resources_when_logged_in(self, mock_is_logged_in):
61         request = DummyRequest(['/login/grand_children'])
62         mock_is_logged_in.return_value = True
63         when(self.services_factory).has_session(ANY()).thenReturn(True)
64
65         d = self.web.get(request)
66
67         def assert_resources_not_found(_):
68             self.assertEqual(404, request.responseCode)
69             self.assertIn("No Such Resource", request.written[0])
70
71         d.addCallback(assert_resources_not_found)
72         return d
73
74     def test_get(self):
75         request = DummyRequest([''])
76
77         d = self.web.get(request)
78
79         def assert_login_page_rendered(_):
80             self.assertEqual(200, request.responseCode)
81             title = 'Pixelated - Login'
82             default_disclaimer = 'Some disclaimer'
83             written_response = ''.join(request.written)
84             self.assertIn(title, written_response)
85             self.assertIn(default_disclaimer, written_response)
86
87         d.addCallback(assert_login_page_rendered)
88         return d
89
90     def _write(self, filename, content):
91         with open(filename, 'w') as disclaimer_file:
92             disclaimer_file.write(content)
93
94     def test_override_login_disclaimer_message(self):
95         request = DummyRequest([''])
96
97         banner_file_name = 'banner.txt'
98         banner_disclaimer_content = '<p>some custom disclaimer</p>'
99         self._write(banner_file_name, banner_disclaimer_content)
100
101         self.resource._disclaimer_banner = 'service/_trial_temp/' + banner_file_name
102
103         d = self.web.get(request)
104
105         def assert_custom_disclaimer_rendered(_):
106             self.assertEqual(200, request.responseCode)
107             written_response = ''.join(request.written)
108             self.assertIn(banner_disclaimer_content, written_response)
109
110         def tear_down(_):
111             os.remove(banner_file_name)
112
113         d.addCallback(assert_custom_disclaimer_rendered)
114         d.addCallback(tear_down)
115         return d
116
117     def test_non_xml_compliant_banner_will_send_default_invalid_format_banner(self):
118         request = DummyRequest([''])
119
120         banner_file_name = 'banner.txt'
121         xml_invalid_banner = '<p>some unclosed paragraph'
122         self._write(banner_file_name, xml_invalid_banner)
123
124         self.resource._disclaimer_banner = 'service/_trial_temp/' + banner_file_name
125
126         d = self.web.get(request)
127
128         def assert_default_invalid_banner_disclaimer_rendered(_):
129             self.assertEqual(200, request.responseCode)
130             written_response = ''.join(request.written)
131             self.assertIn("Invalid XML template format for service/_trial_temp/banner.txt.", written_response)
132
133         def tear_down(_):
134             os.remove(banner_file_name)
135
136         d.addCallback(assert_default_invalid_banner_disclaimer_rendered)
137         d.addCallback(tear_down)
138         return d
139
140     def test_wrong_banner_file_location_will_send_default_invalid_format_banner(self):
141         request = DummyRequest([''])
142
143         non_existing_banner_file = 'banner.txt'
144
145         self.resource._disclaimer_banner = non_existing_banner_file
146
147         d = self.web.get(request)
148
149         def assert_default_invalid_banner_disclaimer_rendered(_):
150             self.assertEqual(200, request.responseCode)
151             written_response = ''.join(request.written)
152             self.assertIn("Disclaimer banner file banner.txt could not be read or does not exit.", written_response)
153
154         d.addCallback(assert_default_invalid_banner_disclaimer_rendered)
155         return d
156
157
158 class TestLoginPOST(unittest.TestCase):
159     def setUp(self):
160         self.services_factory = mock()
161         self.provider = mock()
162         self.authenticator = MagicMock()
163         self.resource = LoginResource(self.services_factory, self.provider, authenticator=self.authenticator)
164         self.web = DummySite(self.resource)
165
166         self.request = DummyRequest([''])
167         username = 'ayoyo'
168         self.request.addArg('username', username)
169         password = 'ayoyo_password'
170         self.username = username
171         self.password = password
172         self.request.addArg('password', password)
173         self.request.method = 'POST'
174         user_auth = mock()
175         user_auth.uuid = 'some_user_uuid'
176         self.user_auth = user_auth
177
178     @patch('twisted.web.util.redirectTo')
179     @patch('pixelated.resources.session.PixelatedSession.is_logged_in')
180     def test_should_redirect_to_home_if_user_if_already_logged_in(self, mock_logged_in, mock_redirect):
181         mock_logged_in.return_value = True
182         when(self.services_factory).has_session(ANY()).thenReturn(True)
183         mock_redirect.return_value = "mocked redirection"
184
185         d = self.web.get(self.request)
186
187         def assert_redirected_to_home(_):
188             mock_redirect.assert_called_once_with('/', self.request)
189             self.assertFalse(self.authenticator.authenticate.called)
190
191         d.addCallback(assert_redirected_to_home)
192         return d
193
194     @patch('pixelated.config.leap.BootstrapUserServices.setup')
195     @patch('twisted.web.util.redirectTo')
196     def test_should_redirect_to_login_with_error_flag_when_login_fails(self,
197                                                                        mock_redirect,
198                                                                        mock_user_bootstrap_setup):
199         self.authenticator.authenticate.side_effect = UnauthorizedLogin()
200         mock_redirect.return_value = "mocked redirection"
201
202         d = self.web.get(self.request)
203
204         def assert_redirected_to_login(_):
205             self.authenticator.authenticate.assert_called_once_with(self.username, self.password)
206             mock_redirect.assert_called_once_with('/login?auth-error', self.request)
207             self.assertFalse(mock_user_bootstrap_setup.called)
208             self.assertFalse(self.resource.get_session(self.request).is_logged_in())
209
210         d.addCallback(assert_redirected_to_login)
211         return d
212
213     @patch('pixelated.config.leap.BootstrapUserServices.setup')
214     def test_successful_login_responds_interstitial(self, mock_user_bootstrap_setup):
215         self.authenticator.authenticate.return_value = self.user_auth
216
217         d = self.web.get(self.request)
218
219         def assert_interstitial_in_response(_):
220             self.authenticator.authenticate.assert_called_once_with(self.username, self.password)
221             interstitial_js_in_template = '<script src="/public/interstitial.js"></script>'
222             self.assertIn(interstitial_js_in_template, self.request.written[0])
223
224         d.addCallback(assert_interstitial_in_response)
225         return d
226
227     @patch('pixelated.config.leap.BootstrapUserServices.setup')
228     def test_successful_login_runs_user_services_bootstrap_when_interstitial_loaded(self, mock_user_bootstrap_setup):
229         self.authenticator.authenticate.return_value = self.user_auth
230
231         d = self.web.get(self.request)
232
233         def assert_login_setup_service_for_user(_):
234             mock_user_bootstrap_setup.assert_called_once_with(self.user_auth, self.password, 'en-US')
235
236         d.addCallback(assert_login_setup_service_for_user)
237         return d
238
239     @patch('pixelated.config.leap.BootstrapUserServices.setup')
240     def test_successful_adds_cookies_to_indicate_logged_in_status_when_services_are_loaded(self, mock_user_bootstrap_setup):
241         self.authenticator.authenticate.return_value = self.user_auth
242         irrelevant = None
243         mock_user_bootstrap_setup.return_value = defer.succeed(irrelevant)
244
245         d = self.web.get(self.request)
246
247         def assert_login_setup_service_for_user(_):
248             self.assertTrue(self.resource.get_session(self.request).is_logged_in())
249
250         d.addCallback(assert_login_setup_service_for_user)
251         return d
252
253     @patch('pixelated.resources.session.PixelatedSession.login_started')
254     def test_session_adds_login_started_status_after_authentication(self, mock_login_started):
255         self.authenticator.authenticate.return_value = self.user_auth
256
257         d = self.web.get(self.request)
258
259         def assert_login_started_called(_):
260             mock_login_started.assert_called_once()
261
262         d.addCallback(assert_login_started_called)
263         return d
264
265     @patch('pixelated.resources.session.PixelatedSession.login_successful')
266     @patch('pixelated.config.leap.BootstrapUserServices.setup')
267     def test_session_adds_login_successful_status_when_services_setup_finishes(self, mock_user_bootstrap_setup, mock_login_successful):
268         self.authenticator.authenticate.return_value = self.user_auth
269         mock_user_bootstrap_setup.return_value = defer.succeed(None)
270
271         d = self.web.get(self.request)
272
273         def assert_login_successful_called(_):
274             mock_login_successful.assert_called_once()
275
276         d.addCallback(assert_login_successful_called)
277         return d
278
279     @patch('pixelated.resources.session.PixelatedSession.login_error')
280     @patch('pixelated.config.leap.BootstrapUserServices.setup')
281     def test_session_adds_login_error_status_when_services_setup_gets_error(self, mock_user_bootstrap_setup, mock_login_error):
282         self.authenticator.authenticate.return_value = self.user_auth
283         mock_user_bootstrap_setup.return_value = defer.fail(Exception('Could not setup user services'))
284
285         d = self.web.get(self.request)
286
287         def assert_login_error_called(_):
288             mock_login_error.assert_called_once()
289
290         d.addCallback(assert_login_error_called)
291         return d
292
293
294 class TestLoginStatus(unittest.TestCase):
295     def setUp(self):
296         self.services_factory = mock()
297         self.resource = LoginStatusResource(self.services_factory)
298         self.web = DummySite(self.resource)
299
300         self.request = DummyRequest(['/status'])
301
302     def test_login_status_completed_when_single_user(self):
303         self.services_factory.mode = mock()
304         self.services_factory.mode.is_single_user = True
305         d = self.web.get(self.request)
306
307         def assert_login_completed(_):
308             self.assertIn('completed', self.request.written[0])
309
310         d.addCallback(assert_login_completed)
311         return d
312
313     @patch('pixelated.resources.session.PixelatedSession.check_login_status')
314     def test_login_status_when_multi_user_returns_check_login_status(self, mock_login_status):
315         self.services_factory.mode = mock()
316         self.services_factory.mode.is_single_user = False
317         mock_login_status.return_value = 'started'
318         d = self.web.get(self.request)
319
320         def assert_login_completed(_):
321             self.assertIn('started', self.request.written[0])
322
323         d.addCallback(assert_login_completed)
324         return d