Exemple #1
0
def init_soledad(_):
    token = srpauth.get_token()
    print "token", token

    global soledad
    soledad = Soledad(uuid,
                      _pass,
                      secrets_path,
                      local_db_path,
                      server_url,
                      cert_file,
                      auth_token=token,
                      defer_encryption=False)

    def getall(_):
        d = soledad.get_all_docs()
        return d

    d1 = soledad.create_doc({"test": 42})
    d1.addCallback(getall)
    d1.addCallbacks(printStuff, printErr)

    d2 = soledad.sync()
    d2.addCallbacks(printStuff, printErr)
    d2.addBoth(lambda r: reactor.stop())
Exemple #2
0
    def setUp(self):
        self.gpg_binary_path = get_gpg_bin_path()

        # TODO: we pass a fake shared database to soledad because we don't want
        # it o try to reach network to lookup a stored secret in the shared
        # database. This logic still has to be improved in soledad itself to
        # account for tests, and when that is done we can remove this from
        # here.
        class FakeSharedDb(object):
            def get_doc(self, doc_id):
                return None

            def put_doc(self, doc):
                return None

        self._soledad = Soledad(
            u"*****@*****.**",
            u"123456",
            secrets_path=self.tempdir + "/secret.gpg",
            local_db_path=self.tempdir + "/soledad.u1db",
            server_url='',
            cert_file=None,
            auth_token=None,
            shared_db=FakeSharedDb(),
        )
def initialize_soledad(tempdir):
    uuid = "foobar-uuid"
    passphrase = u"verysecretpassphrase"
    secret_path = os.path.join(tempdir, "secret.gpg")
    local_db_path = os.path.join(tempdir, "soledad.u1db")
    server_url = "http://provider"
    cert_file = ""

    class MockSharedDB(object):
        get_doc = Mock(return_value=None)
        put_doc = Mock()
        lock = Mock(return_value=('atoken', 300))
        unlock = Mock(return_value=True)

        def __call__(self):
            return self

    Soledad._shared_db = MockSharedDB()

    _soledad = Soledad(
        uuid,
        passphrase,
        secret_path,
        local_db_path,
        server_url,
        cert_file)

    from leap.mail.imap.fields import fields

    for name, expression in fields.INDEXES.items():
        _soledad.create_index(name, *expression)

    return _soledad
Exemple #4
0
    def setUp(self):
        self.gpg_binary_path = get_gpg_bin_path()

        self._soledad = Soledad(u"*****@*****.**",
                                u"123456",
                                secrets_path=self.tempdir + "/secret.gpg",
                                local_db_path=self.tempdir + "/soledad.u1db",
                                server_url='',
                                cert_file=None,
                                auth_token=None,
                                shared_db=defaultMockSharedDB())

        self.km = self._key_manager()

        class Response(object):
            code = 200
            phrase = ''

            def deliverBody(self, x):
                return ''

        self.km._nicknym._async_client_pinned.request = Mock(
            return_value=defer.succeed(Response()))

        d1 = self.km.put_raw_key(PRIVATE_KEY, ADDRESS)
        d2 = self.km.put_raw_key(PRIVATE_KEY_2, ADDRESS_2)
        return gatherResults([d1, d2])
    def setUp(self):
        self.gpg_binary_path = get_gpg_bin_path()

        self._soledad = Soledad(u"*****@*****.**",
                                u"123456",
                                secrets_path=self.tempdir + "/secret.gpg",
                                local_db_path=self.tempdir + "/soledad.u1db",
                                server_url='',
                                cert_file=None,
                                auth_token=None,
                                syncable=False)
