Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve password handling #550

Merged
merged 84 commits into from
Jan 18, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
7af6dad
Improve password handling (partial)
samuel-w Jul 19, 2020
e7dfbd3
Add translation strings
samuel-w Jul 20, 2020
573ecdd
Add missed translation string
samuel-w Jul 20, 2020
12d054a
Move password_transparency to utils
samuel-w Jul 22, 2020
3486d27
Lint
samuel-w Jul 22, 2020
58ed4ef
Add memory keyring
samuel-w Jul 23, 2020
1e750ec
Add methods to keyring
samuel-w Jul 23, 2020
78d9435
Lint
samuel-w Jul 23, 2020
cc62821
Prevent circular import
samuel-w Jul 23, 2020
625fcd6
Remove debug print
samuel-w Jul 23, 2020
9fe91f0
Move password functions to vorta.views.utils
samuel-w Jul 23, 2020
a2706f8
Store passwords statically
samuel-w Jul 23, 2020
0b79cee
Revert memory keyring changes
samuel-w Jul 23, 2020
1f0d0ce
Use early returns, show os-specific password storage
samuel-w Aug 2, 2020
6aec1e6
Remove unneeded function
samuel-w Aug 2, 2020
2c185f2
Merge branch 'master' into Partial-385
samuel-w Aug 3, 2020
c2abe80
Use qtbot instead of direct entry
samuel-w Aug 8, 2020
3b83b48
Make constants global
samuel-w Aug 9, 2020
9c59351
Merge remote-tracking branch 'upstream/master' into Partial-385
samuel-w Aug 17, 2020
c8f5be5
Determine keyring at borg thread
samuel-w Aug 20, 2020
7679df0
Seperate import
samuel-w Aug 20, 2020
55aecef
Remove setting of global keyring
samuel-w Aug 21, 2020
b06df6a
Clarify comment
samuel-w Aug 27, 2020
54713db
Clarify password validation case
samuel-w Aug 27, 2020
e22bee7
Deduplicate string
samuel-w Aug 27, 2020
184d103
Merge branch 'master' into Partial-385
samuel-w Aug 29, 2020
1728054
Merge branch 'Partial-385' of github:samuel-w/vorta-1 into Partial-385
samuel-w Aug 30, 2020
8d3e0b1
Merge branch 'master' into Partial-385
samuel-w Aug 30, 2020
d8d97d0
Merge branch 'Partial-385' of github:samuel-w/vorta-1 into Partial-385
samuel-w Sep 1, 2020
0fe8447
Merge branch 'master' into Partial-385
samuel-w Sep 4, 2020
d132bdf
Add message for when cannot find password
samuel-w Sep 4, 2020
66b1b9b
Fix FakeRepo
samuel-w Sep 4, 2020
210240e
Give secret storage program example
samuel-w Sep 4, 2020
39526d7
Revert "Determine keyring at borg thread"
samuel-w Sep 4, 2020
bc384fe
Merge branch 'master' into Partial-385
samuel-w Sep 5, 2020
282df94
Move import back down
samuel-w Sep 6, 2020
02a1112
Add translation string
samuel-w Sep 6, 2020
2b3a32f
Translate directly
samuel-w Sep 6, 2020
0a8ce63
Merge branch 'master' into Partial-385
samuel-w Sep 6, 2020
48a763f
Merge branch 'master' into Partial-385
samuel-w Sep 6, 2020
cc72d9e
Merge branch 'master' into Partial-385
samuel-w Sep 6, 2020
6c74ad5
Remove accidental paste
samuel-w Sep 6, 2020
9eb3426
Revert "Translate directly"
samuel-w Sep 6, 2020
c034c56
Translate password transparency
samuel-w Sep 6, 2020
01ccfff
Fix missed variable name
samuel-w Sep 6, 2020
31aa3c6
Break string across lines
samuel-w Sep 6, 2020
9febacd
Merge branch 'master' into Partial-385
samuel-w Sep 7, 2020
20b3918
Merge branch 'Partial-385' of github:samuel-w/vorta-1 into Partial-385
samuel-w Sep 9, 2020
4694050
Cleanup transparency
samuel-w Sep 10, 2020
29e6e3a
Merge branch 'Partial-385' of github:samuel-w/vorta-1 into Partial-385
samuel-w Sep 10, 2020
d8a373a
Merge branch 'master' into Partial-385
samuel-w Sep 12, 2020
e99d432
Merge branch 'master' into Partial-385
samuel-w Sep 19, 2020
6ba5175
Use fstring
samuel-w Sep 24, 2020
d0d6c0e
Revert "Use fstring"
samuel-w Sep 27, 2020
e124070
Merge branch 'master' into Partial-385
samuel-w Sep 27, 2020
c7b3f1b
Merge branch 'master' into Partial-385
samuel-w Oct 9, 2020
75adbca
Add password autofill from keyring
samuel-w Oct 9, 2020
6eabeaf
Add password visibility box
samuel-w Oct 9, 2020
e2b8e3d
Notify user when autofilling password
samuel-w Oct 9, 2020
8a83008
Punctuation
samuel-w Oct 9, 2020
1831c46
Fix tests
samuel-w Oct 9, 2020
27e844b
Don't use entire values
samuel-w Oct 9, 2020
981b584
Put show hide password button inline
samuel-w Oct 9, 2020
ed19950
Various small changes, squashed below
samuel-w Oct 9, 2020
55bb22d
Remove lambda
samuel-w Oct 9, 2020
5410497
Change text and icon based on visibility
samuel-w Oct 10, 2020
2b1bf29
Merge branch 'master' into Partial-385
samuel-w Nov 19, 2020
3f2c137
Merge branch 'master' into Partial-385
samuel-w Nov 19, 2020
5b9132e
Merge branch 'master' into Partial-385
samuel-w Nov 20, 2020
70c3b1f
Merge branch 'master' into Partial-385
m3nu Nov 29, 2020
9425115
Change method name, use full sentences instead of merging phrases.
samuel-w Nov 29, 2020
6d8e53e
Move get_keyring to VortaKeyring, store keyring in BorgThread
samuel-w Dec 1, 2020
34f9184
Merge branch 'master' into Partial-385
samuel-w Dec 1, 2020
0307b90
Fix keyring
samuel-w Dec 1, 2020
3177ec4
Remove unneeded line
samuel-w Dec 1, 2020
f5d151b
Make tests go brrrrr
samuel-w Dec 1, 2020
aaf2f98
Merge branch 'master' into Partial-385
samuel-w Dec 4, 2020
6d7e689
Add comments
samuel-w Dec 4, 2020
6dfb5ee
KWallet 5 -> KWallet
samuel-w Dec 4, 2020
2130543
Merge branch 'master' into Partial-385
samuel-w Dec 8, 2020
c7374e9
Merge branch 'master' into Partial-385
samuel-w Dec 16, 2020
7d92c35
Merge branch 'master' into Partial-385
samuel-w Jan 18, 2021
183693c
Simplify backend detection
samuel-w Jan 18, 2021
23e1a2a
Use snake case
samuel-w Jan 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/vorta/assets/UI/repoadd.ui
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,30 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="confirmLineEdit">
<property name="enabled">
<bool>true</bool>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="confirmLabel">
<property name="text">
<string>Confirm passphrase:</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QLabel" name="passwordLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabWidgetPage2">
Expand Down Expand Up @@ -271,5 +295,6 @@
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
41 changes: 40 additions & 1 deletion src/vorta/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from PyQt5.QtWidgets import QApplication, QFileDialog, QSystemTrayIcon

