summaryrefslogtreecommitdiff
path: root/src/leap
diff options
context:
space:
mode:
authorKali Kaneko (leap communications) <kali@leap.se>2016-11-24 16:57:56 +0100
committerKali Kaneko (leap communications) <kali@leap.se>2016-12-29 03:09:51 +0100
commite50a442c6f03ba09a800f9999e29e9340b1d45c7 (patch)
treeefa469130162cd53c7cd9ddbaede96929c346109 /src/leap
parent86976a27e7aa9222afc0695c240b0ea7cc8e362b (diff)
[feature] local session service
Diffstat (limited to 'src/leap')
-rw-r--r--src/leap/bitmask/core/_session.py70
-rw-r--r--src/leap/bitmask/core/_web.py46
-rw-r--r--src/leap/bitmask/core/service.py16
3 files changed, 105 insertions, 27 deletions
diff --git a/src/leap/bitmask/core/_session.py b/src/leap/bitmask/core/_session.py
new file mode 100644
index 0000000..24070a8
--- /dev/null
+++ b/src/leap/bitmask/core/_session.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# _session.py
+# Copyright (C) 2016 LEAP Encryption Acess Project
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Service for handling the local sessions.
+"""
+
+import binascii
+import os
+
+from twisted.application import service
+from twisted.logger import Logger
+
+from leap.bitmask.hooks import HookableService
+
+
+logger = Logger()
+
+
+class SessionService(HookableService):
+
+ """
+ This service holds random local-session tokens, that will be use to protect
+ the access to the API resources.
+
+ These tokens are different from the (remote) SRP session tokens: the
+ local-session tokens are ephimeral and generated by the local Bitmask
+ deamon.
+
+ Right now, they are generated when a soledad instance is successfully
+ created. This might be subject to further discussion, but this is the
+ earliest moment in which we can decide if a user should be authenticated
+ locally: it means that the entered password is able to decrypt the local
+ store. In this way, we can protect the API resources even in the case that
+ we don't have connectivity.
+ """
+
+ name = 'sessions'
+
+ def __init__(self, basedir, tokens):
+ service.Service.__init__(self)
+ self._basedir = basedir
+ self._tokens = tokens
+
+ def startService(self):
+ logger.info('starting Session Service')
+ super(SessionService, self).startService()
+
+ def stopService(self):
+ pass
+
+ def hook_on_new_soledad_instance(self, **kw):
+ user = kw['user']
+ session_token = binascii.hexlify(os.urandom(10))
+ print '---------------------------------------------------'
+ print "hook on new soledad instance!", user, session_token
+ self._tokens[user] = session_token
diff --git a/src/leap/bitmask/core/_web.py b/src/leap/bitmask/core/_web.py
index c0c041e..11a7be1 100644
--- a/src/leap/bitmask/core/_web.py
+++ b/src/leap/bitmask/core/_web.py
@@ -64,8 +64,7 @@ class TokenDictChecker:
credentials.IUsernameHashedPassword)
def __init__(self, tokens):
- "tokens: a dict-like object mapping usernames to session-tokens"
- self.tokens = tokens
+ self.tokens = tokens
def requestAvatarId(self, credentials):
username = credentials.username
@@ -87,8 +86,8 @@ class HttpPasswordRealm(object):
self.resource = resource
def requestAvatar(self, user, mind, *interfaces):
+ # the resource is passed on regardless of user
if IResource in interfaces:
- # the resource is passed on regardless of user
return (IResource, self.resource, lambda: None)
raise NotImplementedError()
@@ -123,12 +122,9 @@ class WhitelistHTTPAuthSessionWrapper(HTTPAuthSessionWrapper):
return HTTPAuthSessionWrapper.render(self, request)
-
-def protectedResourceFactory(resource, passwords, whitelist):
+def protectedResourceFactory(resource, session_tokens, whitelist):
realm = HttpPasswordRealm(resource)
- # TODO this should have the per-site tokens.
- # can put it inside the API Resource object.
- checker = PasswordDictChecker(passwords)
+ checker = TokenDictChecker(session_tokens)
resource_portal = portal.Portal(realm, [checker])
credentialFactory = TokenCredentialFactory('localhost')
protected_resource = WhitelistHTTPAuthSessionWrapper(
@@ -155,7 +151,6 @@ class HTTPDispatcherService(service.Service):
'/API/bonafide/user',
)
-
def __init__(self, core, port=7070, debug=False, onion=False):
self._core = core
self.port = port
@@ -178,33 +173,20 @@ class HTTPDispatcherService(service.Service):
'ui', 'app', 'lib', 'bitmask.js')
jsapi = File(os.path.abspath(jspath))
- root = File(webdir)
-
- # TODO move this to the tests...
- DUMMY_PASS = {'user1': 'pass'}
-
api = Api(CommandDispatcher(self._core))
protected_api = protectedResourceFactory(
- api, DUMMY_PASS, self.API_WHITELIST)
- root.putChild(u'API', protected_api)
+ api, self._core.tokens, self.API_WHITELIST)
+ root = File(webdir)
+ root.putChild(u'API', protected_api)
if not HAS_WEB_UI:
root.putChild('bitmask.js', jsapi)
- # TODO --- pass requestFactory for header authentication
- # so that we remove the setting of the cookie.
-
- # http://www.tsheffler.com/blog/2011/09/22/twisted-learning-about-cred-and-basicdigest-authentication/#Digest_Authentication
factory = Site(root)
self.site = factory
- if self.onion:
- try:
- import txtorcon
- except ImportError:
- log.error('onion is enabled, but could not find txtorcon')
- return
- self._start_onion_service(factory)
+ if self.onion and _has_txtorcon():
+ self._start_onion_service(factory)
else:
interface = '127.0.0.1'
endpoint = endpoints.TCP4ServerEndpoint(
@@ -274,3 +256,13 @@ class Api(Resource):
request.setHeader('Content-Type', 'application/json')
request.write(response)
request.finish()
+
+
+def _has_txtorcon():
+ try:
+ import txtorcon
+ txtorcon
+ except ImportError:
+ log.error('onion is enabled, but could not find txtorcon')
+ return False
+ return True
diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py
index cba6f8d..705dcb5 100644
--- a/src/leap/bitmask/core/service.py
+++ b/src/leap/bitmask/core/service.py
@@ -31,6 +31,7 @@ from leap.bitmask.core import configurable
from leap.bitmask.core import _zmq
from leap.bitmask.core import _web
from leap.bitmask.core import flags
+from leap.bitmask.core import _session
from leap.common.events import server as event_server
# from leap.vpn import EIPService
@@ -61,6 +62,7 @@ class BitmaskBackend(configurable.ConfigurableService):
configurable.ConfigurableService.__init__(self, basedir)
self.core_commands = BackendCommands(self)
+ self.tokens = {}
def enabled(service):
return self.get_config('services', service, False, boolean=True)
@@ -69,6 +71,7 @@ class BitmaskBackend(configurable.ConfigurableService):
on_start(self.init_events)
on_start(self.init_bonafide)
+ on_start(self.init_sessions)
if enabled('mail'):
on_start(self._init_mail_services)
@@ -103,6 +106,10 @@ class BitmaskBackend(configurable.ConfigurableService):
bf.register_hook('on_bonafide_auth', listener='mail')
bf.register_hook('on_bonafide_logout', listener='mail')
+ def init_sessions(self):
+ sessions = _session.SessionService(self.basedir, self.tokens)
+ sessions.setServiceParent(self)
+
def _start_child_service(self, name):
logger.debug('starting backend child service: %s' % name)
service = self.getServiceNamed(name)
@@ -138,6 +145,15 @@ class BitmaskBackend(configurable.ConfigurableService):
sol.register_hook(
'on_new_soledad_instance', listener='keymanager')
+ # XXX this might not be the right place for hooking the sessions.
+ # If we want to be offline, we need to authenticate them after
+ # soledad. But this is not valid for the VPN case,
+ # because we have not decided if soledad is required in that case
+ # (seemingly not). If only VPN, then we have to return the token
+ # from the SRP authentication.
+ sol.register_hook(
+ 'on_new_soledad_instance', listener='sessions')
+
def _init_keymanager(self):
service = mail_services.KeymanagerService
km = self._maybe_init_service(