diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/bitmask/core/_web.py | 121 | ||||
| -rw-r--r-- | src/leap/bitmask/core/dispatcher.py | 3 | ||||
| -rw-r--r-- | src/leap/bitmask/core/service.py | 8 | 
3 files changed, 121 insertions, 11 deletions
| diff --git a/src/leap/bitmask/core/_web.py b/src/leap/bitmask/core/_web.py index 1cbba19..c0c041e 100644 --- a/src/leap/bitmask/core/_web.py +++ b/src/leap/bitmask/core/_web.py @@ -23,14 +23,18 @@ import json  import os  import pkg_resources -from twisted.internet import reactor  from twisted.application import service  from twisted.internet import endpoints -from twisted.web.resource import Resource +from twisted.cred import portal, checkers, credentials, error as credError +from twisted.internet import reactor, defer +from twisted.logger import Logger +from twisted.web.guard import HTTPAuthSessionWrapper, BasicCredentialFactory +from twisted.web.resource import IResource, Resource  from twisted.web.server import Site, NOT_DONE_YET  from twisted.web.static import File -from twisted.logger import Logger + +from zope.interface import implementer  from leap.bitmask.util import here  from leap.bitmask.core.dispatcher import CommandDispatcher @@ -49,6 +53,90 @@ except Exception:  log = Logger() +class TokenCredentialFactory(BasicCredentialFactory): +    scheme = 'token' + + +@implementer(checkers.ICredentialsChecker) +class TokenDictChecker: + +    credentialInterfaces = (credentials.IUsernamePassword, +                            credentials.IUsernameHashedPassword) + +    def __init__(self, tokens): +        "tokens: a dict-like object mapping usernames to session-tokens" +        self.tokens = tokens  + +    def requestAvatarId(self, credentials): +        username = credentials.username +        if username in self.tokens: +            if credentials.checkPassword(self.tokens[username]): +                return defer.succeed(username) +            else: +                return defer.fail( +                    credError.UnauthorizedLogin("Bad session token")) +        else: +            return defer.fail( +                credError.UnauthorizedLogin("No such user")) + + +@implementer(portal.IRealm) +class HttpPasswordRealm(object): + +    def __init__(self, resource): +        self.resource = resource + +    def requestAvatar(self, user, mind, *interfaces): +        if IResource in interfaces: +            # the resource is passed on regardless of user +            return (IResource, self.resource, lambda: None) +        raise NotImplementedError() + + +@implementer(IResource) +class WhitelistHTTPAuthSessionWrapper(HTTPAuthSessionWrapper): + +    """ +    Wrap a portal, enforcing supported header-based authentication schemes. +    It doesn't apply the enforcement to routes included in a whitelist. +    """ + +    # TODO extend this to inspect the data -- so that we pass a tuple +    # with the action + +    whitelist = (None,) + +    def __init__(self, *args, **kw): +        self.whitelist = kw.pop('whitelist', tuple()) +        super(WhitelistHTTPAuthSessionWrapper, self).__init__( +            *args, **kw) + +    def getChildWithDefault(self, path, request): +        if request.path in self.whitelist: +            return self +        return HTTPAuthSessionWrapper.getChildWithDefault(self, path, request) + +    def render(self, request): +        if request.path in self.whitelist: +            _res = self._portal.realm.resource +            return _res.render(request) +        return HTTPAuthSessionWrapper.render(self, request) + + + +def protectedResourceFactory(resource, passwords, whitelist): +    realm = HttpPasswordRealm(resource) +    # TODO this should have the per-site tokens. +    # can put it inside the API Resource object. +    checker = PasswordDictChecker(passwords) +    resource_portal = portal.Portal(realm, [checker]) +    credentialFactory = TokenCredentialFactory('localhost') +    protected_resource = WhitelistHTTPAuthSessionWrapper( +        resource_portal, [credentialFactory], +        whitelist=whitelist) +    return protected_resource + +  class HTTPDispatcherService(service.Service):      """ @@ -58,11 +146,16 @@ class HTTPDispatcherService(service.Service):      If the package ``leap.bitmask_js`` is found in the import path, we'll serve      the whole JS UI in the root resource too (under the ``public`` path). -     +      If that package cannot be found, we'll serve just the javascript wrapper      around the REST API.      """ +    API_WHITELIST = ( +        '/API/bonafide/user', +    ) + +      def __init__(self, core, port=7070, debug=False, onion=False):          self._core = core          self.port = port @@ -71,6 +164,7 @@ class HTTPDispatcherService(service.Service):          self.uri = ''      def startService(self): +        # TODO refactor this, too long----------------------------------------          if HAS_WEB_UI:              webdir = os.path.abspath(                  pkg_resources.resource_filename('leap.bitmask_js', 'public')) @@ -83,18 +177,27 @@ class HTTPDispatcherService(service.Service):                  here(), '..', '..', '..',                  '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)) -        root.putChild(u'API', api) +        protected_api = protectedResourceFactory( +            api, DUMMY_PASS, self.API_WHITELIST) +        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 not HAS_WEB_UI: -            root.putChild('bitmask.js', jsapi) -          if self.onion:              try:                  import txtorcon @@ -102,13 +205,13 @@ class HTTPDispatcherService(service.Service):                  log.error('onion is enabled, but could not find txtorcon')                  return              self._start_onion_service(factory) -          else:              interface = '127.0.0.1'              endpoint = endpoints.TCP4ServerEndpoint(                  reactor, self.port, interface=interface)              self.uri = 'https://%s:%s' % (interface, self.port)              endpoint.listen(factory) +          # TODO this should be set in a callback to the listen call          self.running = True diff --git a/src/leap/bitmask/core/dispatcher.py b/src/leap/bitmask/core/dispatcher.py index 11a319f..5900390 100644 --- a/src/leap/bitmask/core/dispatcher.py +++ b/src/leap/bitmask/core/dispatcher.py @@ -43,6 +43,7 @@ class SubCommand(object):      __metaclass__ = APICommand      def dispatch(self, service, *parts, **kw): +        subcmd = ''          try:              subcmd = parts[1]              _method = getattr(self, 'do_' + subcmd.upper(), None) @@ -402,7 +403,7 @@ class CommandDispatcher(object):          return d      def do_WEBUI(self, *parts): -        subcmd = parts[1] +        subcmd = parts[1] if parts and len(parts) > 1 else None          dispatch = self.subcommand_webui.dispatch          if subcmd == 'enable': diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index 9fde788..cba6f8d 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -51,6 +51,12 @@ else:  class BitmaskBackend(configurable.ConfigurableService): +    """ +    The Bitmask Core Backend Service. +    Here is where the multiple service tree gets composed. +    This is passed to the command dispatcher. +    """ +      def __init__(self, basedir=configurable.DEFAULT_BASEDIR):          configurable.ConfigurableService.__init__(self, basedir) @@ -217,7 +223,7 @@ class BitmaskBackend(configurable.ConfigurableService):  class BackendCommands(object):      """ -    General commands for the BitmaskBackend Core Service. +    General Commands for the BitmaskBackend Core Service.      """      def __init__(self, core): | 
