summaryrefslogtreecommitdiff
path: root/service/pixelated
diff options
context:
space:
mode:
authorNavaL <ayoyo@thoughtworks.com>2016-02-24 16:33:20 +0100
committerNavaL <mnandri@thoughtworks.com>2016-02-25 09:17:53 +0100
commit9573bdca55ddc5488066d3af525e41ed1d872ea6 (patch)
tree228ca246c306bd44faa37c01e52c6d7aefec1531 /service/pixelated
parentb79035b83e81e4fd654b587426083c6033e695ad (diff)
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
Diffstat (limited to 'service/pixelated')
-rw-r--r--service/pixelated/resources/__init__.py4
-rw-r--r--service/pixelated/resources/login_resource.py3
-rw-r--r--service/pixelated/resources/root_resource.py69
3 files changed, 57 insertions, 19 deletions
diff --git a/service/pixelated/resources/__init__.py b/service/pixelated/resources/__init__.py
index 14ecac86..469c8bc8 100644
--- a/service/pixelated/resources/__init__.py
+++ b/service/pixelated/resources/__init__.py
@@ -99,3 +99,7 @@ class UnAuthorizedResource(Resource):
def render_GET(self, request):
request.setResponseCode(UNAUTHORIZED)
return "Unauthorized!"
+
+ def render_POST(self, request):
+ request.setResponseCode(UNAUTHORIZED)
+ return "Unauthorized!"
diff --git a/service/pixelated/resources/login_resource.py b/service/pixelated/resources/login_resource.py
index f1d9c1e3..aca266cf 100644
--- a/service/pixelated/resources/login_resource.py
+++ b/service/pixelated/resources/login_resource.py
@@ -20,14 +20,13 @@ from xml.sax import SAXParseException
from twisted.cred import credentials
from twisted.internet import defer
+from twisted.python.filepath import FilePath
from twisted.web import util
-from twisted.web.error import FlattenerError
from twisted.web.http import UNAUTHORIZED, OK
from twisted.web.resource import IResource, NoResource
from twisted.web.server import NOT_DONE_YET
from twisted.web.static import File
from twisted.web.template import Element, XMLFile, renderElement, renderer
-from twisted.python.filepath import FilePath
from pixelated.adapter.welcome_mail import add_welcome_mail
from pixelated.resources import BaseResource, UnAuthorizedResource, IPixelatedSession
diff --git a/service/pixelated/resources/root_resource.py b/service/pixelated/resources/root_resource.py
index 6e619951..2c32ab0c 100644
--- a/service/pixelated/resources/root_resource.py
+++ b/service/pixelated/resources/root_resource.py
@@ -13,11 +13,12 @@
#
# 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 hashlib
+import json
import os
from string import Template
-from pixelated.resources import BaseResource
+from pixelated.resources import BaseResource, UnAuthorizedResource
from pixelated.resources.attachments_resource import AttachmentsResource
from pixelated.resources.contacts_resource import ContactsResource
from pixelated.resources.features_resource import FeaturesResource
@@ -29,22 +30,22 @@ from pixelated.resources.mail_resource import MailResource
from pixelated.resources.mails_resource import MailsResource
from pixelated.resources.tags_resource import TagsResource
from pixelated.resources.keys_resource import KeysResource
-from twisted.web.resource import Resource
from twisted.web.static import File
+CSRF_TOKEN_LENGTH = 32
MODE_STARTUP = 1
MODE_RUNNING = 2
class RootResource(BaseResource):
-
def __init__(self, services_factory):
BaseResource.__init__(self, services_factory)
self._startup_assets_folder = self._get_startup_folder()
self._static_folder = self._get_static_folder()
self._html_template = open(os.path.join(self._static_folder, 'index.html')).read()
self._services_factory = services_factory
+ self.child_resources = ChildResourcesMap()
self._startup_mode()
def _startup_mode(self):
@@ -54,21 +55,39 @@ class RootResource(BaseResource):
def getChild(self, path, request):
if path == '':
return self
- return Resource.getChild(self, path, request)
+ if self._is_xsrf_valid(request):
+ return self.child_resources.get(path)
+ return UnAuthorizedResource()
+
+ def _is_xsrf_valid(self, request):
+ xsrf_token = request.getCookie('XSRF-TOKEN')
+
+ ajax_request = (request.getHeader('x-requested-with') == 'XMLHttpRequest')
+ if ajax_request:
+ xsrf_header = request.getHeader('x-xsrf-token')
+ return xsrf_header and xsrf_header == xsrf_token
+
+ get_request = (request.method == 'GET')
+ if get_request:
+ return True
+
+ csrf_input = request.args.get('csrftoken', [None])[0] or json.loads(request.content.read()).get('csrftoken', [None])[0]
+ return csrf_input and csrf_input == xsrf_token
def initialize(self, portal=None, disclaimer_banner=None):
- self.putChild('assets', File(self._static_folder))
- self.putChild('keys', KeysResource(self._services_factory))
- self.putChild(AttachmentsResource.BASE_URL, AttachmentsResource(self._services_factory))
- self.putChild('contacts', ContactsResource(self._services_factory))
- self.putChild('features', FeaturesResource(portal))
- self.putChild('tags', TagsResource(self._services_factory))
- self.putChild('mails', MailsResource(self._services_factory))
- self.putChild('mail', MailResource(self._services_factory))
- self.putChild('feedback', FeedbackResource(self._services_factory))
- self.putChild('user-settings', UserSettingsResource(self._services_factory))
- self.putChild(LoginResource.BASE_URL, LoginResource(self._services_factory, portal, disclaimer_banner=disclaimer_banner))
- self.putChild(LogoutResource.BASE_URL, LogoutResource(self._services_factory))
+ self.child_resources.add('assets', File(self._static_folder))
+ self.child_resources.add('keys', KeysResource(self._services_factory))
+ self.child_resources.add(AttachmentsResource.BASE_URL, AttachmentsResource(self._services_factory))
+ self.child_resources.add('contacts', ContactsResource(self._services_factory))
+ self.child_resources.add('features', FeaturesResource(portal))
+ self.child_resources.add('tags', TagsResource(self._services_factory))
+ self.child_resources.add('mails', MailsResource(self._services_factory))
+ self.child_resources.add('mail', MailResource(self._services_factory))
+ self.child_resources.add('feedback', FeedbackResource(self._services_factory))
+ self.child_resources.add('user-settings', UserSettingsResource(self._services_factory))
+ self.child_resources.add(LoginResource.BASE_URL,
+ LoginResource(self._services_factory, portal, disclaimer_banner=disclaimer_banner))
+ self.child_resources.add(LogoutResource.BASE_URL, LogoutResource(self._services_factory))
self._mode = MODE_RUNNING
@@ -89,10 +108,26 @@ class RootResource(BaseResource):
def _is_starting(self):
return self._mode == MODE_STARTUP
+ def _add_csrf_cookie(self, request):
+ csrf_token = hashlib.sha256(os.urandom(CSRF_TOKEN_LENGTH)).hexdigest()
+ request.addCookie('XSRF-TOKEN', csrf_token)
+
def render_GET(self, request):
+ self._add_csrf_cookie(request)
if self._is_starting():
return open(os.path.join(self._startup_assets_folder, 'Interstitial.html')).read()
else:
account_email = self.mail_service(request).account_email
response = Template(self._html_template).safe_substitute(account_email=account_email)
return str(response)
+
+
+class ChildResourcesMap(object):
+ def __init__(self):
+ self._registry = {}
+
+ def add(self, path, resource):
+ self._registry[path] = resource
+
+ def get(self, path):
+ return self._registry.get(path)