diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/changes/feat_configurable_ensure | 5 | ||||
-rwxr-xr-x | server/pkg/create-user-db | 44 | ||||
-rw-r--r-- | server/src/leap/soledad/server/__init__.py | 50 | ||||
-rw-r--r-- | server/src/leap/soledad/server/auth.py | 55 | ||||
-rw-r--r-- | server/src/leap/soledad/server/sync.py | 7 |
5 files changed, 74 insertions, 87 deletions
diff --git a/server/changes/feat_configurable_ensure b/server/changes/feat_configurable_ensure new file mode 100644 index 00000000..8abd2ac9 --- /dev/null +++ b/server/changes/feat_configurable_ensure @@ -0,0 +1,5 @@ +o 'create-user-db' script now can be configured from soledad-server.conf + when generating the user's security document. +o Migrating a user's database to newest design documents is now possible by + using a parameter '--migrate-all' on 'create-user-db' script. +o Remove tsafe monkeypatch from SSL lib, as it was needed for Twisted <12 diff --git a/server/pkg/create-user-db b/server/pkg/create-user-db index 7eafc945..54856643 100755 --- a/server/pkg/create-user-db +++ b/server/pkg/create-user-db @@ -20,7 +20,8 @@ import sys import netrc import argparse from leap.soledad.common.couch import CouchDatabase -from leap.soledad.common.couch import is_db_name_valid +from leap.soledad.common.couch.state import is_db_name_valid +from leap.soledad.common.couch import list_users_dbs from leap.soledad.server import load_configuration @@ -30,8 +31,12 @@ This is meant to be used by Soledad Server. """ parser = argparse.ArgumentParser(description=description) parser.add_argument('dbname', metavar='user-d34db33f', type=str, + default='', nargs='?', help='database name on the format user-{uuid4}') -NETRC_PATH = load_configuration('/etc/soledad/soledad-server.conf')['admin_netrc'] +parser.add_argument('--migrate-all', action='store_true', + help="recreate all design docs for all existing account") +CONF = load_configuration('/etc/soledad/soledad-server.conf') +NETRC_PATH = CONF['soledad-server']['admin_netrc'] def url_for_db(dbname): @@ -48,13 +53,34 @@ def url_for_db(dbname): return url -if __name__ == '__main__': - args = parser.parse_args() - if not is_db_name_valid(args.dbname): - print ("Invalid name! %s" % args.dbname) +def ensure_database(dbname): + """ + This method will ensure that a database named `dbname` will exist + or created if it doesn't. Calling it twice will ensure that design + documents are present and updated. + The database name has to match this criteria to be considered valid: + user-[a-f0-9]+ + + :param dbname: name of the user database + :type dbname: str + """ + if not is_db_name_valid(dbname): + print ("Invalid name! %s" % dbname) sys.exit(1) - url = url_for_db(args.dbname) + url = url_for_db(dbname) + db_security = CONF['database-security'] db = CouchDatabase.open_database(url=url, create=True, - replica_uid=None, ensure_ddocs=True) - print ('success! Created %s, replica_uid: %s' % + replica_uid=None, ensure_ddocs=True, + database_security=db_security) + print ('success! Ensured that database %s exists, with replica_uid: %s' % (db._dbname, db.replica_uid)) + + +if __name__ == '__main__': + args = parser.parse_args() + if args.migrate_all: + couch_url = url_for_db('') + for dbname in list_users_dbs(couch_url): + ensure_database(dbname) + else: + ensure_database(args.dbname) diff --git a/server/src/leap/soledad/server/__init__.py b/server/src/leap/soledad/server/__init__.py index f64d07bf..00e1e9fb 100644 --- a/server/src/leap/soledad/server/__init__.py +++ b/server/src/leap/soledad/server/__init__.py @@ -94,12 +94,6 @@ from u1db.remote import http_app, utils from ._version import get_versions -# Keep OpenSSL's tsafe before importing Twisted submodules so we can put -# it back if Twisted==12.0.0 messes with it. -from OpenSSL import tsafe - -from twisted import version - from leap.soledad.server.auth import SoledadTokenAuthMiddleware from leap.soledad.server.gzip_middleware import GzipMiddleware from leap.soledad.server.lock_resource import LockResource @@ -110,14 +104,7 @@ from leap.soledad.server.sync import ( ) from leap.soledad.common import SHARED_DB_NAME -from leap.soledad.common.couch import CouchServerState - -old_tsafe = tsafe - -if version.base() == "12.0.0": - # Put OpenSSL's tsafe back into place. This can probably be removed if we - # come to use Twisted>=12.3.0. - sys.modules['OpenSSL.tsafe'] = old_tsafe +from leap.soledad.common.couch.state import CouchServerState # ---------------------------------------------------------------------------- # Soledad WSGI application @@ -272,6 +259,20 @@ http_app.HTTPInvocationByMethodWithBody = HTTPInvocationByMethodWithBody # ---------------------------------------------------------------------------- # Auxiliary functions # ---------------------------------------------------------------------------- +CONFIG_DEFAULTS = { + 'soledad-server': { + 'couch_url': 'http://localhost:5984', + 'create_cmd': None, + 'admin_netrc': '/etc/couchdb/couchdb-admin.netrc', + }, + 'database-security': { + 'members': ['soledad'], + 'members_roles': [], + 'admins': [], + 'admins_roles': [] + } +} + def load_configuration(file_path): """ @@ -283,17 +284,19 @@ def load_configuration(file_path): @return: A dictionary with the configuration. @rtype: dict """ - defaults = { - 'couch_url': 'http://localhost:5984', - 'create_cmd': None, - 'admin_netrc': '/etc/couchdb/couchdb-admin.netrc', - } + defaults = dict(CONFIG_DEFAULTS) config = configparser.ConfigParser() config.read(file_path) - if 'soledad-server' in config: - for key in defaults: - if key in config['soledad-server']: - defaults[key] = config['soledad-server'][key] + for section in defaults.keys(): + if section in config: + for key in defaults[section]: + if key in config[section]: + defaults[section][key] = config[section][key] + for key, value in defaults['database-security'].iteritems(): + if type(value) is not unicode: + continue + defaults['database-security'][key] = \ + [item.strip() for item in value.split(',')] # TODO: implement basic parsing/sanitization of options comming from # config file. return defaults @@ -305,6 +308,7 @@ def load_configuration(file_path): def application(environ, start_response): conf = load_configuration('/etc/soledad/soledad-server.conf') + conf = conf['soledad-server'] state = CouchServerState(conf['couch_url'], create_cmd=conf['create_cmd']) # WSGI application that may be used by `twistd -web` application = GzipMiddleware( diff --git a/server/src/leap/soledad/server/auth.py b/server/src/leap/soledad/server/auth.py index 02b54cca..ccbd6fbd 100644 --- a/server/src/leap/soledad/server/auth.py +++ b/server/src/leap/soledad/server/auth.py @@ -21,20 +21,16 @@ Authentication facilities for Soledad Server. """ -import time import httplib import json from u1db import DBNAME_CONSTRAINTS, errors as u1db_errors from abc import ABCMeta, abstractmethod from routes.mapper import Mapper -from couchdb.client import Server from twisted.python import log -from hashlib import sha512 from leap.soledad.common import SHARED_DB_NAME from leap.soledad.common import USER_DB_PREFIX -from leap.soledad.common.errors import InvalidAuthTokenError class URLToAuthorization(object): @@ -351,14 +347,12 @@ class SoledadTokenAuthMiddleware(SoledadAuthMiddleware): Token based authentication. """ - TOKENS_DB_PREFIX = "tokens_" - TOKENS_DB_EXPIRE = 30 * 24 * 3600 # 30 days in seconds - TOKENS_TYPE_KEY = "type" - TOKENS_TYPE_DEF = "Token" - TOKENS_USER_ID_KEY = "user_id" - TOKEN_AUTH_ERROR_STRING = "Incorrect address or token." + def __init__(self, app): + self._state = app.state + super(SoledadTokenAuthMiddleware, self).__init__(app) + def _verify_authentication_scheme(self, scheme): """ Verify if authentication scheme is valid. @@ -391,50 +385,11 @@ class SoledadTokenAuthMiddleware(SoledadAuthMiddleware): """ token = auth_data # we expect a cleartext token at this point try: - return self._verify_token_in_couch(uuid, token) - except InvalidAuthTokenError: - raise + return self._state.verify_token(uuid, token) except Exception as e: log.err(e) return False - def _verify_token_in_couch(self, uuid, token): - """ - Query couchdb to decide if C{token} is valid for C{uuid}. - - @param uuid: The user uuid. - @type uuid: str - @param token: The token. - @type token: str - - @raise InvalidAuthTokenError: Raised when token received from user is - either missing in the tokens db or is - invalid. - """ - server = Server(url=self._app.state.couch_url) - # the tokens db rotates every 30 days, and the current db name is - # "tokens_NNN", where NNN is the number of seconds since epoch divided - # by the rotate period in seconds. When rotating, old and new tokens - # db coexist during a certain window of time and valid tokens are - # replicated from the old db to the new one. See: - # https://leap.se/code/issues/6785 - dbname = self.TOKENS_DB_PREFIX + \ - str(int(time.time() / self.TOKENS_DB_EXPIRE)) - db = server[dbname] - # lookup key is a hash of the token to prevent timing attacks. - token = db.get(sha512(token).hexdigest()) - if token is None: - raise InvalidAuthTokenError() - # we compare uuid hashes to avoid possible timing attacks that - # might exploit python's builtin comparison operator behaviour, - # which fails immediatelly when non-matching bytes are found. - couch_uuid_hash = sha512(token[self.TOKENS_USER_ID_KEY]).digest() - req_uuid_hash = sha512(uuid).digest() - if token[self.TOKENS_TYPE_KEY] != self.TOKENS_TYPE_DEF \ - or couch_uuid_hash != req_uuid_hash: - raise InvalidAuthTokenError() - return True - def _get_auth_error_string(self): """ Get the error string for token auth. diff --git a/server/src/leap/soledad/server/sync.py b/server/src/leap/soledad/server/sync.py index 619be565..db25c406 100644 --- a/server/src/leap/soledad/server/sync.py +++ b/server/src/leap/soledad/server/sync.py @@ -32,7 +32,7 @@ class SyncExchange(sync.SyncExchange): def __init__(self, db, source_replica_uid, last_known_generation, sync_id): """ :param db: The target syncing database. - :type db: CouchDatabase + :type db: SoledadBackend :param source_replica_uid: The uid of the source syncing replica. :type source_replica_uid: str :param last_known_generation: The last target replica generation the @@ -185,15 +185,12 @@ class SyncResource(http_app.SyncResource): :type ensure: bool """ # create or open the database - cache = get_cache_for('db-' + sync_id + self.dbname) + cache = get_cache_for('db-' + sync_id + self.dbname, expire=120) if ensure: db, self.replica_uid = self.state.ensure_database(self.dbname) - elif cache and 'instance' in cache: - db = cache['instance'] else: db = self.state.open_database(self.dbname) db.init_caching(cache) - cache['instance'] = db # validate the information the client has about server replica db.validate_gen_and_trans_id( last_known_generation, last_known_trans_id) |