mirror of https://github.com/borgbase/vorta
V0.5.4 - Fix macOS autostart implementation (#82)
* Add own macOS Keychain implementation to avoid conflict with autostart. #81 * Resolve conflight with pytest-xdist and pyobjc. * Move requirements.txt files to own folder.
This commit is contained in:
commit
97fcf08934
|
@ -67,7 +67,7 @@ install:
|
|||
- pip install -U setuptools pip
|
||||
- pip install .
|
||||
- pip install borgbackup
|
||||
- pip install -r requirements-dev.txt
|
||||
- pip install -r requirements.d/dev.txt
|
||||
|
||||
before_script:
|
||||
- if [ $TRAVIS_OS_NAME = "linux" ]; then (herbstluftwm )& fi
|
||||
|
|
|
@ -51,7 +51,7 @@ $ vorta
|
|||
|
||||
Install developer packages we use (pytest, tox, pyinstaller):
|
||||
```
|
||||
pip install -r requirements-dev.txt
|
||||
pip install -r requirements.d/dev.txt
|
||||
```
|
||||
|
||||
Qt Creator is used to edit views. Install from [their site](https://www.qt.io/download) or using Homebrew and then open the .ui files in `vorta/UI`:
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
name: vorta
|
||||
|
||||
channels:
|
||||
- conda-forge
|
||||
- defaults
|
||||
|
||||
dependencies:
|
||||
- python=3.6.7
|
||||
- appdirs
|
||||
- paramiko
|
||||
- pyqt
|
||||
- peewee
|
||||
- python-dateutil
|
||||
- keyring
|
||||
- apscheduler
|
||||
- sentry-sdk
|
||||
- psutil
|
||||
- pyobjc-core
|
||||
- pyobjc-framework-Cocoa
|
||||
- pyinstaller
|
||||
- pip:
|
||||
- sentry-sdk
|
||||
|
|
@ -41,6 +41,7 @@ install_requires =
|
|||
psutil
|
||||
pyobjc-core; sys_platform == 'darwin'
|
||||
pyobjc-framework-Cocoa; sys_platform == 'darwin'
|
||||
pyobjc-framework-LaunchServices; sys_platform == 'darwin'
|
||||
tests_require =
|
||||
pytest
|
||||
pytest-qt
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
# flake8: noqa
|
||||
|
||||
"""
|
||||
A dirty objc implementation to access the macOS Keychain. Because the
|
||||
keyring implementation was causing trouble when used together with other
|
||||
objc modules.
|
||||
|
||||
Adapted from https://gist.github.com/apettinen/5dc7bf1f6a07d148b2075725db6b1950
|
||||
"""
|
||||
|
||||
from keyring.backend import KeyringBackend
|
||||
|
||||
|
||||
class VortaDarwinKeyring(KeyringBackend):
|
||||
"""Homemade macOS Keychain Service"""
|
||||
|
||||
login_keychain = None
|
||||
|
||||
def _set_keychain(self):
|
||||
"""
|
||||
Lazy import to avoid conflict with pytest-xdist.
|
||||
"""
|
||||
import objc
|
||||
from Foundation import NSBundle
|
||||
Security = NSBundle.bundleWithIdentifier_('com.apple.security')
|
||||
|
||||
S_functions = [
|
||||
('SecKeychainGetTypeID', b'I'),
|
||||
('SecKeychainItemGetTypeID', b'I'),
|
||||
('SecKeychainAddGenericPassword', b'i^{OpaqueSecKeychainRef=}I*I*I*o^^{OpaqueSecKeychainItemRef}'),
|
||||
('SecKeychainOpen', b'i*o^^{OpaqueSecKeychainRef}'),
|
||||
('SecKeychainFindGenericPassword', b'i@I*I*o^Io^^{OpaquePassBuff}o^^{OpaqueSecKeychainItemRef}'),
|
||||
]
|
||||
|
||||
objc.loadBundleFunctions(Security, globals(), S_functions)
|
||||
|
||||
SecKeychainRef = objc.registerCFSignature('SecKeychainRef', b'^{OpaqueSecKeychainRef=}', SecKeychainGetTypeID())
|
||||
SecKeychainItemRef = objc.registerCFSignature('SecKeychainItemRef', b'^{OpaqueSecKeychainItemRef=}', SecKeychainItemGetTypeID())
|
||||
PassBuffRef = objc.createOpaquePointerType('PassBuffRef', b'^{OpaquePassBuff=}', None)
|
||||
|
||||
# Get the login keychain
|
||||
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()
|
||||
|
||||
SecKeychainAddGenericPassword(
|
||||
self.login_keychain,
|
||||
len(service), service.encode(),
|
||||
len(repo_url), repo_url.encode(),
|
||||
len(password), password.encode(),
|
||||
None)
|
||||
|
||||
def get_password(self, service, repo_url):
|
||||
if not self.login_keychain: self._set_keychain()
|
||||
|
||||
result, password_length, password_buffer, keychain_item = SecKeychainFindGenericPassword(
|
||||
self.login_keychain, len(service), service.encode(), len(repo_url), repo_url.encode(), None, None, None)
|
||||
password = None
|
||||
if (result == 0) and (password_length != 0):
|
||||
# We apparently were able to find a password
|
||||
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
|
||||
return (c_char * password_length).from_address(password_buffer.__pointer__)[:].decode('utf-8')
|
|
@ -0,0 +1,32 @@
|
|||
import keyring
|
||||
|
||||
|
||||
class VortaDBKeyring(keyring.backend.KeyringBackend):
|
||||
"""
|
||||
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
|
||||
keyring_entry, created = RepoPassword.get_or_create(
|
||||
url=repo_url,
|
||||
defaults={'password': password}
|
||||
)
|
||||
keyring_entry.password = password
|
||||
keyring_entry.save()
|
||||
|
||||
def get_password(self, service, repo_url):
|
||||
from .models import RepoPassword
|
||||
try:
|
||||
keyring_entry = RepoPassword.get(url=repo_url)
|
||||
return keyring_entry.password
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def delete_password(self, service, repo_url):
|
||||
pass
|
|
@ -7,9 +7,9 @@ def get_updater():
|
|||
if sys.platform == 'darwin' and getattr(sys, 'frozen', False):
|
||||
# Use sparkle framework on macOS.
|
||||
# Examples: https://programtalk.com/python-examples/objc.loadBundle/
|
||||
from objc import loadBundle
|
||||
import objc
|
||||
bundle_path = os.path.join(os.path.dirname(sys.executable), os.pardir, 'Frameworks', 'Sparkle.framework')
|
||||
loadBundle('Sparkle', globals(), bundle_path)
|
||||
objc.loadBundle('Sparkle', globals(), bundle_path)
|
||||
sparkle = SUUpdater.sharedUpdater() # noqa: F821
|
||||
if SettingsModel.get(key='updates_include_beta').value:
|
||||
sparkle.SharedUpdater.FeedURL = 'https://borgbase.github.io/vorta/appcast-pre.xml'
|
||||
|
|
|
@ -18,39 +18,23 @@ from PyQt5.QtGui import QIcon
|
|||
from PyQt5 import QtCore
|
||||
import subprocess
|
||||
import keyring
|
||||
from vorta.keyring_db import VortaDBKeyring
|
||||
|
||||
|
||||
class VortaKeyring(keyring.backend.KeyringBackend):
|
||||
"""Fallback keyring service."""
|
||||
@classmethod
|
||||
def priority(cls):
|
||||
return 5
|
||||
"""
|
||||
Set the most appropriate Keyring backend for the current system.
|
||||
|
||||
def set_password(self, service, repo_url, password):
|
||||
from .models import RepoPassword
|
||||
keyring_entry, created = RepoPassword.get_or_create(
|
||||
url=repo_url,
|
||||
defaults={'password': password}
|
||||
)
|
||||
keyring_entry.password = password
|
||||
keyring_entry.save()
|
||||
For macOS we use our own implementation due to conflicts between
|
||||
Keyring and the autostart code.
|
||||
|
||||
def get_password(self, service, repo_url):
|
||||
from .models import RepoPassword
|
||||
try:
|
||||
keyring_entry = RepoPassword.get(url=repo_url)
|
||||
return keyring_entry.password
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def delete_password(self, service, repo_url):
|
||||
pass
|
||||
|
||||
|
||||
# Select keyring/Workaround for pyinstaller+keyring issue.
|
||||
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 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())
|
||||
|
@ -60,9 +44,9 @@ elif sys.platform == 'linux':
|
|||
SecretService.Keyring.priority() # Test if keyring works.
|
||||
keyring.set_keyring(SecretService.Keyring())
|
||||
except Exception:
|
||||
keyring.set_keyring(VortaKeyring())
|
||||
keyring.set_keyring(VortaDBKeyring())
|
||||
else: # Fall back to saving password to database.
|
||||
keyring.set_keyring(VortaKeyring())
|
||||
keyring.set_keyring(VortaDBKeyring())
|
||||
|
||||
|
||||
def nested_dict():
|
||||
|
@ -222,22 +206,25 @@ def set_tray_icon(tray, active=False):
|
|||
|
||||
|
||||
def open_app_at_startup(enabled=True):
|
||||
"""
|
||||
This function adds/removes the current app bundle from Login items in macOS
|
||||
"""
|
||||
if sys.platform == 'darwin':
|
||||
print('Not implemented due to conflict with keyring package.')
|
||||
# From https://stackoverflow.com/questions/26213884/cocoa-add-app-to-startup-in-sandbox-using-pyobjc
|
||||
# from Foundation import NSDictionary
|
||||
# from Cocoa import NSBundle, NSURL
|
||||
# from CoreFoundation import kCFAllocatorDefault
|
||||
# from LaunchServices import (LSSharedFileListCreate, kLSSharedFileListSessionLoginItems,
|
||||
# LSSharedFileListInsertItemURL, kLSSharedFileListItemHidden,
|
||||
# kLSSharedFileListItemLast, LSSharedFileListItemRemove)
|
||||
#
|
||||
# app_path = NSBundle.mainBundle().bundlePath()
|
||||
# url = NSURL.alloc().initFileURLWithPath_(app_path)
|
||||
# login_items = LSSharedFileListCreate(kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, None)
|
||||
# props = NSDictionary.dictionaryWithObject_forKey_(True, kLSSharedFileListItemHidden)
|
||||
#
|
||||
# new_item = LSSharedFileListInsertItemURL(login_items, kLSSharedFileListItemLast,
|
||||
# None, None, url, props, None)
|
||||
# if not enabled:
|
||||
# LSSharedFileListItemRemove(login_items, new_item)
|
||||
from Foundation import NSDictionary
|
||||
|
||||
from Cocoa import NSBundle, NSURL
|
||||
from CoreFoundation import kCFAllocatorDefault
|
||||
# CF = CDLL(find_library('CoreFoundation'))
|
||||
from LaunchServices import (LSSharedFileListCreate, kLSSharedFileListSessionLoginItems,
|
||||
LSSharedFileListInsertItemURL, kLSSharedFileListItemHidden,
|
||||
kLSSharedFileListItemLast, LSSharedFileListItemRemove)
|
||||
|
||||
app_path = NSBundle.mainBundle().bundlePath()
|
||||
url = NSURL.alloc().initFileURLWithPath_(app_path)
|
||||
login_items = LSSharedFileListCreate(kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, None)
|
||||
props = NSDictionary.dictionaryWithObject_forKey_(True, kLSSharedFileListItemHidden)
|
||||
|
||||
new_item = LSSharedFileListInsertItemURL(login_items, kLSSharedFileListItemLast,
|
||||
None, None, url, props, None)
|
||||
if not enabled:
|
||||
LSSharedFileListItemRemove(login_items, new_item)
|
||||
|
|
Loading…
Reference in New Issue