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())
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
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)
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 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
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
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)
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"
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
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
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
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)
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, )
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)
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
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)
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)
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)
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)
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)
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)
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
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))
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
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")
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)
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)