from vorta.borg._compatibility import BorgCompatibility
from vorta.i18n import trans_late
from vorta.keyring.abc import VortaKeyring
from vorta.log import logger

Expand Down Expand Up @@ -96,7 +97,7 @@ def get_private_keys():
def pretty_bytes(size):
"""from https://stackoverflow.com/questions/12523586/
python-format-size-application-converting-b-to-kb-mb-gb-tb/37423778"""
if type(size) != int:
if not isinstance(size, int):
return ''
power = 1000 # GiB is base 2**10, GB is base 10**3.
n = 0
Expand Down Expand Up @@ -273,3 +274,41 @@ def is_system_tray_available():
is_available = tray.isSystemTrayAvailable()

return is_available


def validate_passwords(firstPass, secondPass):
m3nu marked this conversation as resolved.
Show resolved Hide resolved
passEqual = firstPass == secondPass
passLong = len(firstPass) > 8

if not (passLong or passEqual):
return trans_late('utils', "Passwords must be identical and greater than 8 characters long")
if not passEqual:
return trans_late('utils', "Passwords must be identical")
if not passLong:
return trans_late('utils', "Passwords must be greater than 8 characters long")

return ""


def password_transparency(encryption):
samuel-w marked this conversation as resolved.
Show resolved Hide resolved
platform = {
'linux': trans_late('utils', 'the Secret Service API'),
'darwin': trans_late('utils', 'Keychain Access')
}