Exemple #6
0
    def _try_soledad_init(self, uuid, secrets_path, local_db_path, server_url,
                          cert_file, auth_token):
        """
        Try to initialize soledad.

        :param uuid: user identifier
        :type uuid: str
        :param secrets_path: path to secrets file
        :type secrets_path: str
        :param local_db_path: path to local db file
        :type local_db_path: str
        :param server_url: soledad server uri
        :type server_url: str
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        # TODO: If selected server fails, retry with another host
        # (issue #3309)
        encoding = sys.getfilesystemencoding()

        # XXX We should get a flag in soledad itself
        if flags.OFFLINE is True:
            Soledad._shared_db = MockSharedDB()
        try:
            self._soledad = Soledad(
                uuid,
                self._password,
                secrets_path=secrets_path.encode(encoding),
                local_db_path=local_db_path.encode(encoding),
                server_url=server_url,
                cert_file=cert_file.encode(encoding),
                auth_token=auth_token,
                defer_encryption=True)

        # XXX All these errors should be handled by soledad itself,
        # and return a subclass of SoledadInitializationFailed

        # recoverable, will guarantee retries
        except (socket.timeout, socket.error, BootstrapSequenceError):
            logger.warning("Error while initializing Soledad")
            raise

        # unrecoverable
        except (u1db_errors.Unauthorized, u1db_errors.HTTPError):
            logger.error("Error while initializing Soledad (u1db error).")
            raise
        except Exception as exc:
            logger.exception("Unhandled error while initializating "
                             "Soledad: %r" % (exc, ))
            raise
Exemple #7
0
    def create(force_fresh_db=False,
               uuid=default_uuid,
               passphrase=DEFAULT_PASSPHRASE,
               token=DEFAULT_TOKEN):

        secrets_file = '%s.secret' % uuid
        secrets_path = os.path.join(tmpdir.strpath, secrets_file)

        # in some tests we might want to use the same user and remote database
        # but with a clean/empty local database (i.e. download benchmarks), so
        # here we provide a way to do that.
        idx = 1
        if force_fresh_db:
            # find the next index for this user
            idx = len(glob.glob('%s/*-*.db' % tmpdir.strpath)) + 1
        db_file = '%s-%d.db' % (uuid, idx)
        local_db_path = os.path.join(tmpdir.strpath, db_file)

        soledad_client = Soledad(uuid,
                                 unicode(passphrase),
                                 secrets_path=secrets_path,
                                 local_db_path=local_db_path,
                                 server_url=server_url,
                                 cert_file=cert_file,
                                 auth_token=token,
                                 with_blobs=True)
        request.addfinalizer(soledad_client.close)
        return soledad_client
Exemple #8
0
def _initialize_soledad(email, gnupg_home, tempdir):
    """
    Initializes soledad by hand

    :param email: ID for the user
    :param gnupg_home: path to home used by gnupg
    :param tempdir: path to temporal dir
    :rtype: Soledad instance
    """

    uuid = "foobar-uuid"
    passphrase = u"verysecretpassphrase"
    secret_path = os.path.join(tempdir, "secret.gpg")
    local_db_path = os.path.join(tempdir, "soledad.u1db")
    server_url = "https://provider"
    cert_file = ""

    soledad = Soledad(
        uuid,
        passphrase,
        secret_path,
        local_db_path,
        server_url,
        cert_file,
        syncable=False)

    return soledad
Exemple #9
0
    def _soledad_instance(self,
                          user=ADDRESS,
                          passphrase=u'123',
                          prefix='',
                          secrets_path=Soledad.STORAGE_SECRETS_FILE_NAME,
                          local_db_path='soledad.u1db',
                          server_url='',
                          cert_file=None,
                          secret_id=None,
                          shared_db_class=None):
        def _put_doc_side_effect(doc):
            self._doc_put = doc

        if shared_db_class is not None:
            MockSharedDB = shared_db_class
        else:
            MockSharedDB = self.get_default_shared_mock(_put_doc_side_effect)

        Soledad._shared_db = MockSharedDB()
        return Soledad(
            user,
            passphrase,
            secrets_path=os.path.join(self.tempdir, prefix, secrets_path),
            local_db_path=os.path.join(self.tempdir, prefix, local_db_path),
            server_url=server_url,  # Soledad will fail if not given an url.
            cert_file=cert_file,
            secret_id=secret_id,
            defer_encryption=self.defer_sync_encryption)
Exemple #10
0
class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest):

    def setUp(self):
        self.setUpEnv()
        self.gpg_binary_path = self._find_gpg()

        self._soledad = Soledad(
            u"*****@*****.**",
            u"123456",
            secrets_path=self.tempdir + "/secret.gpg",
            local_db_path=self.tempdir + "/soledad.u1db",
            server_url='',
            cert_file=None,
            auth_token=None,
            syncable=False
        )

    def tearDown(self):
        km = self._key_manager()

        # wait for the indexes to be ready for the tear down
        d = km._wrapper_map[OpenPGPKey].deferred_init
        d.addCallback(lambda _: self.delete_all_keys(km))
        d.addCallback(lambda _: self.tearDownEnv())
        d.addCallback(lambda _: self._soledad.close())
        return d

    def delete_all_keys(self, km):
        def delete_keys(keys):
            deferreds = []
            for key in keys:
                d = km._wrapper_map[key.__class__].delete_key(key)
                deferreds.append(d)
            return gatherResults(deferreds)

        def check_deleted(_, private):
            d = km.get_all_keys(private=private)
            d.addCallback(lambda keys: self.assertEqual(keys, []))
            return d

        deferreds = []
        for private in [True, False]:
            d = km.get_all_keys(private=private)
            d.addCallback(delete_keys)
            d.addCallback(check_deleted, private)
            deferreds.append(d)
        return gatherResults(deferreds)

    def _key_manager(self, user=ADDRESS, url='', token=None,
                     ca_cert_path=None):
        return KeyManager(user, url, self._soledad, token=token,
                          gpgbinary=self.gpg_binary_path,
                          ca_cert_path=ca_cert_path)

    def _find_gpg(self):
        gpg_path = distutils.spawn.find_executable('gpg')
        if gpg_path is not None:
            return os.path.realpath(gpg_path)
        else:
            return "/usr/bin/gpg"
Exemple #11
0
    def _soledad_instance(self,
                          user=ADDRESS,
                          passphrase=u'123',
                          prefix='',
                          secrets_path='secrets.json',
                          local_db_path='soledad.u1db',
                          server_url='https://127.0.0.1/',
                          cert_file=None,
                          shared_db_class=None,
                          auth_token='auth-token'):
        def _put_doc_side_effect(doc):
            self._doc_put = doc

        if shared_db_class is not None:
            MockSharedDB = shared_db_class
        else:
            MockSharedDB = self.get_default_shared_mock(_put_doc_side_effect)

        soledad = Soledad(
            user,
            passphrase,
            secrets_path=os.path.join(self.tempdir, prefix, secrets_path),
            local_db_path=os.path.join(self.tempdir, prefix, local_db_path),
            server_url=server_url,  # Soledad will fail if not given an url.
            cert_file=cert_file,
            defer_encryption=self.defer_sync_encryption,
            shared_db=MockSharedDB(),
            auth_token=auth_token)
        self.addCleanup(soledad.close)
        return soledad
Exemple #12
0
def initialize_soledad(tempdir):
    if os.path.isdir(tempdir):
        shutil.rmtree(tempdir)

    uuid = "foobar-uuid"
    passphrase = u"verysecretpassphrase"
    secret_path = os.path.join(tempdir, "secret.gpg")
    local_db_path = os.path.join(tempdir, "soledad.u1db")
    server_url = "http://provider"
    cert_file = ""

    class MockSharedDB(object):
        get_doc = Mock(return_value=None)
        put_doc = Mock()
        lock = Mock(return_value=('atoken', 300))
        unlock = Mock(return_value=True)
        close = Mock()

        def __call__(self):
            return self

    Soledad._shared_db = MockSharedDB()

    _soledad = Soledad(uuid, passphrase, secret_path, local_db_path,
                       server_url, cert_file)
    return _soledad
Exemple #13
0
    def _soledad_instance(self, user='******', passphrase=u'123',
                          prefix='',
                          secrets_path='secrets.json',
                          local_db_path='soledad.u1db', server_url='',
                          cert_file=None, auth_token=None):
        """
        Instantiate Soledad.
        """

        # this callback ensures we save a document which is sent to the shared
        # db.
        def _put_doc_side_effect(doc):
            self._doc_put = doc

        soledad = Soledad(
            user,
            passphrase,
            secrets_path=os.path.join(self.tempdir, prefix, secrets_path),
            local_db_path=os.path.join(
                self.tempdir, prefix, local_db_path),
            server_url=server_url,
            cert_file=cert_file,
            auth_token=auth_token,
            shared_db=self.get_default_shared_mock(_put_doc_side_effect))
        self.addCleanup(soledad.close)
        return soledad
Exemple #14
0
def initialize_soledad(tempdir, uuid):
    if os.path.isdir(tempdir):
        shutil.rmtree(tempdir)

    passphrase = u"verysecretpassphrase"
    secret_path = os.path.join(tempdir, "secret.gpg")
    local_db_path = os.path.join(tempdir, "soledad.u1db")
    server_url = "http://provider"
    cert_file = ""

    class MockSharedDB(object):
        get_doc = Mock(return_value=None)
        put_doc = Mock()
        lock = Mock(return_value=('atoken', 300))
        unlock = Mock(return_value=True)
        close = Mock()

        def __call__(self):
            return self

    Soledad._shared_db = MockSharedDB()

    _soledad = Soledad(
        uuid,
        passphrase,
        secret_path,
        local_db_path,
        server_url,
        cert_file,
        defer_encryption=False,
        syncable=False)

    yield SoledadMailAdaptor().initialize_store(_soledad)

    defer.returnValue(_soledad)
Exemple #15
0
    def _soledad_instance(self, uuid, passphrase, secrets_path, local_db_path,
                          server_url, cert_file):
        """
        Return a Soledad instance for tests.
        """
        # mock key fetching and storing so Soledad doesn't fail when trying to
        # reach the server.
        Soledad._fetch_keys_from_shared_db = Mock(return_value=None)
        Soledad._assert_keys_in_shared_db = Mock(return_value=None)

        # instantiate soledad
        def _put_doc_side_effect(doc):
            self._doc_put = doc

        class MockSharedDB(object):

            get_doc = Mock(return_value=None)
            put_doc = Mock(side_effect=_put_doc_side_effect)
            lock = Mock(return_value=('atoken', 300))
            unlock = Mock(return_value=True)

            def __call__(self):
                return self

        Soledad._shared_db = MockSharedDB()

        return Soledad(
            uuid,
            passphrase,
            secrets_path=secrets_path,
            local_db_path=local_db_path,
            server_url=server_url,
            cert_file=cert_file,
        )
Exemple #16
0
    def test__init_config_defaults(self):
        """
        Test if configuration defaults point to the correct place.
        """
        class SoledadMock(Soledad):
            def __init__(self):
                pass

        # instantiate without initializing so we just test _init_config()
        sol = SoledadMock()
        Soledad._init_config(sol, None, None, '')
        # assert value of secrets_path
        self.assertEquals(
            os.path.join(sol.DEFAULT_PREFIX,
                         Soledad.STORAGE_SECRETS_FILE_NAME), sol.secrets_path)
        # assert value of local_db_path
        self.assertEquals(os.path.join(sol.DEFAULT_PREFIX, 'soledad.u1db'),
                          sol.local_db_path)
Exemple #17
0
 def _get_soledad_client(self):
     self._tempdir = tempfile.mkdtemp()
     return Soledad(
         uuid=self._uuid,
         passphrase=u'123',
         secrets_path=os.path.join(self._tempdir, 'secrets.json'),
         local_db_path=os.path.join(self._tempdir, 'soledad.db'),
         server_url=self._server_url,
         cert_file=None,
         auth_token=self._auth_token,
         secret_id=None)
    def _try_soledad_init(self, uuid, secrets_path, local_db_path,
                          server_url, cert_file, auth_token):
        """
        Try to initialize soledad.

        :param uuid: user identifier
        :type uuid: str
        :param secrets_path: path to secrets file
        :type secrets_path: str
        :param local_db_path: path to local db file
        :type local_db_path: str
        :param server_url: soledad server uri
        :type server_url: str
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        # TODO: If selected server fails, retry with another host
        # (issue #3309)
        encoding = sys.getfilesystemencoding()

        # XXX We should get a flag in soledad itself
        if flags.OFFLINE is True:
            Soledad._shared_db = MockSharedDB()
        try:
            self._soledad = Soledad(
                uuid,
                self._password,
                secrets_path=secrets_path.encode(encoding),
                local_db_path=local_db_path.encode(encoding),
                server_url=server_url,
                cert_file=cert_file.encode(encoding),
                auth_token=auth_token,
                defer_encryption=True)

        # XXX All these errors should be handled by soledad itself,
        # and return a subclass of SoledadInitializationFailed

        # recoverable, will guarantee retries
        except (socket.timeout, socket.error, BootstrapSequenceError):
            logger.warning("Error while initializing Soledad")
            raise

        # unrecoverable
        except (u1db_errors.Unauthorized, u1db_errors.HTTPError):
            logger.error("Error while initializing Soledad (u1db error).")
            raise
        except Exception as exc:
            logger.exception("Unhandled error while initializating "
                             "Soledad: %r" % (exc,))
            raise
Exemple #19
0
def init_soledad(_):
    token = srpauth.get_token()
    print "token", token

    global soledad
    soledad = Soledad(uuid, _pass, secrets_path, local_db_path,
                      server_url, cert_file,
                      auth_token=token, defer_encryption=False)

    def getall(_):
        d = soledad.get_all_docs()
        return d

    d1 = soledad.create_doc({"test": 42})
    d1.addCallback(getall)
    d1.addCallbacks(printStuff, printErr)

    d2 = soledad.sync()
    d2.addCallbacks(printStuff, printErr)
    d2.addBoth(lambda r: reactor.stop())
