summaryrefslogtreecommitdiff
path: root/service/pixelated
diff options
context:
space:
mode:
authorJon Newson <jon_newson@ieee.org>2016-02-26 16:20:59 +1100
committerJon Newson <jon_newson@ieee.org>2016-02-26 16:20:59 +1100
commit05f4e2ca2d64eaba23c87df4d2e2cc9e09bba6de (patch)
tree50b2ccf6454f31a3f6bceaa997a5e2abbcb91a80 /service/pixelated
parent52467b9aef76c9aac2f250478befd3afb7b6aabd (diff)
parentdbb434b56e6b161a3b851ae6a81f96dff14a29da (diff)
Merge branch 'master' of https://github.com/pixelated/pixelated-user-agent
# By Felix Hammerl (5) and others # Via NavaL * 'master' of https://github.com/pixelated/pixelated-user-agent: serving the client directly, as the current dependency on proxy strips out xsrf cookies -fixing functional test only adding feature resource in root_resource test -- fixing build changed logout to post Issue #612 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 Consolidate stylesheets Remove unused font and stylesheetgit s Create a new deferred for all IMAPAccount calls Clean up jshintrc Recreate session on soledad problems issue #617: Remove old html whitelister Issue #617: Sanitize received content
Diffstat (limited to 'service/pixelated')
-rw-r--r--service/pixelated/bitmask_libraries/session.py24
-rw-r--r--service/pixelated/config/leap.py31
-rw-r--r--service/pixelated/resources/__init__.py4
-rw-r--r--service/pixelated/resources/auth.py1
-rw-r--r--service/pixelated/resources/login_resource.py3
-rw-r--r--service/pixelated/resources/logout_resource.py2
-rw-r--r--service/pixelated/resources/root_resource.py69
7 files changed, 99 insertions, 35 deletions
diff --git a/service/pixelated/bitmask_libraries/session.py b/service/pixelated/bitmask_libraries/session.py
index f28d9f59..ae3eb992 100644
--- a/service/pixelated/bitmask_libraries/session.py
+++ b/service/pixelated/bitmask_libraries/session.py
@@ -53,18 +53,19 @@ class LeapSession(object):
self.fresh_account = False
self.incoming_mail_fetcher = None
self.account = None
- self._has_been_synced = False
+ self._has_been_initially_synced = False
self._sem_intial_sync = defer.DeferredLock()
+ self._is_closed = False
register(events.KEYMANAGER_FINISHED_KEY_GENERATION, self._set_fresh_account, uid=self.account_email())
@defer.inlineCallbacks
def initial_sync(self):
yield self._sem_intial_sync.acquire()
try:
- if not self._has_been_synced:
- yield self.sync()
+ yield self.sync()
+ if not self._has_been_initially_synced:
yield self.after_first_sync()
- self._has_been_synced = True
+ self._has_been_initially_synced = True
finally:
yield self._sem_intial_sync.release()
defer.returnValue(self)
@@ -81,8 +82,7 @@ class LeapSession(object):
reactor.callFromThread(self.incoming_mail_fetcher.startService)
def _create_account(self, user_mail, soledad):
- account = IMAPAccount(user_mail, soledad)
- return account
+ return IMAPAccount(user_mail, soledad, defer.Deferred())
def _set_fresh_account(self, event, email_address):
log.debug('Key for email %s has been generated' % email_address)
@@ -94,12 +94,17 @@ class LeapSession(object):
return self.provider.address_for(name)
def close(self):
+ self._is_closed = True
self.stop_background_jobs()
unregister(events.KEYMANAGER_FINISHED_KEY_GENERATION, uid=self.account_email())
self.soledad.close()
self.remove_from_cache()
self._close_account()
+ @property
+ def is_closed(self):
+ return self._is_closed
+
def _close_account(self):
if self.account:
self.account.end_session()
@@ -284,7 +289,12 @@ class SessionCache(object):
@staticmethod
def lookup_session(key):
- return SessionCache.sessions.get(key, None)
+ session = SessionCache.sessions.get(key, None)
+ if session is not None and session.is_closed:
+ SessionCache.remove_session(key)
+ return None
+ else:
+ return session
@staticmethod
def remember_session(key, session):
diff --git a/service/pixelated/config/leap.py b/service/pixelated/config/leap.py
index a8666086..17a69406 100644
--- a/service/pixelated/config/leap.py
+++ b/service/pixelated/config/leap.py
@@ -1,14 +1,13 @@
from __future__ import absolute_import
-from leap.common.events import (server as events_server,
- register, catalog as events)
+from leap.common.events import (server as events_server)
+from leap.soledad.common.errors import InvalidAuthTokenError
+
from pixelated.config import credentials
from pixelated.bitmask_libraries.config import LeapConfig
from pixelated.bitmask_libraries.certs import LeapCertificate
from pixelated.bitmask_libraries.provider import LeapProvider
from pixelated.bitmask_libraries.session import LeapSessionFactory
from twisted.internet import defer
-import os
-import logging
import logging
log = logging.getLogger(__name__)
@@ -39,11 +38,29 @@ def initialize_leap_multi_user(provider_hostname,
defer.returnValue((config, provider))
+def _create_session(provider, username, password, auth):
+ return LeapSessionFactory(provider).create(username, password, auth)
+
+
+def _force_close_session(session):
+ try:
+ session.close()
+ except Exception, e:
+ log.error(e)
+
+
@defer.inlineCallbacks
def authenticate_user(provider, username, password, initial_sync=True, auth=None):
- leap_session = LeapSessionFactory(provider).create(username, password, auth)
- if initial_sync:
- yield leap_session.initial_sync()
+ leap_session = _create_session(provider, username, password, auth)
+ try:
+ if initial_sync:
+ yield leap_session.initial_sync()
+ except InvalidAuthTokenError:
+ _force_close_session(leap_session)
+
+ leap_session = _create_session(provider, username, password, auth)
+ if initial_sync:
+ yield leap_session.initial_sync()
defer.returnValue(leap_session)
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/auth.py b/service/pixelated/resources/auth.py
index 02729a01..a6ab5396 100644
--- a/service/pixelated/resources/auth.py
+++ b/service/pixelated/resources/auth.py
@@ -41,7 +41,6 @@ log = logging.getLogger(__name__)
class LeapPasswordChecker(object):
credentialInterfaces = (
credentials.IUsernamePassword,
- credentials.IUsernameHashedPassword
)
def __init__(self, leap_provider):
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/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/pixelated/resources/root_resource.py b/service/pixelated/resources/root_resource.py
index 6e619951..86435d89 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)