Ejemplo n.º 1
0
    def from_db(cls, profile, store_password=True, include_settings=True):
        profile_dict = model_to_dict(profile, exclude=[RepoModel.id])  # Have to retain profile ID

        keyring = VortaKeyring.get_keyring()
        if store_password:
            profile_dict['password'] = keyring.get_password('vorta-repo', profile.repo.url)

        # For all below, exclude ids to prevent collisions. DB will automatically reassign ids
        # Add SourceFileModel
        profile_dict['SourceFileModel'] = [
            model_to_dict(
                source,
                recurse=False, exclude=[SourceFileModel.id]) for source in SourceFileModel.select().where(
                SourceFileModel.profile == profile)]
        # Add SchemaVersion
        profile_dict['SchemaVersion'] = model_to_dict(SchemaVersion.get(id=1))

        if include_settings:
            # Add WifiSettingModel
            profile_dict['WifiSettingModel'] = [
                model_to_dict(
                    wifi, recurse=False) for wifi in WifiSettingModel.select().where(
                    WifiSettingModel.profile == profile.id)]
            # Add SettingsModel
            profile_dict['SettingsModel'] = [
                model_to_dict(s, exclude=[SettingsModel.id]) for s in SettingsModel]
        return ProfileExport(profile_dict)
Ejemplo n.º 2
0
def test_repo_add_success(qapp, qtbot, mocker, borg_json_output):
    # Add new repo window
    main = qapp.main_window
    main.repoTab.repoSelector.setCurrentIndex(1)
    add_repo_window = main.repoTab._window
    test_repo_url = f'vorta-test-repo.{uuid.uuid4()}.com:repo'  # Random repo URL to avoid macOS keychain

    qtbot.keyClicks(add_repo_window.repoURL, test_repo_url)
    qtbot.keyClicks(add_repo_window.passwordLineEdit, LONG_PASSWORD)
    qtbot.keyClicks(add_repo_window.confirmLineEdit, LONG_PASSWORD)

    stdout, stderr = borg_json_output('info')
    popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
    mocker.patch.object(vorta.borg.borg_job,
                        'Popen',
                        return_value=popen_result)

    add_repo_window.run()
    qtbot.waitUntil(
        lambda: EventLogModel.select().where(EventLogModel.returncode == 0).
        count() == 2, **pytest._wait_defaults)

    assert RepoModel.get(id=2).url == test_repo_url

    keyring = VortaKeyring.get_keyring()
    assert keyring.get_password("vorta-repo",
                                RepoModel.get(id=2).url) == LONG_PASSWORD
    assert main.repoTab.repoSelector.currentText() == test_repo_url
Ejemplo n.º 3
0
def test_repo_add_success(qapp, qtbot, mocker, borg_json_output):
    # Add new repo window
    main = qapp.main_window
    main.repoTab.repoSelector.setCurrentIndex(1)
    add_repo_window = main.repoTab._window
    test_repo_url = f'vorta-test-repo.{uuid.uuid4()}.com:repo'  # Random repo URL to avoid macOS keychain

    qtbot.keyClicks(add_repo_window.repoURL, test_repo_url)
    qtbot.keyClicks(add_repo_window.passwordLineEdit, LONG_PASSWORD)
    qtbot.keyClicks(add_repo_window.confirmLineEdit, LONG_PASSWORD)

    stdout, stderr = borg_json_output('info')
    popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0)
    mocker.patch.object(vorta.borg.borg_thread, 'Popen', return_value=popen_result)

    qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton)

    with qtbot.waitSignal(add_repo_window.thread.result, timeout=3000) as _:
        pass

    assert EventLogModel.select().count() == 2
    assert RepoModel.get(id=2).url == test_repo_url

    keyring = VortaKeyring.get_keyring()
    assert keyring.get_password("vorta-repo", RepoModel.get(id=2).url) == LONG_PASSWORD
    assert main.repoTab.repoSelector.currentText() == test_repo_url
Ejemplo n.º 4
0
def test_keyring(qapp):
    UNICODE_PW = 'kjalsdfüadsfäadsfß'
    REPO = f'vorta-test-repo.{uuid.uuid4()}.com:repo'  # Random repo URL

    keyring = VortaKeyring.get_keyring()
    keyring.set_password('vorta-repo', REPO, UNICODE_PW)
    assert keyring.get_password("vorta-repo", REPO) == UNICODE_PW
Ejemplo n.º 5
0
 def set_password(self, URL):
     ''' Autofill password from keyring only if current entry is empty '''
     password = VortaKeyring.get_keyring().get_password('vorta-repo', URL)
     if password and self.passwordLineEdit.text() == "":
         self.passwordLabel.setText(self.tr("Autofilled password from password manager."))
         self.passwordLineEdit.setText(password)
         if self.__class__ == AddRepoWindow:
             self.confirmLineEdit.setText(password)