if encryption != 'none':
keyringClass = VortaKeyring.get_keyring().__class__.__name__
messages = {
m3nu marked this conversation as resolved.
Show resolved Hide resolved
'VortaDBKeyring': trans_late('utils', 'plaintext on disk.\nVorta supports {storage} for password storage'.format(storage=platform.get(sys.platform))), # noqa
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding those messages to the individual Keyring classes to have it all in one?

'VortaSecretStorageKeyring': trans_late('utils', 'the Secret Service API'),
'VortaDarwinKeyring': trans_late('utils', 'Keychain Access'),
'VortaKWallet5Keyring': trans_late('utils', 'KWallet 5'),
'VortaKWallet4Keyring': trans_late('utils', 'KWallet 4')
}
# Just in case some other keyring support is added
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then this isn't needed.

keyringName = messages.get(keyringClass,
trans_late('utils',
'somewhere that was not anticipated. Please file a bug report on Github'))
return trans_late('utils', 'The password will be stored in %s') % keyringName
else:
return ""
34 changes: 26 additions & 8 deletions src/vorta/views/repo_add_dialog.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import re
from PyQt5 import uic

from vorta.utils import get_private_keys, get_asset, choose_file_dialog, borg_compat
from vorta.utils import get_private_keys, get_asset, choose_file_dialog, \
borg_compat, validate_passwords, password_transparency
from vorta.borg.init import BorgInitThread
from vorta.borg.info import BorgInfoThread
from vorta.i18n import translate
from vorta.views.utils import get_colored_icon
from vorta.models import RepoModel

Expand All @@ -22,11 +24,15 @@ def __init__(self, parent=None):
self.saveButton.clicked.connect(self.run)
self.chooseLocalFolderButton.clicked.connect(self.choose_local_backup_folder)
self.useRemoteRepoButton.clicked.connect(self.use_remote_repo_action)
self.passwordLineEdit.textChanged.connect(self.password_listener)
self.confirmLineEdit.textChanged.connect(self.password_listener)
self.encryptionComboBox.activated.connect(self.password_transparency)
self.tabWidget.setCurrentIndex(0)

self.init_encryption()
self.init_ssh_key()
self.set_icons()
self.password_transparency()

def set_icons(self):
self.chooseLocalFolderButton.setIcon(get_colored_icon('folder-open'))
Expand All @@ -44,6 +50,9 @@ def values(self):
out['encryption'] = self.encryptionComboBox.currentData()
return out

def password_transparency(self):
self.passwordLabel.setText(password_transparency(self.values.get('encryption')))

def choose_local_backup_folder(self):
def receive():
folder = dialog.selectedFiles()
Expand All @@ -66,7 +75,7 @@ def use_remote_repo_action(self):
self.is_remote_repo = True

def run(self):
if self.validate():
if self.validate() and self.password_listener():
params = BorgInitThread.prepare(self.values)
if params['ok']:
self.saveButton.setEnabled(False)
Expand Down Expand Up @@ -122,21 +131,30 @@ def validate(self):
self._set_status(self.tr('This repo has already been added.'))
return False

