summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Shyba <victor1984@riseup.net>2017-08-22 05:23:24 -0300
committerdrebs <drebs@riseup.net>2017-09-05 11:08:46 -0300
commitc8c742cb981cbe4087a7863ebc3d2a8e3dd93f25 (patch)
treedfc0fbec7de3c97368e81299bf5216c62cef42a3
parent9f4739c4ce468110fe070c766260770bb4d669a2 (diff)
[feature] add a local realm with file auth checker
-- Related: #8867
-rw-r--r--src/leap/soledad/server/_resource.py9
-rw-r--r--src/leap/soledad/server/auth.py73
-rw-r--r--src/leap/soledad/server/entrypoint.py2
-rw-r--r--testing/tests/server/test_auth.py42
4 files changed, 116 insertions, 10 deletions
diff --git a/src/leap/soledad/server/_resource.py b/src/leap/soledad/server/_resource.py
index 28344b38..e4c51ded 100644
--- a/src/leap/soledad/server/_resource.py
+++ b/src/leap/soledad/server/_resource.py
@@ -50,6 +50,15 @@ class SoledadAnonResource(Resource):
self.putChild('robots.txt', _Robots())
+class LocalResource(Resource):
+ """
+ Used for localhost endpoints, like IncomingBox delivery.
+ """
+
+ def __init__(conf):
+ pass
+
+
class SoledadResource(Resource):
"""
This is a dummy twisted resource, used only to allow different entry points
diff --git a/src/leap/soledad/server/auth.py b/src/leap/soledad/server/auth.py
index 1357b289..934517b5 100644
--- a/src/leap/soledad/server/auth.py
+++ b/src/leap/soledad/server/auth.py
@@ -39,6 +39,7 @@ from twisted.web.resource import IResource
from leap.soledad.common.couch import couch_server
from ._resource import SoledadResource, SoledadAnonResource
+from ._resource import LocalResource
from ._blobs import BlobsResource
from ._config import get_config
@@ -77,8 +78,63 @@ class SoledadRealm(object):
raise NotImplementedError()
+@implementer(IRealm)
+class LocalServicesRealm(object):
+
+ def __init__(self, conf=None):
+ if conf is None:
+ conf = get_config()
+ self.anon_resource = SoledadAnonResource(
+ enable_blobs=conf['blobs'])
+ self.auth_resource = LocalResource(conf)
+
+ def requestAvatar(self, avatarId, mind, *interfaces):
+
+ # Anonymous access
+ if IAnonymous.providedBy(avatarId):
+ return (IResource, self.anon_resource,
+ lambda: None)
+
+ # Authenticated access
+ else:
+ if IResource in interfaces:
+ return (IResource, self.auth_resource,
+ lambda: None)
+ raise NotImplementedError()
+
+
+@implementer(ICredentialsChecker)
+class FileTokenChecker(object):
+
+ def __init__(self, conf=None):
+ conf = conf or get_config()
+ self._trusted_services_tokens = {}
+ self._tokens_file_path = conf['services_tokens_file']
+ self._reload_tokens()
+
+ def _reload_tokens(self):
+ with open(self._tokens_file_path) as tokens_file:
+ for line in tokens_file.readlines():
+ line = line.strip()
+ if not line.startswith('#'):
+ service, token = line.split(':')
+ self._trusted_services_tokens[service] = token
+
+ def requestAvatarId(self, credentials):
+ if IAnonymous.providedBy(credentials):
+ return defer.succeed(Anonymous())
+
+ service = credentials.username
+ token = credentials.password
+
+ if self._trusted_services_tokens[service] != token:
+ return defer.fail(error.UnauthorizedLogin())
+
+ return defer.succeed(service)
+
+
@implementer(ICredentialsChecker)
-class TokenChecker(object):
+class CouchDBTokenChecker(object):
credentialInterfaces = [IUsernamePassword, IAnonymous]
@@ -164,10 +220,17 @@ class TokenCredentialFactory(object):
raise error.LoginFailed('Invalid credentials')
-def portalFactory(sync_pool):
- realm = SoledadRealm(sync_pool=sync_pool)
- checker = TokenChecker()
- return Portal(realm, [checker])
+def portalFactory(public=True, sync_pool=None):
+ database_checker = CouchDBTokenChecker()
+ file_checker = FileTokenChecker()
+ if public:
+ assert sync_pool
+ realm = SoledadRealm(sync_pool=sync_pool)
+ auth_checkers = [database_checker]
+ else:
+ realm = LocalServicesRealm()
+ auth_checkers = [file_checker, database_checker]
+ return Portal(realm, auth_checkers)
credentialFactory = TokenCredentialFactory()
diff --git a/src/leap/soledad/server/entrypoint.py b/src/leap/soledad/server/entrypoint.py
index c06b740e..9b9a5e66 100644
--- a/src/leap/soledad/server/entrypoint.py
+++ b/src/leap/soledad/server/entrypoint.py
@@ -40,7 +40,7 @@ class SoledadEntrypoint(SoledadSession):
pool = threadpool.ThreadPool(name='wsgi')
reactor.callWhenRunning(pool.start)
reactor.addSystemEventTrigger('after', 'shutdown', pool.stop)
- portal = portalFactory(pool)
+ portal = portalFactory(public=True, sync_pool=pool)
SoledadSession.__init__(self, portal)
diff --git a/testing/tests/server/test_auth.py b/testing/tests/server/test_auth.py
index 6eb647ee..85dd5ecf 100644
--- a/testing/tests/server/test_auth.py
+++ b/testing/tests/server/test_auth.py
@@ -17,7 +17,9 @@
"""
Tests for auth pieces.
"""
+import os
import collections
+import pytest
from contextlib import contextmanager
@@ -31,7 +33,8 @@ from twisted.web.test import test_httpauth
import leap.soledad.server.auth as auth_module
from leap.soledad.server.auth import SoledadRealm
-from leap.soledad.server.auth import TokenChecker
+from leap.soledad.server.auth import CouchDBTokenChecker
+from leap.soledad.server.auth import FileTokenChecker
from leap.soledad.server.auth import TokenCredentialFactory
from leap.soledad.server._resource import SoledadResource
@@ -66,7 +69,7 @@ def dummy_server(token):
yield collections.defaultdict(lambda: DummyServer(token))
-class TokenCheckerTestCase(unittest.TestCase):
+class DatabaseTokenCheckerTestCase(unittest.TestCase):
@inlineCallbacks
def test_good_creds(self):
@@ -74,7 +77,7 @@ class TokenCheckerTestCase(unittest.TestCase):
token = {'user_id': 'user', 'type': 'Token'}
server = dummy_server(token)
# setup the checker with the custom server
- checker = TokenChecker()
+ checker = CouchDBTokenChecker()
auth_module.couch_server = lambda url: server
# assert the checker *can* verify the creds
creds = UsernamePassword('user', 'pass')
@@ -87,7 +90,7 @@ class TokenCheckerTestCase(unittest.TestCase):
token = None
server = dummy_server(token)
# setup the checker with the custom server
- checker = TokenChecker()
+ checker = CouchDBTokenChecker()
auth_module.couch_server = lambda url: server
# assert the checker *cannot* verify the creds
creds = UsernamePassword('user', '')
@@ -95,6 +98,37 @@ class TokenCheckerTestCase(unittest.TestCase):
yield checker.requestAvatarId(creds)
+class FileTokenCheckerTestCase(unittest.TestCase):
+
+ @inlineCallbacks
+ @pytest.mark.usefixtures("method_tmpdir")
+ def test_good_creds(self):
+ auth_file_path = os.path.join(self.tempdir, 'auth.file')
+ with open(auth_file_path, 'w') as tempfile:
+ tempfile.write('goodservice:goodtoken')
+ # setup the checker with the auth tokens file
+ conf = {'services_tokens_file': auth_file_path}
+ checker = FileTokenChecker(conf)
+ # assert the checker *can* verify the creds
+ creds = UsernamePassword('goodservice', 'goodtoken')
+ avatarId = yield checker.requestAvatarId(creds)
+ self.assertEqual('goodservice', avatarId)
+
+ @inlineCallbacks
+ @pytest.mark.usefixtures("method_tmpdir")
+ def test_bad_creds(self):
+ auth_file_path = os.path.join(self.tempdir, 'auth.file')
+ with open(auth_file_path, 'w') as tempfile:
+ tempfile.write('service:token')
+ # setup the checker with the auth tokens file
+ conf = {'services_tokens_file': auth_file_path}
+ checker = FileTokenChecker(conf)
+ # assert the checker *cannot* verify the creds
+ creds = UsernamePassword('service', 'wrongtoken')
+ with self.assertRaises(UnauthorizedLogin):
+ yield checker.requestAvatarId(creds)
+
+
class TokenCredentialFactoryTestcase(
test_httpauth.RequestMixin, test_httpauth.BasicAuthTestsMixin,
unittest.TestCase):