Exemple #20
0
 def create():
     secrets_path = os.path.join(tmpdir.strpath, '%s.secret' % uuid4().hex)
     local_db_path = os.path.join(tmpdir.strpath, '%s.db' % uuid4().hex)
     soledad_client = Soledad(default_uuid,
                              unicode(passphrase),
                              secrets_path=secrets_path,
                              local_db_path=local_db_path,
                              server_url=server_url,
                              cert_file=None,
                              auth_token=token,
                              defer_encryption=True)
     request.addfinalizer(soledad_client.close)
     return soledad_client
    def test__init_config_defaults(self):
        """
        Test if configuration defaults point to the correct place.
        """

        class SoledadMock(Soledad):

            def __init__(self):
                pass

        # instantiate without initializing so we just test _init_config()
        sol = SoledadMock()
        Soledad._init_config(sol, None, None, '')
        # assert value of secrets_path
        self.assertEquals(
            os.path.join(
                sol.DEFAULT_PREFIX, Soledad.STORAGE_SECRETS_FILE_NAME),
            sol.secrets_path)
        # assert value of local_db_path
        self.assertEquals(
            os.path.join(sol.DEFAULT_PREFIX, 'soledad.u1db'),
            sol.local_db_path)
Exemple #22
0
    def setUp(self):
        self.setUpEnv()
        self.gpg_binary_path = self._find_gpg()

        self._soledad = Soledad(
            u"*****@*****.**",
            u"123456",
            secrets_path=self.tempdir + "/secret.gpg",
            local_db_path=self.tempdir + "/soledad.u1db",
            server_url='',
            cert_file=None,
            auth_token=None,
            syncable=False
        )
def _get_soledad_instance(uuid, passphrase, basedir, server_url, cert_file,
                          token):
    # setup soledad info
    logger.info('UUID is %s' % uuid)
    logger.info('Server URL is %s' % server_url)
    secrets_path = os.path.join(basedir, '%s.secret' % uuid)
    local_db_path = os.path.join(basedir, '%s.db' % uuid)
    # instantiate soledad
    return Soledad(uuid,
                   unicode(passphrase),
                   secrets_path=secrets_path,
                   local_db_path=local_db_path,
                   server_url=server_url,
                   cert_file=cert_file,
                   auth_token=token)
