diff options
author | Kali Kaneko (leap communications) <kali@leap.se> | 2016-12-07 20:46:02 +0100 |
---|---|---|
committer | Kali Kaneko (leap communications) <kali@leap.se> | 2016-12-29 03:09:52 +0100 |
commit | 7c588e919e959f32b33235b4a44da257d8f4a964 (patch) | |
tree | 91db85ddbffd6989d14f7843e6ec5dd004935893 | |
parent | e50a442c6f03ba09a800f9999e29e9340b1d45c7 (diff) |
[refactor] move web service to its own submodule
-rw-r--r-- | src/leap/bitmask/core/service.py | 4 | ||||
-rw-r--r-- | src/leap/bitmask/core/web/__init__.py | 12 | ||||
-rw-r--r-- | src/leap/bitmask/core/web/_auth.py | 86 | ||||
-rw-r--r-- | src/leap/bitmask/core/web/api.py | 32 | ||||
-rw-r--r-- | src/leap/bitmask/core/web/service.py (renamed from src/leap/bitmask/core/_web.py) | 142 | ||||
-rw-r--r-- | src/leap/bitmask/core/web/static/README (renamed from src/leap/bitmask/core/web/README) | 0 | ||||
-rw-r--r-- | src/leap/bitmask/core/web/static/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/bitmask/core/web/static/index.html (renamed from src/leap/bitmask/core/web/index.html) | 0 |
8 files changed, 145 insertions, 131 deletions
diff --git a/src/leap/bitmask/core/service.py b/src/leap/bitmask/core/service.py index 705dcb5..9682c18 100644 --- a/src/leap/bitmask/core/service.py +++ b/src/leap/bitmask/core/service.py @@ -29,9 +29,9 @@ from twisted.logger import Logger from leap.bitmask import __version__ 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.bitmask.core.web.service import HTTPDispatcherService from leap.common.events import server as event_server # from leap.vpn import EIPService @@ -175,7 +175,7 @@ class BitmaskBackend(configurable.ConfigurableService): zs.setServiceParent(self) def _init_web(self, onion=False): - service = _web.HTTPDispatcherService + service = HTTPDispatcherService self._maybe_init_service('web', service, self, onion=onion) def _init_websockets(self): diff --git a/src/leap/bitmask/core/web/__init__.py b/src/leap/bitmask/core/web/__init__.py index e69de29..ed8cc52 100644 --- a/src/leap/bitmask/core/web/__init__.py +++ b/src/leap/bitmask/core/web/__init__.py @@ -0,0 +1,12 @@ +try: + import leap.bitmask_js + assert leap.bitmask_js + HAS_WEB_UI = True +except ImportError: + HAS_WEB_UI = False + +try: + import txtorcon + assert txtorcon +except Exception: + pass diff --git a/src/leap/bitmask/core/web/_auth.py b/src/leap/bitmask/core/web/_auth.py new file mode 100644 index 0000000..6a5e362 --- /dev/null +++ b/src/leap/bitmask/core/web/_auth.py @@ -0,0 +1,86 @@ +from zope.interface import implementer + +from twisted.cred import portal, checkers, credentials, error as credError +from twisted.internet import defer +from twisted.web.guard import HTTPAuthSessionWrapper, BasicCredentialFactory +from twisted.web.resource import IResource + + +class TokenCredentialFactory(BasicCredentialFactory): + scheme = 'token' + + +@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, session_tokens, whitelist): + realm = HttpPasswordRealm(resource) + checker = TokenDictChecker(session_tokens) + resource_portal = portal.Portal(realm, [checker]) + credentialFactory = TokenCredentialFactory('localhost') + protected_resource = WhitelistHTTPAuthSessionWrapper( + resource_portal, [credentialFactory], + whitelist=whitelist) + return protected_resource + + +@implementer(checkers.ICredentialsChecker) +class TokenDictChecker: + + credentialInterfaces = (credentials.IUsernamePassword, + credentials.IUsernameHashedPassword) + + def __init__(self, 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): + # the resource is passed on regardless of user + if IResource in interfaces: + return (IResource, self.resource, lambda: None) + raise NotImplementedError() diff --git a/src/leap/bitmask/core/web/api.py b/src/leap/bitmask/core/web/api.py new file mode 100644 index 0000000..e8bd21e --- /dev/null +++ b/src/leap/bitmask/core/web/api.py @@ -0,0 +1,32 @@ +import json +from twisted.web.server import NOT_DONE_YET + +from twisted.web.resource import Resource + + +class Api(Resource): + + isLeaf = True + + def __init__(self, dispatcher): + Resource.__init__(self) + self.dispatcher = dispatcher + + def render_POST(self, request): + command = request.uri.split('/')[2:] + params = request.content.getvalue() + if params: + # json.loads returns unicode strings and the rest of the code + # expects strings. This 'str(param)' conversion can be removed + # if we move to python3 + for param in json.loads(params): + command.append(str(param)) + + d = self.dispatcher.dispatch(command) + d.addCallback(self._write_response, request) + return NOT_DONE_YET + + def _write_response(self, response, request): + request.setHeader('Content-Type', 'application/json') + request.write(response) + request.finish() diff --git a/src/leap/bitmask/core/_web.py b/src/leap/bitmask/core/web/service.py index 11a7be1..2437d2d 100644 --- a/src/leap/bitmask/core/_web.py +++ b/src/leap/bitmask/core/web/service.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# _web.py -# Copyright (C) 2016 LEAP Encryption Access Project +# service.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 @@ -19,118 +19,29 @@ HTTP REST Dispatcher Service. """ -import json import os import pkg_resources from twisted.application import service - -from twisted.internet import endpoints -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.internet import endpoints +from twisted.internet import reactor +from twisted.web.server import Site from twisted.web.static import File -from zope.interface import implementer - -from leap.bitmask.util import here from leap.bitmask.core.dispatcher import CommandDispatcher - -try: - import leap.bitmask_js - HAS_WEB_UI = True -except ImportError: - HAS_WEB_UI = False +from leap.bitmask.core.web import HAS_WEB_UI +from leap.bitmask.core.web.api import Api +from leap.bitmask.core.web._auth import protectedResourceFactory +from leap.bitmask.util import here try: import txtorcon -except Exception: +except ImportError: pass -log = Logger() - - -class TokenCredentialFactory(BasicCredentialFactory): - scheme = 'token' - - -@implementer(checkers.ICredentialsChecker) -class TokenDictChecker: - - credentialInterfaces = (credentials.IUsernamePassword, - credentials.IUsernameHashedPassword) - - def __init__(self, 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): - # the resource is passed on regardless of user - if IResource in interfaces: - 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, session_tokens, whitelist): - realm = HttpPasswordRealm(resource) - checker = TokenDictChecker(session_tokens) - resource_portal = portal.Portal(realm, [checker]) - credentialFactory = TokenCredentialFactory('localhost') - protected_resource = WhitelistHTTPAuthSessionWrapper( - resource_portal, [credentialFactory], - whitelist=whitelist) - return protected_resource +log = Logger() class HTTPDispatcherService(service.Service): @@ -167,7 +78,8 @@ class HTTPDispatcherService(service.Service): else: log.warn('bitmask_js not found, serving bitmask.core ui') webdir = os.path.abspath( - pkg_resources.resource_filename('leap.bitmask.core', 'web')) + pkg_resources.resource_filename( + 'leap.bitmask.core.web', 'static')) jspath = os.path.join( here(), '..', '..', '..', 'ui', 'app', 'lib', 'bitmask.js') @@ -230,34 +142,6 @@ class HTTPDispatcherService(service.Service): return {'web': status, 'uri': self.uri} -class Api(Resource): - - isLeaf = True - - def __init__(self, dispatcher): - Resource.__init__(self) - self.dispatcher = dispatcher - - def render_POST(self, request): - command = request.uri.split('/')[2:] - params = request.content.getvalue() - if params: - # json.loads returns unicode strings and the rest of the code - # expects strings. This 'str(param)' conversion can be removed - # if we move to python3 - for param in json.loads(params): - command.append(str(param)) - - d = self.dispatcher.dispatch(command) - d.addCallback(self._write_response, request) - return NOT_DONE_YET - - def _write_response(self, response, request): - request.setHeader('Content-Type', 'application/json') - request.write(response) - request.finish() - - def _has_txtorcon(): try: import txtorcon diff --git a/src/leap/bitmask/core/web/README b/src/leap/bitmask/core/web/static/README index 2b99926..2b99926 100644 --- a/src/leap/bitmask/core/web/README +++ b/src/leap/bitmask/core/web/static/README diff --git a/src/leap/bitmask/core/web/static/__init__.py b/src/leap/bitmask/core/web/static/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/leap/bitmask/core/web/static/__init__.py diff --git a/src/leap/bitmask/core/web/index.html b/src/leap/bitmask/core/web/static/index.html index 9951a9b..9951a9b 100644 --- a/src/leap/bitmask/core/web/index.html +++ b/src/leap/bitmask/core/web/static/index.html |