diff options
-rw-r--r-- | service/pixelated/resources/logout_resource.py | 2 | ||||
-rw-r--r-- | service/test/integration/test_logout.py | 6 | ||||
-rw-r--r-- | service/test/support/integration/multi_user_client.py | 9 | ||||
-rw-r--r-- | service/test/unit/resources/test_logout_resources.py | 8 | ||||
-rw-r--r-- | web-ui/app/js/helpers/browser.js | 2 | ||||
-rw-r--r-- | web-ui/app/js/page/logout.js | 13 | ||||
-rw-r--r-- | web-ui/app/scss/_styles.scss | 1 | ||||
-rw-r--r-- | web-ui/app/templates/page/logout.hbs | 7 | ||||
-rw-r--r-- | web-ui/test/spec/page/logout.spec.js | 53 |
9 files changed, 84 insertions, 17 deletions
diff --git a/service/pixelated/resources/logout_resource.py b/service/pixelated/resources/logout_resource.py index fe80316e..344ad2e9 100644 --- a/service/pixelated/resources/logout_resource.py +++ b/service/pixelated/resources/logout_resource.py @@ -8,7 +8,7 @@ class LogoutResource(BaseResource): BASE_URL = "logout" isLeaf = True - def render_GET(self, request): + def render_POST(self, request): session = self.get_session(request) self._services_factory.log_out_user(session.user_uuid) session.expire() diff --git a/service/test/integration/test_logout.py b/service/test/integration/test_logout.py index 52f7e34f..da414126 100644 --- a/service/test/integration/test_logout.py +++ b/service/test/integration/test_logout.py @@ -13,10 +13,11 @@ # # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see <http://www.gnu.org/licenses/>. +import json + from mockito import verify from twisted.internet import defer -from test.support.integration import load_mail_from_file from test.support.integration.multi_user_client import MultiUserClient from test.support.integration.soledad_test_base import SoledadTestBase @@ -34,7 +35,8 @@ class MultiUserLogoutTest(MultiUserClient, SoledadTestBase): yield self.wait_for_session_user_id_to_finish() - response, request = self.get("/logout", as_json=False, from_request=login_request) + response, request = self.post("/logout", json.dumps({'csrftoken': [login_request.getCookie('XSRF-TOKEN')]}), + from_request=login_request, as_json=False) yield response self.assertEqual(302, request.responseCode) # redirected diff --git a/service/test/support/integration/multi_user_client.py b/service/test/support/integration/multi_user_client.py index fa65fb06..5f24456b 100644 --- a/service/test/support/integration/multi_user_client.py +++ b/service/test/support/integration/multi_user_client.py @@ -82,3 +82,12 @@ class MultiUserClient(AppTestClient): session = from_request.getSession() request.session = session return self._render(request, as_json) + + def post(self, path, body='', headers=None, ajax=True, csrf='token', as_json=True, from_request=None): + headers = headers or {'Content-Type': 'application/json'} + request = request_mock(path=path, method="POST", body=body, headers=headers, ajax=ajax, csrf=csrf) + + if from_request: + session = from_request.getSession() + request.session = session + return self._render(request, as_json) diff --git a/service/test/unit/resources/test_logout_resources.py b/service/test/unit/resources/test_logout_resources.py index 48cf9db9..6246eeb9 100644 --- a/service/test/unit/resources/test_logout_resources.py +++ b/service/test/unit/resources/test_logout_resources.py @@ -1,6 +1,7 @@ from mock import patch from mockito import mock, verify from twisted.trial import unittest +from twisted.web.error import UnsupportedMethod from twisted.web.test.requesthelper import DummyRequest from pixelated.resources.logout_resource import LogoutResource @@ -16,6 +17,7 @@ class TestLogoutResource(unittest.TestCase): @patch('twisted.web.util.redirectTo') def test_logout(self, mock_redirect): request = DummyRequest(['/logout']) + request.method = 'POST' mock_redirect.return_value = 'haha' @@ -29,3 +31,9 @@ class TestLogoutResource(unittest.TestCase): d.addCallback(expire_session_and_redirect) return d + + def test_get_is_not_supported_for_logout(self): + request = DummyRequest(['/logout']) + request.method = 'GET' + + self.assertRaises(UnsupportedMethod, self.web.get, request) diff --git a/web-ui/app/js/helpers/browser.js b/web-ui/app/js/helpers/browser.js index 23aa79c8..dacf2263 100644 --- a/web-ui/app/js/helpers/browser.js +++ b/web-ui/app/js/helpers/browser.js @@ -26,7 +26,7 @@ define([], function () { function getCookie(name) { var value = '; ' + document.cookie; var parts = value.split('; ' + name + '='); - if (parts.length === 2) return parts.pop().split(';').shift(); + if (parts.length === 2) { return parts.pop().split(';').shift(); } } return { diff --git a/web-ui/app/js/page/logout.js b/web-ui/app/js/page/logout.js index d881f6c2..81b57db2 100644 --- a/web-ui/app/js/page/logout.js +++ b/web-ui/app/js/page/logout.js @@ -14,19 +14,28 @@ * You should have received a copy of the GNU Affero General Public License * along with Pixelated. If not, see <http://www.gnu.org/licenses/>. */ -define(['flight/lib/component', 'features', 'views/templates'], function (defineComponent, features, templates) { +define(['flight/lib/component', 'features', 'views/templates', 'helpers/browser'], + function (defineComponent, features, templates, browser) { 'use strict'; return defineComponent(function () { + this.defaultAttrs({form: '#logout-form'}); + this.render = function () { - var logoutHTML = templates.page.logout({ logout_url: features.getLogoutUrl() }); + var logoutHTML = templates.page.logout({ logout_url: features.getLogoutUrl(), + csrf_token: browser.getCookie('XSRF-TOKEN')}); this.$node.html(logoutHTML); }; + this.logout = function(){ + this.select('form').submit(); + }; + this.after('initialize', function () { if (features.isLogoutEnabled()) { this.render(); + this.on(this.$node, 'click', this.logout); } }); diff --git a/web-ui/app/scss/_styles.scss b/web-ui/app/scss/_styles.scss index a7d6dedf..63f15f6a 100644 --- a/web-ui/app/scss/_styles.scss +++ b/web-ui/app/scss/_styles.scss @@ -8,6 +8,7 @@ #logout { color: $white; + cursor: pointer; } .search-highlight { diff --git a/web-ui/app/templates/page/logout.hbs b/web-ui/app/templates/page/logout.hbs index dd931274..3768d24f 100644 --- a/web-ui/app/templates/page/logout.hbs +++ b/web-ui/app/templates/page/logout.hbs @@ -1,8 +1,9 @@ <ul id="logout"> - <a title="logout" href={{logout_url}}> - <li> + <form id="logout-form" method="POST" action="{{ logout_url }}"> + <input type="hidden" name="csrftoken" value="{{ csrf_token }}" /> + <li> <div class="fa fa-sign-out"></div> <i class="shortcut-label"></i> Logout </li> - </a> + </form> </ul> diff --git a/web-ui/test/spec/page/logout.spec.js b/web-ui/test/spec/page/logout.spec.js index 7e384cad..a8b882b0 100644 --- a/web-ui/test/spec/page/logout.spec.js +++ b/web-ui/test/spec/page/logout.spec.js @@ -8,26 +8,48 @@ describeComponent('page/logout', function () { features = require('features'); }); - it('should provide logout link if logout is enabled', function () { + it('should provide logout form if logout is enabled', function () { spyOn(features, 'isLogoutEnabled').and.returnValue(true); this.setupComponent('<nav id="logout"></nav>', {}); - var logout_link = this.component.$node.find('a')[0]; - expect(logout_link).toExist(); - expect(logout_link.href).toMatch('test/logout/url'); + var logout_form = this.component.$node.find('form')[0]; + expect(logout_form).toExist(); + expect(logout_form.action).toMatch('test/logout/url'); + expect(logout_form.method).toMatch('POST'); }); - it('should not provide logout link if disabled', function() { + it('should not provide logout form if logout is disabled', function () { spyOn(features, 'isLogoutEnabled').and.returnValue(false); this.setupComponent('<nav id="logout"></nav>', {}); - var logout_link = this.component.$node.find('a')[0]; - expect(logout_link).not.toExist(); + var logout_form = this.component.$node.find('form')[0]; + expect(logout_form).not.toExist(); }); - it('should render logout in collapsed nav bar if logout is enabled', function() { + it('should provide csrf token if logout is enabled', function () { + spyOn(features, 'isLogoutEnabled').and.returnValue(true); + document.cookie = 'XSRF-TOKEN=ff895ffc45a4ce140bfc5dda6c61d232; i18next=en-us'; + + this.setupComponent('<nav id="logout"></nav>', {}); + + var logout_input = this.component.$node.find('input')[0]; + expect(logout_input).toExist(); + expect(logout_input.value).toEqual('ff895ffc45a4ce140bfc5dda6c61d232'); + expect(logout_input.type).toEqual('hidden'); + }); + + it('should not provide csrf token if logout is disabled', function () { + spyOn(features, 'isLogoutEnabled').and.returnValue(false); + + this.setupComponent('<nav id="logout"></nav>', {}); + + var logout_input = this.component.$node.find('input')[0]; + expect(logout_input).not.toExist(); + }); + + xit('should render logout in collapsed nav bar if logout is enabled', function() { spyOn(features, 'isLogoutEnabled').and.returnValue(true); this.setupComponent('<ul id="logout-shortcuts" class="shortcuts"></ul>', {}); @@ -36,6 +58,21 @@ describeComponent('page/logout', function () { expect(logout_icon).toExist(); expect(logout_icon.innerHTML).toContain('<div class="fa fa-sign-out"></div>'); }); + + it('should submit logout form if logout is enabled', function () { + spyOn(features, 'isLogoutEnabled').and.returnValue(true); + + this.setupComponent('<nav id="logout"></nav>', {}); + + var logout_form = this.component.$node.find('form')[0]; + spyOn(logout_form, 'submit'); + + this.component.$node.click(); + + expect(logout_form.submit).toHaveBeenCalled(); + }); + + }); }); |