diff options
| -rw-r--r-- | src/leap/bitmask/core/_session.py | 70 | ||||
| -rw-r--r-- | src/leap/bitmask/core/_web.py | 46 | ||||
| -rw-r--r-- | src/leap/bitmask/core/service.py | 16 | 
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( | 
