mirror of https://github.com/borgbase/vorta
Remove keyring dependency. Fixes #190. By @Hofer-Julian
* Implement secretstorage backend for DBus connection * Use newer Ubuntu version for Travis CI testing
This commit is contained in:
parent
b115aef19b
commit
a1d41612b1
|
@ -6,6 +6,7 @@ dist/
|
|||
docs/
|
||||
*.autosave
|
||||
__pycache__
|
||||
.pytest_cache
|
||||
.eggs
|
||||
vorta.egg-info
|
||||
.coverage
|
||||
|
|
24
.travis.yml
24
.travis.yml
|
@ -1,4 +1,6 @@
|
|||
language: generic
|
||||
sudo: required
|
||||
dist: xenial
|
||||
|
||||
addons:
|
||||
apt:
|
||||
|
@ -10,6 +12,7 @@ addons:
|
|||
- libacl1-dev
|
||||
- libacl1
|
||||
- build-essential
|
||||
- libxkbcommon-x11-0
|
||||
homebrew:
|
||||
update: false
|
||||
packages:
|
||||
|
@ -27,9 +30,6 @@ cache:
|
|||
- $HOME/.pyenv/versions
|
||||
- $HOME/Library/Caches/Homebrew
|
||||
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
env:
|
||||
global:
|
||||
- SETUP_XVFB=true
|
||||
|
@ -39,9 +39,9 @@ env:
|
|||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: trusty
|
||||
dist: xenial
|
||||
env:
|
||||
- RUN_PYINSTALLER=true
|
||||
- RUN_PYINSTALLER=true
|
||||
- os: osx
|
||||
env:
|
||||
- RUN_PYINSTALLER=true
|
||||
|
@ -52,17 +52,13 @@ install:
|
|||
export DISPLAY=:99.0
|
||||
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX +render -noreset
|
||||
sleep 3
|
||||
# cd /opt/pyenv && git pull origin master
|
||||
pyenv install -s $PYTHON36
|
||||
eval "$(pyenv init -)"
|
||||
pyenv shell $PYTHON36
|
||||
elif [ $TRAVIS_OS_NAME = "osx" ]; then
|
||||
brew upgrade pyenv
|
||||
pyenv install -s $PYTHON37
|
||||
pyenv install -s $PYTHON36
|
||||
eval "$(pyenv init -)"
|
||||
pyenv shell $PYTHON36 $PYTHON37
|
||||
fi
|
||||
pyenv install -s $PYTHON37
|
||||
pyenv install -s $PYTHON36
|
||||
eval "$(pyenv init -)"
|
||||
pyenv shell $PYTHON36 $PYTHON37
|
||||
|
||||
- pip install -U setuptools pip==18.1
|
||||
- pip install .
|
||||
|
@ -70,7 +66,7 @@ install:
|
|||
- pip install -r requirements.d/dev.txt
|
||||
|
||||
before_script:
|
||||
- if [ $TRAVIS_OS_NAME = "linux" ]; then (herbstluftwm )& fi
|
||||
- if [ $TRAVIS_OS_NAME = "linux" ]; then (herbstluftwm)& fi
|
||||
- sleep 3
|
||||
|
||||
script:
|
||||
|
|
|
@ -36,10 +36,10 @@ install_requires =
|
|||
pyqt5
|
||||
peewee
|
||||
python-dateutil
|
||||
keyring
|
||||
apscheduler
|
||||
psutil
|
||||
qdarkstyle
|
||||
secretstorage; sys_platform != 'darwin'
|
||||
pyobjc-core; sys_platform == 'darwin'
|
||||
pyobjc-framework-Cocoa; sys_platform == 'darwin'
|
||||
pyobjc-framework-LaunchServices; sys_platform == 'darwin'
|
||||
|
@ -83,7 +83,6 @@ deps =
|
|||
pytest-mock
|
||||
pytest-xdist
|
||||
pytest-faulthandler
|
||||
PyQt5==5.11.3
|
||||
commands=pytest
|
||||
passenv = DISPLAY
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ from subprocess import Popen, PIPE
|
|||
from vorta.i18n import trans_late
|
||||
from vorta.models import EventLogModel, BackupProfileMixin
|
||||
from vorta.utils import keyring
|
||||
from vorta.keyring.db import VortaDBKeyring
|
||||
|
||||
mutex = QtCore.QMutex()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -109,11 +110,18 @@ class BorgThread(QtCore.QThread, BackupProfileMixin):
|
|||
return ret
|
||||
|
||||
# Try to get password from chosen keyring backend.
|
||||
try:
|
||||
ret['password'] = keyring.get_password("vorta-repo", profile.repo.url)
|
||||
except Exception:
|
||||
ret['message'] = trans_late('messages', 'Please make sure you grant Vorta permission to use the Keychain.')
|
||||
return ret
|
||||
logger.debug("Using %s keyring to store passwords.", keyring.__class__.__name__)
|
||||
ret['password'] = keyring.get_password('vorta-repo', profile.repo.url)
|
||||
|
||||
# Try to fall back to DB Keyring, if we use the system keychain.
|
||||
if ret['password'] is None and keyring.is_primary:
|
||||
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.')
|
||||
|
||||
ret['ssh_key'] = profile.ssh_key
|
||||
ret['repo_id'] = profile.repo.id
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
"""
|
||||
Set the most appropriate Keyring backend for the current system.
|
||||
For Linux not every system has SecretService available, so it will
|
||||
fall back to a simple database keystore if needed.
|
||||
"""
|
||||
import sys
|
||||
|
||||
|
||||
class VortaKeyring:
|
||||
@classmethod
|
||||
def get_keyring(cls):
|
||||
if sys.platform == 'darwin': # Use Keychain on macOS
|
||||
from .darwin import VortaDarwinKeyring
|
||||
return VortaDarwinKeyring()
|
||||
else: # Try to use DBus (available on Linux and *BSD)
|
||||
import secretstorage
|
||||
from .secretstorage import VortaSecretStorageKeyring
|
||||
try:
|
||||
return VortaSecretStorageKeyring()
|
||||
except secretstorage.SecretServiceNotAvailableException: # Save passwords in DB, if all else fails.
|
||||
from .db import VortaDBKeyring
|
||||
return VortaDBKeyring()
|
||||
|
||||
def set_password(self, service, repo_url, password):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_password(self, service, repo_url):
|
||||
"""
|
||||
Retrieve a password from the underlying store. Return None if not found.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def is_primary(self):
|
||||
"""
|
||||
Return True if the current subclass is the system's primary keychain mechanism,
|
||||
rather than a fallback (like our own VortaDBKeyring).
|
||||
"""
|
||||
return True
|
|
@ -8,10 +8,10 @@ objc modules.
|
|||
Adapted from https://gist.github.com/apettinen/5dc7bf1f6a07d148b2075725db6b1950
|
||||
"""
|
||||
|
||||
from keyring.backend import KeyringBackend
|
||||
from .abc import VortaKeyring
|
||||
|
||||
|
||||
class VortaDarwinKeyring(KeyringBackend):
|
||||
class VortaDarwinKeyring(VortaKeyring):
|
||||
"""Homemade macOS Keychain Service"""
|
||||
|
||||
login_keychain = None
|
||||
|
@ -42,10 +42,6 @@ class VortaDarwinKeyring(KeyringBackend):
|
|||
result, login_keychain = SecKeychainOpen(b'login.keychain', None)
|
||||
self.login_keychain = login_keychain
|
||||
|
||||
@classmethod
|
||||
def priority(cls):
|
||||
return 5
|
||||
|
||||
def set_password(self, service, repo_url, password):
|
||||
if not self.login_keychain: self._set_keychain()
|
||||
|
||||
|
@ -67,9 +63,6 @@ class VortaDarwinKeyring(KeyringBackend):
|
|||
password = _resolve_password(password_length, password_buffer)
|
||||
return password
|
||||
|
||||
def delete_password(self, service, repo_url):
|
||||
pass
|
||||
|
||||
|
||||
def _resolve_password(password_length, password_buffer):
|
||||
from ctypes import c_char
|
|
@ -1,18 +1,16 @@
|
|||
import keyring
|
||||
import peewee
|
||||
from .abc import VortaKeyring
|
||||
|
||||
|
||||
class VortaDBKeyring(keyring.backend.KeyringBackend):
|
||||
class VortaDBKeyring(VortaKeyring):
|
||||
"""
|
||||
Our own fallback keyring service. Uses the main database
|
||||
to store repo passwords if no other (more secure) backend
|
||||
is available.
|
||||
"""
|
||||
@classmethod
|
||||
def priority(cls):
|
||||
return 5
|
||||
|
||||
def set_password(self, service, repo_url, password):
|
||||
from .models import RepoPassword
|
||||
from vorta.models import RepoPassword
|
||||
keyring_entry, created = RepoPassword.get_or_create(
|
||||
url=repo_url,
|
||||
defaults={'password': password}
|
||||
|
@ -21,12 +19,13 @@ class VortaDBKeyring(keyring.backend.KeyringBackend):
|
|||
keyring_entry.save()
|
||||
|
||||
def get_password(self, service, repo_url):
|
||||
from .models import RepoPassword
|
||||
from vorta.models import RepoPassword
|
||||
try:
|
||||
keyring_entry = RepoPassword.get(url=repo_url)
|
||||
return keyring_entry.password
|
||||
except Exception:
|
||||
except peewee.DoesNotExist:
|
||||
return None
|
||||
|
||||
def delete_password(self, service, repo_url):
|
||||
pass
|
||||
@property
|
||||
def is_primary(self):
|
||||
return False
|
|
@ -0,0 +1,32 @@
|
|||
import secretstorage
|
||||
import asyncio
|
||||
from vorta.keyring.abc import VortaKeyring
|
||||
from vorta.log import logger
|
||||
|
||||
|
||||
class VortaSecretStorageKeyring(VortaKeyring):
|
||||
"""A wrapper for the secretstorage package to support the custom keyring backend"""
|
||||
|
||||
def __init__(self):
|
||||
self.connection = secretstorage.dbus_init()
|
||||
|
||||
def set_password(self, service, repo_url, password):
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
collection = secretstorage.get_default_collection(self.connection)
|
||||
attributes = {
|
||||
'application': 'Vorta',
|
||||
'service': service,
|
||||
'repo_url': repo_url,
|
||||
'xdg:schema': 'org.freedesktop.Secret.Generic'}
|
||||
collection.create_item(repo_url, attributes, password, replace=True)
|
||||
|
||||
def get_password(self, service, repo_url):
|
||||
collection = secretstorage.get_default_collection(self.connection)
|
||||
if collection.is_locked():
|
||||
collection.unlock()
|
||||
attributes = {'application': 'Vorta', 'service': service, 'repo_url': repo_url}
|
||||
items = list(collection.search_items(attributes))
|
||||
logger.debug('Found %i passwords matching repo URL.', len(items))
|
||||
if len(items) > 0:
|
||||
return items[0].get_secret().decode("utf-8")
|
||||
return None
|
|
@ -7,6 +7,7 @@ import unicodedata
|
|||
import re
|
||||
from datetime import datetime as dt
|
||||
import getpass
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
from functools import reduce
|
||||
import operator
|
||||
|
@ -19,37 +20,9 @@ from paramiko import SSHException
|
|||
from PyQt5.QtWidgets import QFileDialog
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5 import QtCore
|
||||
import subprocess
|
||||
import keyring
|
||||
from vorta.keyring_db import VortaDBKeyring
|
||||
from vorta.keyring.abc import VortaKeyring
|
||||
|
||||
|
||||
"""
|
||||
Set the most appropriate Keyring backend for the current system.
|
||||
|
||||
For macOS we use our own implementation due to conflicts between
|
||||
Keyring and the autostart code.
|
||||
|
||||
For Linux not every system has SecretService available, so it will
|
||||
fall back to a simple database keystore if needed.
|
||||
"""
|
||||
if sys.platform == 'darwin':
|
||||
# from keyring.backends import OS_X
|
||||
# keyring.set_keyring(OS_X.Keyring())
|
||||
from vorta.keyring_darwin import VortaDarwinKeyring
|
||||
keyring.set_keyring(VortaDarwinKeyring())
|
||||
elif sys.platform == 'win32':
|
||||
from keyring.backends import Windows
|
||||
keyring.set_keyring(Windows.WinVaultKeyring())
|
||||
elif sys.platform == 'linux':
|
||||
from keyring.backends import SecretService
|
||||
try:
|
||||
SecretService.Keyring.priority() # Test if keyring works.
|
||||
keyring.set_keyring(SecretService.Keyring())
|
||||
except Exception:
|
||||
keyring.set_keyring(VortaDBKeyring())
|
||||
else: # Fall back to saving password to database.
|
||||
keyring.set_keyring(VortaDBKeyring())
|
||||
keyring = VortaKeyring.get_keyring()
|
||||
|
||||
|
||||
def nested_dict():
|
||||
|
|
Loading…
Reference in New Issue