Exemple #24
0
    def create(cls, user_token, user_uuid, encryption_passphrase, secrets,
               local_db, server_url, api_cert):
        try:
            return Soledad(user_uuid,
                           passphrase=unicode(encryption_passphrase),
                           secrets_path=secrets,
                           local_db_path=local_db,
                           server_url=server_url,
                           cert_file=api_cert,
                           shared_db=None,
                           auth_token=user_token,
                           defer_encryption=False)

        except (WrongMacError, UnknownMacMethodError), e:
            raise SoledadWrongPassphraseException(e)
    def _init_soledad(self, encryption_passphrase):
        try:
            server_url = self._discover_soledad_server()

            self._create_database_dir()
            secrets = self._secrets_path()
            local_db = self._local_db_path()

            return Soledad(self.leap_srp_session.uuid,
                           unicode(encryption_passphrase), secrets, local_db,
                           server_url, which_bundle(self.provider),
                           self.leap_srp_session.token)

        except (WrongMac, UnknownMacMethod, MacMethods), e:
            raise SoledadWrongPassphraseException(e)
    def _try_soledad_init(self, uuid, secrets_path, local_db_path,
                          server_url, cert_file, auth_token):
        """
        Tries to initialize soledad.

        :param uuid: user identifier
        :param secrets_path: path to secrets file
        :param local_db_path: path to local db file
        :param server_url: soledad server uri
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        # TODO: If selected server fails, retry with another host
        # (issue #3309)
        try:
            self._soledad = Soledad(
                uuid,
                self._password.encode("utf-8"),
                secrets_path=secrets_path,
                local_db_path=local_db_path,
                server_url=server_url,
                cert_file=cert_file,
                auth_token=auth_token)

        # XXX All these errors should be handled by soledad itself,
        # and return a subclass of SoledadInitializationFailed

        # recoverable, will guarantee retries
        except socket.timeout:
            logger.debug("SOLEDAD initialization TIMED OUT...")
            self.soledad_timeout.emit()
        except socket.error as exc:
            logger.error("Socket error while initializing soledad")
            self.soledad_timeout.emit()

        # unrecoverable
        except u1db_errors.Unauthorized:
            logger.error("Error while initializing soledad "
                         "(unauthorized).")
            self.soledad_failed.emit()
        except Exception as exc:
            logger.exception("Unhandled error while initializating "
                             "soledad: %r" % (exc,))
            self.soledad_failed.emit()
def initialize_soledad(uuid, email, passwd,
                       secrets, localdb,
                       gnupg_home, tempdir):
    """
    Initializes soledad by hand

    :param email: ID for the user
    :param gnupg_home: path to home used by gnupg
    :param tempdir: path to temporal dir
    :rtype: Soledad instance
    """
    # XXX TODO unify with an authoritative source of mocks
    # for soledad (or partial initializations).
    # This is copied from the imap tests.

    server_url = "http://provider"
    cert_file = ""

    class Mock(object):

        def __init__(self, return_value=None):
            self._return = return_value

        def __call__(self, *args, **kwargs):
            return self._return

    class MockSharedDB(object):

        get_doc = Mock()
        put_doc = Mock()
        lock = Mock(return_value=('atoken', 300))
        unlock = Mock(return_value=True)

        def __call__(self):
            return self

    Soledad._shared_db = MockSharedDB()
    soledad = Soledad(
        uuid,
        passwd,
        secrets,
        localdb,
        server_url,
        cert_file,
        defer_encryption=True)

    return soledad
    def _init_soledad(self, encryption_passphrase):
        try:
            server_url = self._discover_soledad_server()

            self._create_database_dir()
            secrets = self._secrets_path()
            local_db = self._local_db_path()

            return Soledad(self.user_uuid,
                           passphrase=unicode(encryption_passphrase),
                           secrets_path=secrets,
                           local_db_path=local_db, server_url=server_url,
                           cert_file=LeapCertificate(self.provider).provider_api_cert,
                           shared_db=None,
                           auth_token=self.user_token,
                           defer_encryption=False)

        except (WrongMacError, UnknownMacMethodError), e:
            raise SoledadWrongPassphraseException(e)
Exemple #29
0
    def _init_soledad(self, encryption_passphrase):
        try:
            server_url = self._discover_soledad_server()

            self._create_database_dir()
            secrets = self._secrets_path()
            local_db = self._local_db_path()

            return Soledad(self.user_uuid,
                           unicode(encryption_passphrase),
                           secrets,
                           local_db,
                           server_url,
                           which_api_CA_bundle(self.provider),
                           self.user_token,
                           defer_encryption=False)

        except (WrongMac, UnknownMacMethod), e:
            raise SoledadWrongPassphraseException(e)
Exemple #30
0
    def _soledad_instance(self,
                          user='******',
                          passphrase=u'123',
                          prefix='',
                          secrets_path=Soledad.STORAGE_SECRETS_FILE_NAME,
                          local_db_path='soledad.u1db',
                          server_url='',
                          cert_file=None,
                          auth_token=None,
                          secret_id=None):
        """
        Instantiate Soledad.
        """

        # this callback ensures we save a document which is sent to the shared
        # db.
        def _put_doc_side_effect(doc):
            self._doc_put = doc

        # we need a mocked shared db or else Soledad will try to access the
        # network to find if there are uploaded secrets.
        class MockSharedDB(object):

            get_doc = mock.Mock(return_value=None)
            put_doc = mock.Mock(side_effect=_put_doc_side_effect)
            lock = mock.Mock(return_value=('atoken', 300))
            unlock = mock.Mock()

            def __call__(self):
                return self

        Soledad._shared_db = MockSharedDB()
        return Soledad(user,
                       passphrase,
                       secrets_path=os.path.join(self.tempdir, prefix,
                                                 secrets_path),
                       local_db_path=os.path.join(self.tempdir, prefix,
                                                  local_db_path),
                       server_url=server_url,
                       cert_file=cert_file,
                       auth_token=auth_token,
                       secret_id=secret_id)
Exemple #31
0
    def setUp(self):
        self.setUpEnv()

        # setup our own stuff
        address = '*****@*****.**'  # user's address in the form user@provider
        uuid = '*****@*****.**'
        passphrase = u'123'
        secrets_path = os.path.join(self.tempdir, 'secret.gpg')
        local_db_path = os.path.join(self.tempdir, 'soledad.u1db')
        server_url = 'http://provider/'
        cert_file = ''

        self._soledad = Soledad(uuid,
                                passphrase,
                                secrets_path=secrets_path,
                                local_db_path=local_db_path,
                                server_url=server_url,
                                cert_file=cert_file,
                                syncable=False)
        return self._setup_keymanager(address)
Exemple #32
0
    def create(force_fresh_db=False):
        secrets_file = '%s.secret' % default_uuid
        secrets_path = os.path.join(tmpdir.strpath, secrets_file)

        # in some tests we might want to use the same user and remote database
        # but with a clean/empty local database (i.e. download benchmarks), so
        # here we provide a way to do that.
        db_file = '%s.db' % default_uuid
        if force_fresh_db:
            prefix = uuid4().hex
            db_file = prefix + '-' + db_file
        local_db_path = os.path.join(tmpdir.strpath, db_file)

        soledad_client = Soledad(default_uuid,
                                 unicode(passphrase),
                                 secrets_path=secrets_path,
                                 local_db_path=local_db_path,
                                 server_url=server_url,
                                 cert_file=None,
                                 auth_token=token)
        request.addfinalizer(soledad_client.close)
        return soledad_client
Exemple #33
0
    def _soledad_instance(self,
                          user=None,
                          passphrase=u'123',
                          prefix='',
                          secrets_path='secrets.json',
                          local_db_path='soledad.u1db',
                          server_url='',
                          cert_file=None,
                          auth_token=None):
        """
        Instantiate Soledad.
        """

        # this callback ensures we save a document which is sent to the shared
        # db.
        def _put_doc_side_effect(doc):
            self._doc_put = doc

        if not server_url:
            # attempt to find the soledad server url
            server_address = None
            server = getattr(self, 'server', None)
            if server:
                server_address = getattr(self.server, 'server_address', None)
            else:
                host = self.port.getHost()
                server_address = (host.host, host.port)
            if server_address:
                server_url = 'http://%s:%d' % (server_address)

        return Soledad(
            user,
            passphrase,
            secrets_path=os.path.join(self.tempdir, prefix, secrets_path),
            local_db_path=os.path.join(self.tempdir, prefix, local_db_path),
            server_url=server_url,
            cert_file=cert_file,
            auth_token=auth_token,
            shared_db=self.get_default_shared_mock(_put_doc_side_effect))
Exemple #34
0
def initialize_soledad(email, gnupg_home, tempdir):
    """
    Initializes soledad by hand

    :param email: ID for the user
    :param gnupg_home: path to home used by gnupg
    :param tempdir: path to temporal dir
    :rtype: Soledad instance
    """

    uuid = "foobar-uuid"
    passphrase = u"verysecretpassphrase"
    secret_path = os.path.join(tempdir, "secret.gpg")
    local_db_path = os.path.join(tempdir, "soledad.u1db")
    server_url = "http://provider"
    cert_file = ""

    class MockSharedDB(object):

        get_doc = Mock(return_value=None)
        put_doc = Mock()
        lock = Mock(return_value=('atoken', 300))
        unlock = Mock(return_value=True)

        def __call__(self):
            return self

    Soledad._shared_db = MockSharedDB()

    _soledad = Soledad(
        uuid,
        passphrase,
        secret_path,
        local_db_path,
        server_url,
        cert_file)

    return _soledad
Exemple #35
0
def _initialize_soledad(email, gnupg_home, tempdir):
    """
    Initializes soledad by hand

    :param email: ID for the user
    :param gnupg_home: path to home used by gnupg
    :param tempdir: path to temporal dir
    :rtype: Soledad instance
    """

    uuid = "foobar-uuid"
    passphrase = u"verysecretpassphrase"
    secret_path = os.path.join(tempdir, "secret.gpg")
    local_db_path = os.path.join(tempdir, "soledad.u1db")
    server_url = "https://provider"
    cert_file = ""

    # TODO: we pass a fake shared database to soledad because we don't want
    # it o try to reach network to lookup a stored secret in the shared
    # database. This logic still has to be improved in soledad itself to
    # account for tests, and when that is done we can remove this from
    # here.
    class FakeSharedDb(object):
        def get_doc(self, doc_id):
            return None

        def put_doc(self, doc):
            return None

    soledad = Soledad(uuid,
                      passphrase,
                      secret_path,
                      local_db_path,
                      server_url,
                      cert_file,
                      shared_db=FakeSharedDb())

    return soledad
    def load_and_sync_soledad(self):
        """
        Once everthing is in the right place, we instantiate and sync
        Soledad

        :param srp_auth: SRPAuth object used
        :type srp_auth: SRPAuth
        """
        srp_auth = self.srpauth
        uuid = srp_auth.get_uid()

        prefix = os.path.join(self._soledad_config.get_path_prefix(),
                              "leap", "soledad")
        secrets_path = "%s/%s.secret" % (prefix, uuid)
        local_db_path = "%s/%s.db" % (prefix, uuid)

        # TODO: Select server based on timezone (issue #3308)
        server_dict = self._soledad_config.get_hosts()

        if server_dict.keys():
            selected_server = server_dict[server_dict.keys()[0]]
            server_url = "https://%s:%s/user-%s" % (
                selected_server["hostname"],
                selected_server["port"],
                uuid)

            logger.debug("Using soledad server url: %s" % (server_url,))

            cert_file = self._provider_config.get_ca_cert_path()

            # TODO: If selected server fails, retry with another host
            # (issue #3309)
            try:
                self._soledad = Soledad(
                    uuid,
                    self._password.encode("utf-8"),
                    secrets_path=secrets_path,
                    local_db_path=local_db_path,
                    server_url=server_url,
                    cert_file=cert_file,
                    auth_token=srp_auth.get_token())
                self._soledad.sync()

            # XXX All these errors should be handled by soledad itself,
            # and return a subclass of SoledadInitializationFailed
            except socket.timeout:
                logger.debug("SOLEDAD TIMED OUT...")
                self.soledad_timeout.emit()
            except socket.error as exc:
                logger.error("Socket error while initializing soledad")
                self.soledad_failed.emit()
            except u1db_errors.Unauthorized:
                logger.error("Error while initializing soledad "
                             "(unauthorized).")
                self.soledad_failed.emit()
            except Exception as exc:
                logger.error("Unhandled error while initializating "
                             "soledad: %r" % (exc,))
                raise
        else:
            raise Exception("No soledad server found")
Exemple #37
0
class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest):
    def setUp(self):
        self.gpg_binary_path = get_gpg_bin_path()

        self._soledad = Soledad(u"*****@*****.**",
                                u"123456",
                                secrets_path=self.tempdir + "/secret.gpg",
                                local_db_path=self.tempdir + "/soledad.u1db",
                                server_url='',
                                cert_file=None,
                                auth_token=None,
                                shared_db=defaultMockSharedDB())

        self.km = self._key_manager()

        class Response(object):
            code = 200
            phrase = ''

            def deliverBody(self, x):
                return ''

        self.km._nicknym._async_client_pinned.request = Mock(
            return_value=defer.succeed(Response()))

        d1 = self.km.put_raw_key(PRIVATE_KEY, ADDRESS)
        d2 = self.km.put_raw_key(PRIVATE_KEY_2, ADDRESS_2)
        return gatherResults([d1, d2])

    def tearDown(self):
        km = self._key_manager()
        # wait for the indexes to be ready for the tear down
        d = km._openpgp.deferred_init
        d.addCallback(lambda _: self.delete_all_keys(km))
        d.addCallback(lambda _: self._soledad.close())
        return d

    def delete_all_keys(self, km):
        def delete_keys(keys):
            deferreds = []
            for key in keys:
                d = km._openpgp.delete_key(key)
                deferreds.append(d)
            return gatherResults(deferreds)

        def check_deleted(_, private):
            d = km.get_all_keys(private=private)
            d.addCallback(lambda keys: self.assertEqual(keys, []))
            return d

        deferreds = []
        for private in [True, False]:
            d = km.get_all_keys(private=private)
            d.addCallback(delete_keys)
            d.addCallback(check_deleted, private)
            deferreds.append(d)
        return gatherResults(deferreds)

    def _key_manager(self,
                     user=ADDRESS,
                     url='',
                     token=None,
                     ca_cert_path=None):
        return KeyManager(user,
                          url,
                          self._soledad,
                          token=token,
                          gpgbinary=self.gpg_binary_path,
                          ca_cert_path=ca_cert_path)
class SoledadBootstrapper(AbstractBootstrapper):
    """
    Soledad init procedure
    """

    SOLEDAD_KEY = "soledad"
    KEYMANAGER_KEY = "keymanager"

    PUBKEY_KEY = "user[public_key]"

    MAX_INIT_RETRIES = 10

    # All dicts returned are of the form
    # {"passed": bool, "error": str}
    download_config = QtCore.Signal(dict)
    gen_key = QtCore.Signal(dict)
    soledad_timeout = QtCore.Signal()
    soledad_failed = QtCore.Signal()

    def __init__(self):
        AbstractBootstrapper.__init__(self)

        self._provider_config = None
        self._soledad_config = None
        self._keymanager = None
        self._download_if_needed = False
        self._user = ""
        self._password = ""
        self._srpauth = None
        self._soledad = None

        self._soledad_retries = 0

    @property
    def keymanager(self):
        return self._keymanager

    @property
    def soledad(self):
        return self._soledad

    @property
    def srpauth(self):
        leap_assert(self._provider_config is not None,
                    "We need a provider config")
        return SRPAuth(self._provider_config)

    # retries

    def cancel_bootstrap(self):
        self._soledad_retries = self.MAX_INIT_RETRIES

    def should_retry_initialization(self):
        """
        Returns True if we should retry the initialization.
        """
        logger.debug("current retries: %s, max retries: %s" % (
            self._soledad_retries,
            self.MAX_INIT_RETRIES))
        return self._soledad_retries < self.MAX_INIT_RETRIES

    def increment_retries_count(self):
        """
        Increments the count of initialization retries.
        """
        self._soledad_retries += 1

    # initialization

    def load_and_sync_soledad(self):
        """
        Once everthing is in the right place, we instantiate and sync
        Soledad

        :param srp_auth: SRPAuth object used
        :type srp_auth: SRPAuth
        """
        srp_auth = self.srpauth
        uuid = srp_auth.get_uid()

        prefix = os.path.join(self._soledad_config.get_path_prefix(),
                              "leap", "soledad")
        secrets_path = "%s/%s.secret" % (prefix, uuid)
        local_db_path = "%s/%s.db" % (prefix, uuid)

        # TODO: Select server based on timezone (issue #3308)
        server_dict = self._soledad_config.get_hosts()

        if server_dict.keys():
            selected_server = server_dict[server_dict.keys()[0]]
            server_url = "https://%s:%s/user-%s" % (
                selected_server["hostname"],
                selected_server["port"],
                uuid)

            logger.debug("Using soledad server url: %s" % (server_url,))

            cert_file = self._provider_config.get_ca_cert_path()

            # TODO: If selected server fails, retry with another host
            # (issue #3309)
            try:
                self._soledad = Soledad(
                    uuid,
                    self._password.encode("utf-8"),
                    secrets_path=secrets_path,
                    local_db_path=local_db_path,
                    server_url=server_url,
                    cert_file=cert_file,
                    auth_token=srp_auth.get_token())
                self._soledad.sync()

            # XXX All these errors should be handled by soledad itself,
            # and return a subclass of SoledadInitializationFailed
            except socket.timeout:
                logger.debug("SOLEDAD TIMED OUT...")
                self.soledad_timeout.emit()
            except socket.error as exc:
                logger.error("Socket error while initializing soledad")
                self.soledad_failed.emit()
            except u1db_errors.Unauthorized:
                logger.error("Error while initializing soledad "
                             "(unauthorized).")
                self.soledad_failed.emit()
            except Exception as exc:
                logger.error("Unhandled error while initializating "
                             "soledad: %r" % (exc,))
                raise
        else:
            raise Exception("No soledad server found")

    def _download_config(self):
        """
        Downloads the Soledad config for the given provider
        """

        leap_assert(self._provider_config,
                    "We need a provider configuration!")

        logger.debug("Downloading Soledad config for %s" %
                     (self._provider_config.get_domain(),))

        self._soledad_config = SoledadConfig()

        headers = {}
        mtime = get_mtime(
            os.path.join(
                self._soledad_config.get_path_prefix(),
                "leap", "providers",
                self._provider_config.get_domain(),
                "soledad-service.json"))

        if self._download_if_needed and mtime:
            headers['if-modified-since'] = mtime

        api_version = self._provider_config.get_api_version()

        # there is some confusion with this uri,
        config_uri = "%s/%s/config/soledad-service.json" % (
            self._provider_config.get_api_uri(),
            api_version)
        logger.debug('Downloading soledad config from: %s' % config_uri)

        # TODO factor out this srpauth protected get (make decorator)
        srp_auth = self.srpauth
        session_id = srp_auth.get_session_id()
        cookies = None
        if session_id:
            cookies = {"_session_id": session_id}

        res = self._session.get(config_uri,
                                verify=self._provider_config
                                .get_ca_cert_path(),
                                headers=headers,
                                cookies=cookies)
        res.raise_for_status()

        self._soledad_config.set_api_version(api_version)

        # Not modified
        if res.status_code == 304:
            logger.debug("Soledad definition has not been modified")
            self._soledad_config.load(
                os.path.join(
                    "leap", "providers",
                    self._provider_config.get_domain(),
                    "soledad-service.json"))
        else:
            soledad_definition, mtime = get_content(res)

            self._soledad_config.load(data=soledad_definition, mtime=mtime)
            self._soledad_config.save(["leap",
                                       "providers",
                                       self._provider_config.get_domain(),
                                       "soledad-service.json"])

        self.load_and_sync_soledad()

    def _gen_key(self, _):
        """
        Generates the key pair if needed, uploads it to the webapp and
        nickserver
        """
        leap_assert(self._provider_config,
                    "We need a provider configuration!")

        address = "%s@%s" % (self._user, self._provider_config.get_domain())

        logger.debug("Retrieving key for %s" % (address,))

        srp_auth = self.srpauth

        # TODO: use which implementation with known paths
        # TODO: Fix for Windows
        gpgbin = "/usr/bin/gpg"

        if self._standalone:
            gpgbin = os.path.join(self._provider_config.get_path_prefix(),
                                  "..", "apps", "mail", "gpg")

        self._keymanager = KeyManager(
            address,
            "https://nicknym.%s:6425" % (self._provider_config.get_domain(),),
            self._soledad,
            #token=srp_auth.get_token(),  # TODO: enable token usage
            session_id=srp_auth.get_session_id(),
            ca_cert_path=self._provider_config.get_ca_cert_path(),
            api_uri=self._provider_config.get_api_uri(),
            api_version=self._provider_config.get_api_version(),
            uid=srp_auth.get_uid(),
            gpgbinary=gpgbin)
        try:
            self._keymanager.get_key(address, openpgp.OpenPGPKey,
                                     private=True, fetch_remote=False)
        except KeyNotFound:
            logger.debug("Key not found. Generating key for %s" % (address,))
            self._keymanager.gen_key(openpgp.OpenPGPKey)
            self._keymanager.send_key(openpgp.OpenPGPKey)
            logger.debug("Key generated successfully.")

    def run_soledad_setup_checks(self,
                                 provider_config,
                                 user,
                                 password,
                                 download_if_needed=False,
                                 standalone=False):
        """
        Starts the checks needed for a new soledad setup

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig
        :param user: User's login
        :type user: str
        :param password: User's password
        :type password: str
        :param download_if_needed: If True, it will only download
                                   files if the have changed since the
                                   time it was previously downloaded.
        :type download_if_needed: bool
        :param standalone: If True, it'll look for paths inside the
                           bundle (like for gpg)
        :type standalone: bool
        """
        leap_assert_type(provider_config, ProviderConfig)

        # XXX we should provider a method for setting provider_config
        self._provider_config = provider_config
        self._download_if_needed = download_if_needed
        self._user = user
        self._password = password
        self._standalone = standalone

        cb_chain = [
            (self._download_config, self.download_config),
            (self._gen_key, self.gen_key)
        ]

        self.addCallbackChain(cb_chain)
Exemple #39
0
class KeyManagerWithSoledadTestCase(unittest.TestCase, BaseLeapTest):
    def setUp(self):
        self.gpg_binary_path = get_gpg_bin_path()

        # TODO: we pass a fake shared database to soledad because we don't want
        # it o try to reach network to lookup a stored secret in the shared
        # database. This logic still has to be improved in soledad itself to
        # account for tests, and when that is done we can remove this from
        # here.
        class FakeSharedDb(object):
            def get_doc(self, doc_id):
                return None

            def put_doc(self, doc):
                return None

        self._soledad = Soledad(
            u"*****@*****.**",
            u"123456",
            secrets_path=self.tempdir + "/secret.gpg",
            local_db_path=self.tempdir + "/soledad.u1db",
            server_url='',
            cert_file=None,
            auth_token=None,
            shared_db=FakeSharedDb(),
        )

    def tearDown(self):
        km = self._key_manager()

        # wait for the indexes to be ready for the tear down
        d = km._openpgp.deferred_init
        d.addCallback(lambda _: self.delete_all_keys(km))
        d.addCallback(lambda _: self._soledad.close())
        return d

    def delete_all_keys(self, km):
        def delete_keys(keys):
            deferreds = []
            for key in keys:
                d = km._openpgp.delete_key(key)
                deferreds.append(d)
            return gatherResults(deferreds)

        def check_deleted(_, private):
            d = km.get_all_keys(private=private)
            d.addCallback(lambda keys: self.assertEqual(keys, []))
            return d

        deferreds = []
        for private in [True, False]:
            d = km.get_all_keys(private=private)
            d.addCallback(delete_keys)
            d.addCallback(check_deleted, private)
            deferreds.append(d)
        return gatherResults(deferreds)

    def _key_manager(self,
                     user=ADDRESS,
                     url='',
                     token=None,
                     ca_cert_path=None):
        return KeyManager(user,
                          url,
                          self._soledad,
                          token=token,
                          gpgbinary=self.gpg_binary_path,
                          ca_cert_path=ca_cert_path)

    def get_public_binary_key(self):
        with open(PATH + '/public_key.bin', 'r') as binary_public_key:
            return binary_public_key.read()

    def get_private_binary_key(self):
        with open(PATH + '/private_key.bin', 'r') as binary_private_key:
            return binary_private_key.read()
class SoledadBootstrapper(AbstractBootstrapper):
    """
    Soledad init procedure
    """
    SOLEDAD_KEY = "soledad"
    KEYMANAGER_KEY = "keymanager"

    PUBKEY_KEY = "user[public_key]"

    MAX_INIT_RETRIES = 10
    MAX_SYNC_RETRIES = 10

    # All dicts returned are of the form
    # {"passed": bool, "error": str}
    download_config = QtCore.Signal(dict)
    gen_key = QtCore.Signal(dict)
    soledad_timeout = QtCore.Signal()
    soledad_failed = QtCore.Signal()

    def __init__(self):
        AbstractBootstrapper.__init__(self)

        self._provider_config = None
        self._soledad_config = None
        self._keymanager = None
        self._download_if_needed = False

        self._user = ""
        self._password = ""
        self._srpauth = None
        self._soledad = None

        self._soledad_retries = 0

    @property
    def keymanager(self):
        return self._keymanager

    @property
    def soledad(self):
        return self._soledad

    @property
    def srpauth(self):
        leap_assert(self._provider_config is not None,
                    "We need a provider config")
        return SRPAuth(self._provider_config)

    # retries

    def cancel_bootstrap(self):
        self._soledad_retries = self.MAX_INIT_RETRIES

    def should_retry_initialization(self):
        """
        Returns True if we should retry the initialization.
        """
        logger.debug("current retries: %s, max retries: %s" % (
            self._soledad_retries,
            self.MAX_INIT_RETRIES))
        return self._soledad_retries < self.MAX_INIT_RETRIES

    def increment_retries_count(self):
        """
        Increments the count of initialization retries.
        """
        self._soledad_retries += 1

    def _get_db_paths(self, uuid):
        """
        Returns the secrets and local db paths needed for soledad
        initialization

        :param uuid: uuid for user
        :type uuid: str

        :return: a tuple with secrets, local_db paths
        :rtype: tuple
        """
        prefix = os.path.join(get_path_prefix(), "leap", "soledad")
        secrets = "%s/%s.secret" % (prefix, uuid)
        local_db = "%s/%s.db" % (prefix, uuid)

        # We remove an empty file if found to avoid complains
        # about the db not being properly initialized
        if is_file(local_db) and is_empty_file(local_db):
            try:
                os.remove(local_db)
            except OSError:
                logger.warning("Could not remove empty file %s"
                               % local_db)
        return secrets, local_db

    # initialization

    def load_and_sync_soledad(self):
        """
        Once everthing is in the right place, we instantiate and sync
        Soledad
        """
        # TODO this method is still too large
        uuid = self.srpauth.get_uid()
        token = self.srpauth.get_token()

        secrets_path, local_db_path = self._get_db_paths(uuid)

        # TODO: Select server based on timezone (issue #3308)
        server_dict = self._soledad_config.get_hosts()

        if not server_dict.keys():
            # XXX raise more specific exception, and catch it properly!
            raise Exception("No soledad server found")

        selected_server = server_dict[server_dict.keys()[0]]
        server_url = "https://%s:%s/user-%s" % (
            selected_server["hostname"],
            selected_server["port"],
            uuid)
        logger.debug("Using soledad server url: %s" % (server_url,))

        cert_file = self._provider_config.get_ca_cert_path()

        logger.debug('local_db:%s' % (local_db_path,))
        logger.debug('secrets_path:%s' % (secrets_path,))

        try:
            self._try_soledad_init(
                uuid, secrets_path, local_db_path,
                server_url, cert_file, token)
        except:
            # re-raise the exceptions from try_init,
            # we're currently handling the retries from the
            # soledad-launcher in the gui.
            raise

        leap_check(self._soledad is not None,
                   "Null soledad, error while initializing")

        # and now, let's sync
        sync_tries = self.MAX_SYNC_RETRIES
        while sync_tries > 0:
            try:
                self._try_soledad_sync()

                # at this point, sometimes the client
                # gets stuck and does not progress to
                # the _gen_key step. XXX investigate.
                logger.debug("Soledad has been synced.")
                # so long, and thanks for all the fish
                return
            except SoledadSyncError:
                # maybe it's my connection, but I'm getting
                # ssl handshake timeouts and read errors quite often.
                # A particularly big sync is a disaster.
                # This deserves further investigation, maybe the
                # retry strategy can be pushed to u1db, or at least
                # it's something worthy to talk about with the
                # ubuntu folks.
                sync_tries -= 1
                continue

        # reached bottom, failed to sync
        # and there's nothing we can do...
        self.soledad_failed.emit()
        raise SoledadSyncError()

    def _try_soledad_init(self, uuid, secrets_path, local_db_path,
                          server_url, cert_file, auth_token):
        """
        Tries to initialize soledad.

        :param uuid: user identifier
        :param secrets_path: path to secrets file
        :param local_db_path: path to local db file
        :param server_url: soledad server uri
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        # TODO: If selected server fails, retry with another host
        # (issue #3309)
        try:
            self._soledad = Soledad(
                uuid,
                self._password.encode("utf-8"),
                secrets_path=secrets_path,
                local_db_path=local_db_path,
                server_url=server_url,
                cert_file=cert_file,
                auth_token=auth_token)

        # XXX All these errors should be handled by soledad itself,
        # and return a subclass of SoledadInitializationFailed

        # recoverable, will guarantee retries
        except socket.timeout:
            logger.debug("SOLEDAD initialization TIMED OUT...")
            self.soledad_timeout.emit()
        except socket.error as exc:
            logger.error("Socket error while initializing soledad")
            self.soledad_timeout.emit()

        # unrecoverable
        except u1db_errors.Unauthorized:
            logger.error("Error while initializing soledad "
                         "(unauthorized).")
            self.soledad_failed.emit()
        except Exception as exc:
            logger.exception("Unhandled error while initializating "
                             "soledad: %r" % (exc,))
            self.soledad_failed.emit()

    def _try_soledad_sync(self):
        """
        Tries to sync soledad.
        Raises SoledadSyncError if not successful.
        """
        try:
            logger.error("trying to sync soledad....")
            self._soledad.sync()
        except SSLError as exc:
            logger.error("%r" % (exc,))
            raise SoledadSyncError("Failed to sync soledad")
        except Exception as exc:
            logger.exception("Unhandled error while syncing"
                             "soledad: %r" % (exc,))
            self.soledad_failed.emit()
            raise SoledadSyncError("Failed to sync soledad")

    def _download_config(self):
        """
        Downloads the Soledad config for the given provider
        """

        leap_assert(self._provider_config,
                    "We need a provider configuration!")
        logger.debug("Downloading Soledad config for %s" %
                     (self._provider_config.get_domain(),))

        self._soledad_config = SoledadConfig()
        download_service_config(
            self._provider_config,
            self._soledad_config,
            self._session,
            self._download_if_needed)

        # soledad config is ok, let's proceed to load and sync soledad
        # XXX but honestly, this is a pretty strange entry point for that.
        # it feels like it should be the other way around:
        # load_and_sync, and from there, if needed, call download_config
        self.load_and_sync_soledad()

    def _get_gpg_bin_path(self):
        """
        Returns the path to gpg binary.
        :returns: the gpg binary path
        :rtype: str
        """
        # TODO: Fix for Windows
        gpgbin = None
        if flags.STANDALONE:
            gpgbin = os.path.join(
                get_path_prefix(), "..", "apps", "mail", "gpg")
        else:
            try:
                gpgbin_options = which("gpg")
                # gnupg checks that the path to the binary is not a
                # symlink, so we need to filter those and come up with
                # just one option.
                for opt in gpgbin_options:
                    if not os.path.islink(opt):
                        gpgbin = opt
                        break
            except IndexError as e:
                logger.debug("Couldn't find the gpg binary!")
                logger.exception(e)
        leap_check(gpgbin is not None, "Could not find gpg binary")
        return gpgbin

    def _init_keymanager(self, address):
        """
        Initializes the keymanager.
        :param address: the address to initialize the keymanager with.
        :type address: str
        """
        srp_auth = self.srpauth
        logger.debug('initializing keymanager...')
        self._keymanager = KeyManager(
            address,
            "https://nicknym.%s:6425" % (self._provider_config.get_domain(),),
            self._soledad,
            #token=srp_auth.get_token(),  # TODO: enable token usage
            session_id=srp_auth.get_session_id(),
            ca_cert_path=self._provider_config.get_ca_cert_path(),
            api_uri=self._provider_config.get_api_uri(),
            api_version=self._provider_config.get_api_version(),
            uid=srp_auth.get_uid(),
            gpgbinary=self._get_gpg_bin_path())

    def _gen_key(self, _):
        """
        Generates the key pair if needed, uploads it to the webapp and
        nickserver
        """
        leap_assert(self._provider_config is not None,
                    "We need a provider configuration!")
        leap_assert(self._soledad is not None,
                    "We need a non-null soledad to generate keys")

        address = "%s@%s" % (self._user, self._provider_config.get_domain())
        self._init_keymanager(address)
        logger.debug("Retrieving key for %s" % (address,))

        try:
            self._keymanager.get_key(
                address, openpgp.OpenPGPKey, private=True, fetch_remote=False)
            return
        except KeyNotFound:
            logger.debug("Key not found. Generating key for %s" % (address,))

        # generate key
        try:
            self._keymanager.gen_key(openpgp.OpenPGPKey)
        except Exception as exc:
            logger.error("error while generating key!")
            logger.exception(exc)
            raise

        # send key
        try:
            self._keymanager.send_key(openpgp.OpenPGPKey)
        except Exception as exc:
            logger.error("error while sending key!")
            logger.exception(exc)
            raise

        logger.debug("Key generated successfully.")

    def run_soledad_setup_checks(self,
                                 provider_config,
                                 user,
                                 password,
                                 download_if_needed=False):
        """
        Starts the checks needed for a new soledad setup

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig
        :param user: User's login
        :type user: str
        :param password: User's password
        :type password: str
        :param download_if_needed: If True, it will only download
                                   files if the have changed since the
                                   time it was previously downloaded.
        :type download_if_needed: bool
        """
        leap_assert_type(provider_config, ProviderConfig)

        # XXX we should provider a method for setting provider_config
        self._provider_config = provider_config
        self._download_if_needed = download_if_needed
        self._user = user
        self._password = password

        cb_chain = [
            (self._download_config, self.download_config),
            (self._gen_key, self.gen_key)
        ]

        self.addCallbackChain(cb_chain)
