From ccb280703ba851265702b8a92cdedb294cc93608 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 14 Feb 2017 23:40:29 +0100 Subject: [feature] authenticate as anonymous if no token in header and serve / banner and robots to anon users. instead of returning 401 for all cases, I treat the unauthenticated case as a special case, and switch the service tree apart. this allows to serve a different resource tree to unauthenticated users. the new URLs are registered with the mapper. I don't really like that dependency, could be handled by twisted alone, but meh. - Resolves: #8764 --- docs/changelog-next.rst | 3 +++ server/src/leap/soledad/server/_resource.py | 19 ++++++++++++++++++- server/src/leap/soledad/server/auth.py | 26 +++++++++++++++++++++++--- server/src/leap/soledad/server/session.py | 8 ++++++-- server/src/leap/soledad/server/url_mapper.py | 3 +++ 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/docs/changelog-next.rst b/docs/changelog-next.rst index c5430b54..9d5f6297 100644 --- a/docs/changelog-next.rst +++ b/docs/changelog-next.rst @@ -10,6 +10,9 @@ I've added a new category `Misc` so we can track doc/style/packaging stuff. Features ~~~~~~~~ +- Refactor authentication code to use twisted credential system. +- `#8764 `_: Allow unauthenticated + users to retrieve the capabilties banner. - `#1234 `_: Description of the new feature corresponding with issue #1234. - New feature without related issue number. diff --git a/server/src/leap/soledad/server/_resource.py b/server/src/leap/soledad/server/_resource.py index 156e18aa..e04c0708 100644 --- a/server/src/leap/soledad/server/_resource.py +++ b/server/src/leap/soledad/server/_resource.py @@ -24,7 +24,24 @@ from ._server_info import ServerInfo from ._wsgi import get_sync_resource -__all__ = ['SoledadResource'] +__all__ = ['SoledadResource', 'SoledadAnonResource'] + + +class Robots(Resource): + def render_GET(self, request): + return 'robots, go away! please!' + + +class SoledadAnonResource(Resource): + """ + The parts of Soledad Server that unauthenticated users can see + """ + + def __init__(self, enable_blobs=False): + Resource.__init__(self) + server_info = ServerInfo(enable_blobs) + self.putChild('', server_info) + self.putChild('robots.txt', Robots()) class SoledadResource(Resource): diff --git a/server/src/leap/soledad/server/auth.py b/server/src/leap/soledad/server/auth.py index 7112aa35..6ce11e71 100644 --- a/server/src/leap/soledad/server/auth.py +++ b/server/src/leap/soledad/server/auth.py @@ -26,19 +26,25 @@ from zope.interface import implementer from twisted.cred import error from twisted.cred.checkers import ICredentialsChecker from twisted.cred.credentials import IUsernamePassword +from twisted.cred.credentials import IAnonymous +from twisted.cred.credentials import Anonymous from twisted.cred.credentials import UsernamePassword from twisted.cred.portal import IRealm from twisted.cred.portal import Portal +from twisted.logger import Logger from twisted.internet import defer from twisted.web.iweb import ICredentialFactory from twisted.web.resource import IResource from leap.soledad.common.couch import couch_server -from ._resource import SoledadResource +from ._resource import SoledadResource, SoledadAnonResource from ._config import get_config +log = Logger() + + @implementer(IRealm) class SoledadRealm(object): @@ -49,8 +55,17 @@ class SoledadRealm(object): self._sync_pool = sync_pool def requestAvatar(self, avatarId, mind, *interfaces): + log.warn('avatarId {0}'.format(avatarId)) + enable_blobs = self._conf['blobs'] + + # Anonymous access + if IAnonymous.providedBy(avatarId): + resource = SoledadAnonResource( + enable_blobs=enable_blobs) + return (IResource, resource, lambda: None) + + # Authenticated users if IResource in interfaces: - enable_blobs = self._conf['blobs'] resource = SoledadResource( enable_blobs=enable_blobs, sync_pool=self._sync_pool) @@ -61,7 +76,7 @@ class SoledadRealm(object): @implementer(ICredentialsChecker) class TokenChecker(object): - credentialInterfaces = [IUsernamePassword] + credentialInterfaces = [IUsernamePassword, IAnonymous] TOKENS_DB_PREFIX = "tokens_" TOKENS_DB_EXPIRE = 30 * 24 * 3600 # 30 days in seconds @@ -97,6 +112,10 @@ class TokenChecker(object): return db def requestAvatarId(self, credentials): + if IAnonymous.providedBy(credentials): + log.warn('we are anon') + return defer.succeed(Anonymous()) + uuid = credentials.username token = credentials.password @@ -106,6 +125,7 @@ class TokenChecker(object): db = self._tokens_db() token = db.get(sha512(token).hexdigest()) if token is None: + log.warn('token is none') return defer.fail(error.UnauthorizedLogin()) # TODO -- use cryptography constant time builtin comparison. diff --git a/server/src/leap/soledad/server/session.py b/server/src/leap/soledad/server/session.py index a2793bd3..70e4a35b 100644 --- a/server/src/leap/soledad/server/session.py +++ b/server/src/leap/soledad/server/session.py @@ -19,8 +19,9 @@ Twisted resource containing an authenticated Soledad session. """ from zope.interface import implementer +from twisted.cred.credentials import Anonymous from twisted.cred import error -from twisted.python import log +from twisted.logger import Logger from twisted.web import util from twisted.web._auth import wrapper from twisted.web.guard import HTTPAuthSessionWrapper @@ -32,6 +33,9 @@ from leap.soledad.server.auth import credentialFactory from leap.soledad.server.url_mapper import URLMapper +log = Logger() + + @implementer(IResource) class UnauthorizedResource(wrapper.UnauthorizedResource): isLeaf = True @@ -80,7 +84,7 @@ class SoledadSession(HTTPAuthSessionWrapper): # get authorization header or fail header = request.getHeader(b'authorization') if not header: - return UnauthorizedResource() + return util.DeferredResource(self._login(Anonymous())) # parse the authorization header auth_data = self._parseHeader(header) diff --git a/server/src/leap/soledad/server/url_mapper.py b/server/src/leap/soledad/server/url_mapper.py index 483f7e87..a0edeaca 100644 --- a/server/src/leap/soledad/server/url_mapper.py +++ b/server/src/leap/soledad/server/url_mapper.py @@ -53,6 +53,7 @@ class URLMapper(object): URL path | Authorized actions -------------------------------------------------- / | GET + /robots.txt | GET /shared-db | GET /shared-db/docs | - /shared-db/doc/{any_id} | GET, PUT, DELETE @@ -64,6 +65,8 @@ class URLMapper(object): """ # auth info for global resource self._connect('/', ['GET']) + # robots + self._connect('/robots.txt', ['GET']) # auth info for shared-db database resource self._connect('/%s' % SHARED_DB_NAME, ['GET']) # auth info for shared-db doc resource -- cgit v1.2.3