Ejemplo n.º 6
0
def test_password_autofill(qapp, qtbot):
    main = qapp.main_window
    main.repoTab.repoSelector.setCurrentIndex(1)
    add_repo_window = main.repoTab._window
    test_repo_url = f'vorta-test-repo.{uuid.uuid4()}.com:repo'  # Random repo URL to avoid macOS keychain

    keyring = VortaKeyring.get_keyring()
    password = str(uuid.uuid4())
    keyring.set_password('vorta-repo', test_repo_url, password)

    qtbot.keyClicks(add_repo_window.repoURL, test_repo_url)

    assert(add_repo_window.passwordLineEdit.text() == password)
Ejemplo n.º 7
0
def display_password_backend(encryption):
    ''' Display password backend message based off current keyring '''
    # flake8: noqa E501
    if encryption != 'none':
        keyring = VortaKeyring.get_keyring()
        return trans_late(
            'utils', "Storing the password in your password manager."
        ) if keyring.is_primary else trans_late(
            'utils',
            'Saving the password to disk. To store password more securely install a supported secret store such as KeepassXC'
        )
    else:
        return ""
Ejemplo n.º 8
0
 def init_repo_password_field(self, profile_export):
     """Try to prefill the borg passphrase either from the export or from the keyring."""
     self.repoPassword.textChanged[str].connect(self.on_repo_password_changed)
     if profile_export.repo_password:
         self.repoPassword.setText(profile_export.repo_password)
         self.repoPassword.setDisabled(True)
         self.repoPassword.setToolTip(self.tr('The passphrase has been loaded from the export file'))
     elif profile_export.repo_url:
         keyring = VortaKeyring.get_keyring()
         repo_password = keyring.get_password('vorta-repo', profile_export.repo_url)
         if repo_password:
             self.repoPassword.setText(repo_password)
             self.repoPassword.setDisabled(True)
             self.repoPassword.setToolTip(self.tr('The passphrase has been loaded from your keyring'))
Ejemplo n.º 9
0
    def __init__(self, profile):
        """
        @type profile: BackupProfileModel
        """
        super().__init__()
        self.profile = profile
        self.setupUi(self)
        self.setWindowTitle(self.tr("Export Profile"))
        self.buttonBox.accepted.connect(self.run)
        self.buttonBox.rejected.connect(self.reject)

        self.keyring = VortaKeyring.get_keyring()
        profile = self.profile
        if profile.repo is None or self.keyring.get_password(
                'vorta-repo', profile.repo.url) is None:
            self.storePassword.setCheckState(False)
            self.storePassword.setDisabled(True)
            self.storePassword.setToolTip(
                self.tr('The current profile_export has no password'))
Ejemplo n.º 10
0
from collections import defaultdict
from functools import reduce
import operator
import psutil

from paramiko.rsakey import RSAKey
from paramiko.ecdsakey import ECDSAKey
from paramiko.ed25519key import Ed25519Key
from paramiko import SSHException
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtGui import QIcon
from PyQt5 import QtCore
from vorta.keyring.abc import VortaKeyring
from vorta.log import logger

keyring = VortaKeyring.get_keyring()
logger.info('Using %s Keyring implementation.', keyring.__class__.__name__)


def nested_dict():
    """
    Combination of two idioms to quickly build dicts from lists of keys:

    - https://stackoverflow.com/a/16724937/3983708
    - https://stackoverflow.com/a/14692747/3983708
    """
    return defaultdict(nested_dict)


def get_dict_from_list(dataDict, mapList):
    return reduce(operator.getitem, mapList, dataDict)