class SoledadBootstrapper(AbstractBootstrapper):
    """
    Soledad init procedure.
    """

    SOLEDAD_KEY = "soledad"
    KEYMANAGER_KEY = "keymanager"

    PUBKEY_KEY = "user[public_key]"

    MAX_INIT_RETRIES = 10
    MAX_SYNC_RETRIES = 10
    WAIT_MAX_SECONDS = 600
    # WAIT_STEP_SECONDS = 1
    WAIT_STEP_SECONDS = 5

    def __init__(self, signaler=None):
        AbstractBootstrapper.__init__(self, signaler)

        if signaler is not None:
            self._cancel_signal = signaler.soledad_cancelled_bootstrap

        self._provider_config = None
        self._soledad_config = None
        self._keymanager = None
        self._download_if_needed = False

        self._user = ""
        self._password = ""
        self._address = ""
        self._uuid = ""

        self._srpauth = None
        self._soledad = None

    @property
    def keymanager(self):
        return self._keymanager

    @property
    def soledad(self):
        return self._soledad

    @property
    def srpauth(self):
        if flags.OFFLINE is True:
            return None
        leap_assert(self._provider_config is not None, "We need a provider config")
        return SRPAuth(self._provider_config)

    # initialization

    def load_offline_soledad(self, username, password, uuid):
        """
        Instantiate Soledad for offline use.

        :param username: full user id (user@provider)
        :type username: str or unicode
        :param password: the soledad passphrase
        :type password: unicode
        :param uuid: the user uuid
        :type uuid: str or unicode
        """
        self._address = username
        self._password = password
        self._uuid = uuid
        try:
            self.load_and_sync_soledad(uuid, offline=True)
            self._signaler.signal(self._signaler.soledad_offline_finished)
        except Exception as e:
            # TODO: we should handle more specific exceptions in here
            logger.exception(e)
            self._signaler.signal(self._signaler.soledad_offline_failed)

    def _get_soledad_local_params(self, uuid, offline=False):
        """
        Return the locals parameters needed for the soledad initialization.

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :return: secrets_path, local_db_path, token
        :rtype: tuple
        """
        # in the future, when we want to be able to switch to
        # online mode, this should be a proxy object too.
        # Same for server_url below.

        if offline is False:
            token = self.srpauth.get_token()
        else:
            token = ""

        secrets_path, local_db_path = get_db_paths(uuid)

        logger.debug("secrets_path:%s" % (secrets_path,))
        logger.debug("local_db:%s" % (local_db_path,))
        return (secrets_path, local_db_path, token)

    def _get_soledad_server_params(self, uuid, offline):
        """
        Return the remote parameters needed for the soledad initialization.

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :return: server_url, cert_file
        :rtype: tuple
        """
        if uuid is None:
            uuid = self.srpauth.get_uuid()

        if offline is True:
            server_url = "http://localhost:9999/"
            cert_file = ""
        else:
            server_url = self._pick_server(uuid)
            cert_file = self._provider_config.get_ca_cert_path()

        return server_url, cert_file

    def _soledad_sync_errback(self, failure):
        failure.trap(InvalidAuthTokenError)
        # in the case of an invalid token we have already turned off mail and
        # warned the user in _do_soledad_sync()

    def _do_soledad_init(self, uuid, secrets_path, local_db_path, server_url, cert_file, token):
        """
        Initialize soledad, retry if necessary and raise an exception if we
        can't succeed.

        :param uuid: user identifier
        :type uuid: str
        :param secrets_path: path to secrets file
        :type secrets_path: str
        :param local_db_path: path to local db file
        :type local_db_path: str
        :param server_url: soledad server uri
        :type server_url: str
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        init_tries = 1
        while init_tries <= self.MAX_INIT_RETRIES:
            try:
                logger.debug("Trying to init soledad....")
                self._try_soledad_init(uuid, secrets_path, local_db_path, server_url, cert_file, token)
                logger.debug("Soledad has been initialized.")
                return
            except Exception as exc:
                init_tries += 1
                msg = "Init failed, retrying... (retry {0} of {1})".format(init_tries, self.MAX_INIT_RETRIES)
                logger.warning(msg)
                continue

        logger.exception(exc)
        raise SoledadInitError()

    def load_and_sync_soledad(self, uuid=None, offline=False):
        """
        Once everthing is in the right place, we instantiate and sync
        Soledad

        :param uuid: the uuid of the user, used in offline mode.
        :type uuid: unicode, or None.
        :param offline: whether to instantiate soledad for offline use.
        :type offline: bool
        """
        local_param = self._get_soledad_local_params(uuid, offline)
        remote_param = self._get_soledad_server_params(uuid, offline)

        secrets_path, local_db_path, token = local_param
        server_url, cert_file = remote_param

        try:
            self._do_soledad_init(uuid, secrets_path, local_db_path, server_url, cert_file, token)
        except SoledadInitError:
            # re-raise the exceptions from try_init,
            # we're currently handling the retries from the
            # soledad-launcher in the gui.
            raise

        leap_assert(not sameProxiedObjects(self._soledad, None), "Null soledad, error while initializing")

        if flags.OFFLINE:
            self._init_keymanager(self._address, token)
        else:
            try:
                address = make_address(self._user, self._provider_config.get_domain())
                self._init_keymanager(address, token)
                self._keymanager.get_key(address, openpgp.OpenPGPKey, private=True, fetch_remote=False)
                d = threads.deferToThread(self._do_soledad_sync)
                d.addErrback(self._soledad_sync_errback)
            except KeyNotFound:
                logger.debug("Key not found. Generating key for %s" % (address,))
                self._do_soledad_sync()

    def _pick_server(self, uuid):
        """
        Choose a soledad server to sync against.

        :param uuid: the uuid for the user.
        :type uuid: unicode
        :returns: the server url
        :rtype: unicode
        """
        # TODO: Select server based on timezone (issue #3308)
        server_dict = self._soledad_config.get_hosts()

        if not server_dict.keys():
            # XXX raise more specific exception, and catch it properly!
            raise Exception("No soledad server found")

        selected_server = server_dict[first(server_dict.keys())]
        server_url = "https://%s:%s/user-%s" % (selected_server["hostname"], selected_server["port"], uuid)
        logger.debug("Using soledad server url: %s" % (server_url,))
        return server_url

    def _do_soledad_sync(self):
        """
        Do several retries to get an initial soledad sync.
        """
        # and now, let's sync
        sync_tries = self.MAX_SYNC_RETRIES
        step = self.WAIT_STEP_SECONDS
        max_wait = self.WAIT_MAX_SECONDS
        while sync_tries > 0:
            wait = 0
            try:
                logger.debug("Trying to sync soledad....")
                self._try_soledad_sync()
                while self.soledad.syncing:
                    time.sleep(step)
                    wait += step
                    if wait >= max_wait:
                        raise SoledadSyncError("timeout!")
                logger.debug("Soledad has been synced!")
                # so long, and thanks for all the fish
                return
            except SoledadSyncError:
                # maybe it's my connection, but I'm getting
                # ssl handshake timeouts and read errors quite often.
                # A particularly big sync is a disaster.
                # This deserves further investigation, maybe the
                # retry strategy can be pushed to u1db, or at least
                # it's something worthy to talk about with the
                # ubuntu folks.
                sync_tries += 1
                msg = "Sync failed, retrying... (retry {0} of {1})".format(sync_tries, self.MAX_SYNC_RETRIES)
                logger.warning(msg)
                continue
            except InvalidAuthTokenError:
                self._signaler.signal(self._signaler.soledad_invalid_auth_token)
                raise
            except Exception as e:
                # XXX release syncing lock
                logger.exception("Unhandled error while syncing " "soledad: %r" % (e,))
                break

        raise SoledadSyncError()

    def _try_soledad_init(self, uuid, secrets_path, local_db_path, server_url, cert_file, auth_token):
        """
        Try to initialize soledad.

        :param uuid: user identifier
        :type uuid: str
        :param secrets_path: path to secrets file
        :type secrets_path: str
        :param local_db_path: path to local db file
        :type local_db_path: str
        :param server_url: soledad server uri
        :type server_url: str
        :param cert_file: path to the certificate of the ca used
                          to validate the SSL certificate used by the remote
                          soledad server.
        :type cert_file: str
        :param auth token: auth token
        :type auth_token: str
        """
        # TODO: If selected server fails, retry with another host
        # (issue #3309)
        encoding = sys.getfilesystemencoding()

        # XXX We should get a flag in soledad itself
        if flags.OFFLINE is True:
            Soledad._shared_db = MockSharedDB()
        try:
            self._soledad = Soledad(
                uuid,
                self._password,
                secrets_path=secrets_path.encode(encoding),
                local_db_path=local_db_path.encode(encoding),
                server_url=server_url,
                cert_file=cert_file.encode(encoding),
                auth_token=auth_token,
                defer_encryption=True,
            )

        # XXX All these errors should be handled by soledad itself,
        # and return a subclass of SoledadInitializationFailed

        # recoverable, will guarantee retries
        except (socket.timeout, socket.error, BootstrapSequenceError):
            logger.warning("Error while initializing Soledad")
            raise

        # unrecoverable
        except (u1db_errors.Unauthorized, u1db_errors.HTTPError):
            logger.error("Error while initializing Soledad (u1db error).")
            raise
        except Exception as exc:
            logger.exception("Unhandled error while initializating " "Soledad: %r" % (exc,))
            raise

    def _try_soledad_sync(self):
        """
        Try to sync soledad.
        Raises SoledadSyncError if not successful.
        """
        try:
            logger.debug("BOOTSTRAPPER: trying to sync Soledad....")
            # pass defer_decryption=False to get inline decryption
            # for debugging.
            self._soledad.sync(defer_decryption=True)
        except SSLError as exc:
            logger.error("%r" % (exc,))
            raise SoledadSyncError("Failed to sync soledad")
        except u1db_errors.InvalidGeneration as exc:
            logger.error("%r" % (exc,))
            raise SoledadSyncError("u1db: InvalidGeneration")
        except (sqlite_ProgrammingError, sqlcipher_ProgrammingError) as e:
            logger.exception("%r" % (e,))
            raise
        except InvalidAuthTokenError:
            # token is invalid, probably expired
            logger.error("Invalid auth token while trying to sync Soledad")
            raise
        except Exception as exc:
            logger.exception("Unhandled error while syncing " "soledad: %r" % (exc,))
            raise SoledadSyncError("Failed to sync soledad")

    def _download_config(self):
        """
        Download the Soledad config for the given provider
        """
        leap_assert(self._provider_config, "We need a provider configuration!")
        logger.debug("Downloading Soledad config for %s" % (self._provider_config.get_domain(),))

        self._soledad_config = SoledadConfig()
        download_service_config(self._provider_config, self._soledad_config, self._session, self._download_if_needed)

    def _get_gpg_bin_path(self):
        """
        Return the path to gpg binary.

        :returns: the gpg binary path
        :rtype: str
        """
        gpgbin = None
        if flags.STANDALONE:
            gpgbin = os.path.join(get_path_prefix(), "..", "apps", "mail", "gpg")
            if IS_WIN:
                gpgbin += ".exe"
        else:
            try:
                gpgbin_options = which("gpg")
                # gnupg checks that the path to the binary is not a
                # symlink, so we need to filter those and come up with
                # just one option.
                for opt in gpgbin_options:
                    if not os.path.islink(opt):
                        gpgbin = opt
                        break
            except IndexError as e:
                logger.debug("Couldn't find the gpg binary!")
                logger.exception(e)
        leap_check(gpgbin is not None, "Could not find gpg binary")
        return gpgbin

    def _init_keymanager(self, address, token):
        """
        Initialize the keymanager.

        :param address: the address to initialize the keymanager with.
        :type address: str
        :param token: the auth token for accessing webapp.
        :type token: str
        """
        srp_auth = self.srpauth
        logger.debug("initializing keymanager...")

        if flags.OFFLINE is True:
            args = (address, "https://localhost", self._soledad)
            kwargs = {
                "ca_cert_path": "",
                "api_uri": "",
                "api_version": "",
                "uid": self._uuid,
                "gpgbinary": self._get_gpg_bin_path(),
            }
        else:
            args = (address, "https://nicknym.%s:6425" % (self._provider_config.get_domain(),), self._soledad)
            kwargs = {
                "token": token,
                "ca_cert_path": self._provider_config.get_ca_cert_path(),
                "api_uri": self._provider_config.get_api_uri(),
                "api_version": self._provider_config.get_api_version(),
                "uid": srp_auth.get_uuid(),
                "gpgbinary": self._get_gpg_bin_path(),
            }
        try:
            self._keymanager = KeyManager(*args, **kwargs)
        except KeyNotFound:
            logger.debug("key for %s not found." % address)
        except Exception as exc:
            logger.exception(exc)
            raise

        if flags.OFFLINE is False:
            # make sure key is in server
            logger.debug("Trying to send key to server...")
            try:
                self._keymanager.send_key(openpgp.OpenPGPKey)
            except KeyNotFound:
                logger.debug("No key found for %s, will generate soon." % address)
            except Exception as exc:
                logger.error("Error sending key to server.")
                logger.exception(exc)
                # but we do not raise

    def _gen_key(self):
        """
        Generates the key pair if needed, uploads it to the webapp and
        nickserver
        """
        leap_assert(self._provider_config is not None, "We need a provider configuration!")
        leap_assert(self._soledad is not None, "We need a non-null soledad to generate keys")

        address = make_address(self._user, self._provider_config.get_domain())
        logger.debug("Retrieving key for %s" % (address,))

        try:
            self._keymanager.get_key(address, openpgp.OpenPGPKey, private=True, fetch_remote=False)
            return
        except KeyNotFound:
            logger.debug("Key not found. Generating key for %s" % (address,))

        # generate key
        try:
            self._keymanager.gen_key(openpgp.OpenPGPKey)
        except Exception as exc:
            logger.error("Error while generating key!")
            logger.exception(exc)
            raise

        # send key
        try:
            self._keymanager.send_key(openpgp.OpenPGPKey)
        except Exception as exc:
            logger.error("Error while sending key!")
            logger.exception(exc)
            raise

        logger.debug("Key generated successfully.")

    def run_soledad_setup_checks(self, provider_config, user, password, download_if_needed=False):
        """
        Starts the checks needed for a new soledad setup

        :param provider_config: Provider configuration
        :type provider_config: ProviderConfig
        :param user: User's login
        :type user: unicode
        :param password: User's password
        :type password: unicode
        :param download_if_needed: If True, it will only download
                                   files if the have changed since the
                                   time it was previously downloaded.
        :type download_if_needed: bool
        """
        leap_assert_type(provider_config, ProviderConfig)

        # XXX we should provider a method for setting provider_config
        self._provider_config = provider_config
        self._download_if_needed = download_if_needed
        self._user = user
        self._password = password

        if flags.OFFLINE:
            signal_finished = self._signaler.soledad_offline_finished
            signal_failed = self._signaler.soledad_offline_failed
        else:
            signal_finished = self._signaler.soledad_bootstrap_finished
            signal_failed = self._signaler.soledad_bootstrap_failed

        try:
            self._download_config()

            # soledad config is ok, let's proceed to load and sync soledad
            uuid = self.srpauth.get_uuid()
            self.load_and_sync_soledad(uuid)

            if not flags.OFFLINE:
                self._gen_key()

            self._signaler.signal(signal_finished)
        except Exception as e:
            # TODO: we should handle more specific exceptions in here
            self._soledad = None
            self._keymanager = None
            logger.exception("Error while bootstrapping Soledad: %r" % (e,))
            self._signaler.signal(signal_failed)