From 9573bdca55ddc5488066d3af525e41ed1d872ea6 Mon Sep 17 00:00:00 2001 From: NavaL Date: Wed, 24 Feb 2016 16:33:20 +0100 Subject: Backend and frontend protection against csrf attacks: - root resources changes the csrf token cookie everytime it is loaded, in particular during the intestitial load during login - it will also add that cookie on single user mode - initialize will still load all resources - but they you cant access them if the csrf token do not match - all ajax calls needs to add the token to the header - non ajax get requests do not need xsrf token validation - non ajax post will have to send the token in as a form input or in the content Issue #612 --- web-ui/app/js/helpers/browser.js | 9 +++++++- web-ui/app/js/helpers/monitored_ajax.js | 10 +++++---- web-ui/app/js/services/mail_service.js | 2 +- web-ui/test/spec/helpers/browser.spec.js | 12 +++++++++++ .../test/spec/helpers/monitored_ajax_call.spec.js | 24 +++++++++++++++++++--- 5 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 web-ui/test/spec/helpers/browser.spec.js (limited to 'web-ui') diff --git a/web-ui/app/js/helpers/browser.js b/web-ui/app/js/helpers/browser.js index e5be6667..23aa79c8 100644 --- a/web-ui/app/js/helpers/browser.js +++ b/web-ui/app/js/helpers/browser.js @@ -23,7 +23,14 @@ define([], function () { window.location.replace(url); } + function getCookie(name) { + var value = '; ' + document.cookie; + var parts = value.split('; ' + name + '='); + if (parts.length === 2) return parts.pop().split(';').shift(); + } + return { - redirect: redirect + redirect: redirect, + getCookie: getCookie }; }); diff --git a/web-ui/app/js/helpers/monitored_ajax.js b/web-ui/app/js/helpers/monitored_ajax.js index 1cb720de..dc182d58 100644 --- a/web-ui/app/js/helpers/monitored_ajax.js +++ b/web-ui/app/js/helpers/monitored_ajax.js @@ -36,6 +36,8 @@ define(['page/events', 'views/i18n', 'helpers/browser'], function (events, i18n, } }; + config.headers = {'X-XSRF-TOKEN': browser.getCookie('XSRF-TOKEN')}; + var originalComplete = config.complete; config.complete = function () { if (originalComplete) { @@ -46,15 +48,15 @@ define(['page/events', 'views/i18n', 'helpers/browser'], function (events, i18n, return $.ajax(url, config).fail(function (xmlhttprequest, textstatus, message) { if (!config.skipErrorMessage) { var msg = (xmlhttprequest.responseJSON && xmlhttprequest.responseJSON.message) || - messages[textstatus] || - 'unexpected problem while talking to server'; - on.trigger(document, events.ui.userAlerts.displayMessage, {message: i18n(msg)}); + messages[textstatus] || + 'unexpected problem while talking to server'; + on.trigger(document, events.ui.userAlerts.displayMessage, {message: i18n(msg), class: 'error'}); } if (xmlhttprequest.status === 302) { var redirectUrl = xmlhttprequest.getResponseHeader('Location'); browser.redirect(redirectUrl); - }else if (xmlhttprequest.status === 401) { + } else if (xmlhttprequest.status === 401) { browser.redirect('/'); } diff --git a/web-ui/app/js/services/mail_service.js b/web-ui/app/js/services/mail_service.js index a63d517e..412451cb 100644 --- a/web-ui/app/js/services/mail_service.js +++ b/web-ui/app/js/services/mail_service.js @@ -246,7 +246,7 @@ define( this.trigger(document, events.mails.available, _.merge({tag: this.attr.currentTag, forSearch: this.attr.lastQuery }, this.parseMails(data))); }.bind(this)) .fail(function () { - this.trigger(document, events.ui.userAlerts.displayMessage, { message: i18n('Could not fetch messages') }); + this.trigger(document, events.ui.userAlerts.displayMessage, { message: i18n('Could not fetch messages'), class: 'error' }); }.bind(this)); }; diff --git a/web-ui/test/spec/helpers/browser.spec.js b/web-ui/test/spec/helpers/browser.spec.js new file mode 100644 index 00000000..5b740da8 --- /dev/null +++ b/web-ui/test/spec/helpers/browser.spec.js @@ -0,0 +1,12 @@ +define(['helpers/browser'], function (browser) { + 'use strict'; + + describe('browser ', function() { + it('gets cookie', function() { + document.cookie = 'TWISTED_SESSION=ff895ffc45a4ce140bfc5dda6c61d232; i18next=en-us'; + expect(browser.getCookie('TWISTED_SESSION')).toEqual('ff895ffc45a4ce140bfc5dda6c61d232'); + expect(browser.getCookie('i18next')).toEqual('en-us'); + }); + + }); +}); diff --git a/web-ui/test/spec/helpers/monitored_ajax_call.spec.js b/web-ui/test/spec/helpers/monitored_ajax_call.spec.js index 972ca3ae..c0d55198 100644 --- a/web-ui/test/spec/helpers/monitored_ajax_call.spec.js +++ b/web-ui/test/spec/helpers/monitored_ajax_call.spec.js @@ -1,6 +1,24 @@ define(['helpers/monitored_ajax'], function (monitoredAjax) { 'use strict'; describe('monitoredAjaxCall', function () { + + describe('default configs', function () { + + it('should always attach the xsrf token in the header', function () { + var component = { trigger: function () {}}; + var d = $.Deferred(); + spyOn($, 'ajax').and.returnValue(d); + document.cookie = 'XSRF-TOKEN=ff895ffc45a4ce140bfc5dda6c61d232; i18next=en-us'; + var anyUrl = '/'; + + monitoredAjax(component, anyUrl, {}); + + expect($.ajax.calls.mostRecent().args[1].headers).toEqual({ 'X-XSRF-TOKEN' : 'ff895ffc45a4ce140bfc5dda6c61d232' }); + + }); + + }); + describe('when dealing with errors', function () { _.each( @@ -19,7 +37,7 @@ define(['helpers/monitored_ajax'], function (monitoredAjax) { d.reject({ responseJSON: {}}, errorType, ''); expect(component.trigger).toHaveBeenCalledWith(document, Pixelated.events.ui.userAlerts.displayMessage, - { message: errorMessage }); + { message: errorMessage, class: 'error' }); }); }); @@ -33,7 +51,7 @@ define(['helpers/monitored_ajax'], function (monitoredAjax) { d.reject({ responseJSON: { message: 'Server Message'}}, 'error', ''); expect(component.trigger).toHaveBeenCalledWith(document, Pixelated.events.ui.userAlerts.displayMessage, - { message: 'Server Message' }); + { message: 'Server Message', class: 'error' }); }); }); @@ -76,4 +94,4 @@ define(['helpers/monitored_ajax'], function (monitoredAjax) { }); }); -}); \ No newline at end of file +}); -- cgit v1.2.3