Ejemplo n.º 11
0
    def prepare(cls, profile):
        """
        Prepare for running Borg. This function in the base class should be called from all
        subclasses and calls that define their own `cmd`.

        The `prepare()` step does these things:
        - validate if all conditions to run command are met
        - build borg command

        `prepare()` is run 2x. First at the global level and then for each subcommand.

        :return: dict(ok: book, message: str)
        """
        ret = {'ok': False}

        if cls.prepare_bin() is None:
            ret['message'] = trans_late('messages', 'Borg binary was not found.')
            return ret

        if profile.repo is None:
            ret['message'] = trans_late('messages', 'Add a backup repository first.')
            return ret

        if not borg_compat.check('JSON_LOG'):
            ret['message'] = trans_late('messages', 'Your Borg version is too old. >=1.1.0 is required.')
            return ret

        # Try to get password from chosen keyring backend.
        with keyring_lock:
            cls.keyring = VortaKeyring.get_keyring()
            logger.debug("Using %s keyring to store passwords.", cls.keyring.__class__.__name__)
            ret['password'] = cls.keyring.get_password('vorta-repo', profile.repo.url)

            # Check if keyring is locked
            if profile.repo.encryption != 'none' and not cls.keyring.is_unlocked:
                ret['message'] = trans_late('messages',
                                            'Please unlock your system password manager or disable it under Misc')
                return ret

            # Try to fall back to DB Keyring, if we use the system keychain.
            if ret['password'] is None and cls.keyring.is_system:
                logger.debug('Password not found in primary keyring. Falling back to VortaDBKeyring.')
                ret['password'] = VortaDBKeyring().get_password('vorta-repo', profile.repo.url)

                # Give warning and continue if password is found there.
                if ret['password'] is not None:
                    logger.warning('Found password in database, but secure storage was available. '
                                   'Consider re-adding the repo to use it.')

        # Password is required for encryption, cannot continue
        if ret['password'] is None and not isinstance(profile.repo, FakeRepo) and profile.repo.encryption != 'none':
            ret['message'] = trans_late(
                'messages', "Your repo passphrase was stored in a password manager which is no longer available.\n"
                "Try unlinking and re-adding your repo.")
            return ret

        ret['ssh_key'] = profile.ssh_key
        ret['repo_id'] = profile.repo.id
        ret['repo_url'] = profile.repo.url
        ret['extra_borg_arguments'] = profile.repo.extra_borg_arguments
        ret['profile_name'] = profile.name
        ret['profile_id'] = profile.id

        ret['ok'] = True

        return ret
Ejemplo n.º 12
0
 def display_backend_warning(self):
     '''Display password backend message based off current keyring'''
     if self.encryptionComboBox.currentData() != 'none':
         self.passwordLabel.setText(
             VortaKeyring.get_keyring().get_backend_warning())
Ejemplo n.º 13
0
    def to_db(self, overwrite_profile=False, overwrite_settings=True):
        profile_schema = self._profile_dict['SchemaVersion']['version']
        keyring = VortaKeyring.get_keyring()
        if SCHEMA_VERSION < profile_schema:
            raise VersionException()
        elif SCHEMA_VERSION > profile_schema:
            # Add model upgrading code here, only needed if not adding columns
            if profile_schema < 16:
                for sourcedir in self._profile_dict['SourceFileModel']:
                    sourcedir['dir_files_count'] = -1
                    sourcedir['dir_size'] = -1
                    sourcedir['path_isdir'] = False

        existing_profile = None
        if overwrite_profile:
            existing_profile = BackupProfileModel.get_or_none(BackupProfileModel.name == self.name)
            if existing_profile:
                self._profile_dict['id'] = existing_profile.id
        if not overwrite_profile or not existing_profile:
            # Guarantee uniqueness of ids
            while BackupProfileModel.get_or_none(BackupProfileModel.id == self.id) is not None:
                self._profile_dict['id'] += 1

            # Add suffix incase names are the same
            if BackupProfileModel.get_or_none(BackupProfileModel.name == self.name) is not None:
                suffix = 1
                while BackupProfileModel.get_or_none(BackupProfileModel.name == f"{self.name}-{suffix}") is not None:
                    suffix += 1
                self._profile_dict['name'] = f"{self.name}-{suffix}"

        # Load existing repo or restore it
        if self._profile_dict['repo']:
            repo = RepoModel.get_or_none(RepoModel.url == self.repo_url)
            if repo is None:
                # Load repo from export
                repo = dict_to_model(RepoModel, self._profile_dict['repo'])
                repo.save(force_insert=True)
            self._profile_dict['repo'] = model_to_dict(repo)

        if self.repo_password:
            keyring.set_password('vorta-repo', self.repo_url, self.repo_password)
            del self._profile_dict['password']

        # Delete and recreate the tables to clear them
        if overwrite_settings:
            db.drop_tables([SettingsModel, WifiSettingModel])
            db.create_tables([SettingsModel, WifiSettingModel])
            SettingsModel.insert_many(self._profile_dict['SettingsModel']).execute()
            WifiSettingModel.insert_many(self._profile_dict['WifiSettingModel']).execute()

        # Set the profile ids to be match new profile
        for source in self._profile_dict['SourceFileModel']:
            source['profile'] = self.id
        SourceFileModel.insert_many(self._profile_dict['SourceFileModel']).execute()

        # Delete added dictionaries to make it match BackupProfileModel
        del self._profile_dict['SettingsModel']
        del self._profile_dict['SourceFileModel']
        del self._profile_dict['WifiSettingModel']
        del self._profile_dict['SchemaVersion']

        # dict to profile
        new_profile = dict_to_model(BackupProfileModel, self._profile_dict)
        if overwrite_profile and existing_profile:
            force_insert = False
        else:
            force_insert = True
        new_profile.save(force_insert=force_insert)
        init_db()  # rerun db init code to perform the same operations on the new as as on application boot
        return new_profile