if self.__class__ == AddRepoWindow:
if self.values['encryption'] != 'none':
if len(self.values['password']) < 8:
self._set_status(self.tr('Please use a longer passphrase.'))
return False

return True

def password_listener(self):
m3nu marked this conversation as resolved.
Show resolved Hide resolved
if self.values['encryption'] == 'none':
self.passwordLabel.setText("")
return True
else:
firstPass = self.passwordLineEdit.text()
secondPass = self.confirmLineEdit.text()
msg = validate_passwords(firstPass, secondPass)
self.passwordLabel.setText(translate('utils', msg))
return len(msg) == 0


class ExistingRepoWindow(AddRepoWindow):
def __init__(self):
super().__init__()
self.encryptionComboBox.hide()
self.encryptionLabel.hide()
self.title.setText(self.tr('Connect to existing Repository'))
self.passwordLineEdit.textChanged.disconnect()
self.confirmLineEdit.textChanged.disconnect()
self.confirmLineEdit.hide()
self.confirmLabel.hide()

def run(self):
if self.validate():
Expand Down
28 changes: 25 additions & 3 deletions tests/test_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,43 @@
from vorta.views.ssh_dialog import SSHAddWindow
from vorta.models import EventLogModel, RepoModel, ArchiveModel

LONG_PASSWORD = 'long-password-long'
SHORT_PASSWORD = 'hunter2'


def test_repo_add_failures(qapp, qtbot, mocker, borg_json_output):
# Add new repo window
main = qapp.main_window
add_repo_window = AddRepoWindow(main)
qtbot.addWidget(add_repo_window)

qtbot.keyClicks(add_repo_window.passwordLineEdit, LONG_PASSWORD)
qtbot.keyClicks(add_repo_window.confirmLineEdit, LONG_PASSWORD)
qtbot.keyClicks(add_repo_window.repoURL, 'aaa')
qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton)
assert add_repo_window.errorText.text().startswith('Please enter a valid')

add_repo_window.passwordLineEdit.clear()
add_repo_window.confirmLineEdit.clear()
qtbot.keyClicks(add_repo_window.passwordLineEdit, SHORT_PASSWORD)
qtbot.keyClicks(add_repo_window.confirmLineEdit, SHORT_PASSWORD)
qtbot.keyClicks(add_repo_window.repoURL, 'bbb.com:repo')
qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton)
assert add_repo_window.errorText.text() == 'Please use a longer passphrase.'
assert add_repo_window.passwordLabel.text() == 'Passwords must be greater than 8 characters long'

add_repo_window.passwordLineEdit.clear()
add_repo_window.confirmLineEdit.clear()
qtbot.keyClicks(add_repo_window.passwordLineEdit, SHORT_PASSWORD + "1")
qtbot.keyClicks(add_repo_window.confirmLineEdit, SHORT_PASSWORD)
qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton)
assert add_repo_window.passwordLabel.text() == 'Passwords must be identical and greater than 8 characters long'

add_repo_window.passwordLineEdit.clear()
add_repo_window.confirmLineEdit.clear()
qtbot.keyClicks(add_repo_window.passwordLineEdit, LONG_PASSWORD)
qtbot.keyClicks(add_repo_window.confirmLineEdit, SHORT_PASSWORD)
qtbot.mouseClick(add_repo_window.saveButton, QtCore.Qt.LeftButton)
assert add_repo_window.passwordLabel.text() == 'Passwords must be identical'


def test_repo_unlink(qapp, qtbot, monkeypatch):
Expand All @@ -40,8 +63,6 @@ def test_repo_unlink(qapp, qtbot, monkeypatch):


def test_repo_add_success(qapp, qtbot, mocker, borg_json_output):
LONG_PASSWORD = 'long-password-long'

# Add new repo window
main = qapp.main_window
main.repoTab.repo_added.disconnect()
Expand All @@ -50,6 +71,7 @@ def test_repo_add_success(qapp, qtbot, mocker, borg_json_output):

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)
Expand Down