commit 28c9776bda3ca635a02edcb38fb84ff2a10a2594
parent 5a204c6193b570c5da0ad47b18fcddac329530e9
Author: Daniel Vrátil <dvratil@kde.org>
Date: Sun, 3 Feb 2019 13:51:37 +0100
Implement decrypting, copying and expiring passwords.
It's still a bit rough around the edges but it's generally
useful now.
Diffstat:
6 files changed, 208 insertions(+), 69 deletions(-)
diff --git a/harbour-passilic.pro b/harbour-passilic.pro
@@ -31,7 +31,8 @@ DISTFILES += \
rpm/harbour-passilic.spec \
rpm/harbour-passilic.yaml \
translations/*.ts \
- harbour-passilic.desktop
+ harbour-passilic.desktop \
+ qml/pages/PassphraseRequester.qml
SAILFISHAPP_ICONS = 86x86 108x108 128x128 172x172
diff --git a/qml/harbour-passilic.qml b/qml/harbour-passilic.qml
@@ -24,6 +24,12 @@ ApplicationWindow
}
Component {
+ id: passphraseRequester
+
+ PassphraseRequester {}
+ }
+
+ Component {
id: passwordsPage
PasswordListPage {
diff --git a/qml/pages/PassphraseRequester.qml b/qml/pages/PassphraseRequester.qml
@@ -0,0 +1,32 @@
+import QtQuick 2.0
+import Sailfish.Silica 1.0
+import harbour.passilic 1.0
+
+Dialog {
+ id: dlg
+
+ property var requester: null
+
+ DialogHeader {
+ }
+
+ PasswordField {
+ id: passwordField
+
+ anchors {
+ centerIn: parent
+ left: parent.left
+ right: parent.right
+ leftMargin: Theme.horizontalPageMargin
+ rightMargin: Theme.horizontalPageMargin
+ }
+
+ placeholderText: "Key passphrase"
+ EnterKey.iconSource: "image://theme/icom-m-enter-accept"
+ EnterKey.onClicked: dlg.accept()
+
+ }
+
+ onRejected: requester.cancel()
+ onAccepted: requester.setPassphrase(passwordField.text)
+}
diff --git a/qml/pages/PasswordListPage.qml b/qml/pages/PasswordListPage.qml
@@ -12,6 +12,7 @@ Page {
signal folderSelected(var index, var name)
+ signal passwordRequested(var requester)
SilicaListView {
@@ -32,42 +33,105 @@ Page {
delegate: ListItem {
id: listItem
- height: Theme.itemSizeSmall
- Row {
+ property var password : null
- spacing: Theme.paddingMedium
+ contentHeight: password === null ? Theme.itemSizeSmall : Theme.itemSizeLarge
+ Row {
anchors {
- left: parent.left
+ fill: parent
leftMargin: Theme.horizontalPageMargin
- right: parent.right
rightMargin: Theme.horizontalPageMargin
verticalCenter: parent.verticalCenter
+ topMargin: Theme.paddingMedium
}
- Image {
- anchors.verticalCenter: parent.verticalCenter
- source: "image://theme/"
- + ((model.type === PasswordsModel.FolderEntry) ? "icon-m-folder" : "icon-m-device-lock")
- + "?"
- + (listItem.highlighted ? Theme.highlightColor : Theme.primaryColor)
- width: Theme.iconSizeSmall
- height: width
+ Column {
+ spacing: Theme.paddingSmall
+ width: parent.width
+
+ Row {
+ spacing: Theme.paddingMedium
+
+ Image {
+ anchors.verticalCenter: parent.verticalCenter
+ source: "image://theme/"
+ + ((model.type === PasswordsModel.FolderEntry) ? "icon-m-folder" : "icon-m-device-lock")
+ + "?"
+ + (listItem.highlighted ? Theme.highlightColor : Theme.primaryColor)
+ width: Theme.iconSizeSmall
+ height: width
+ }
+
+ Label {
+ id: label
+ text: model.name
+ }
+ }
+
+ Row {
+ visible: password !== null
+ width: parent.width
+
+ Label {
+ id: errorLabel
+
+ visible: password !== null && password.hasError
+
+ text: password ? password.error : ""
+ font.pixelSize: Theme.fontSizeTiny
+ }
+
+ Label {
+ id: okLabel
+
+ visible: password !== null && password.valid
+
+ text: qsTr("Password copied to clipboard")
+ font.pixelSize: Theme.fontSizeTiny
+ }
+ }
}
+ }
+
+ RemorseItem {
+ id: remorse
- Label {
- id: label
- text: model.name
+ cancelText: qsTr("Expire password")
+
+ // HACK: override RemorseItem._execute() to act as cancel when the timer expires
+ function _execute(closeAfterExecute) {
+ cancel()
}
+ onCanceled: {
+ if (listItem.password) {
+ listItem.password.expirePassword();
+ }
+ }
}
onClicked: {
if (model.type === PasswordsModel.FolderEntry) {
passwordListPage.folderSelected(delegateModel.modelIndex(index), model.name);
} else {
- console.log("Password for " + model.name + " requested");
+ model.password.requestPassword()
+ var dialog = pageStack.push(Qt.resolvedUrl("PassphraseRequester.qml"),
+ { "requester": model.password })
+ dialog.done.connect(function() {
+ listItem.password = model.password
+ listItem.password.validChanged.connect(function() {
+ if (listItem.password.valid) {
+ remorse.execute(listItem, qsTr("Password will expire"),
+ function() {
+ if (listItem.password) {
+ listItem.password.expirePassword();
+ }
+ }, listItem.password.defaultTimeout);
+ }
+ });
+ });
}
}
}
diff --git a/src/passwordprovider.cpp b/src/passwordprovider.cpp
@@ -33,7 +33,66 @@ static const auto PasswordTimeoutUpdateInterval = 100;
PasswordProvider::PasswordProvider(const QString &path, QObject *parent)
: QObject(parent)
+ , mPath(path)
+{}
+
+
+PasswordProvider::~PasswordProvider()
+{
+ if (mGpg) {
+ mGpg->terminate();
+ delete mGpg;
+ }
+}
+
+bool PasswordProvider::isValid() const
+{
+ return !mPassword.isNull();
+}
+
+QString PasswordProvider::password() const
+{
+ return mPassword;
+}
+
+void PasswordProvider::setPassword(const QString &password)
+{
+ qGuiApp->clipboard()->setText(password, QClipboard::Clipboard);
+
+ if (qGuiApp->clipboard()->supportsSelection()) {
+ qGuiApp->clipboard()->setText(password, QClipboard::Selection);
+ }
+
+ mPassword = password;
+ Q_EMIT validChanged();
+ Q_EMIT passwordChanged();
+
+ mTimeout = defaultTimeout();
+ Q_EMIT timeoutChanged();
+ mTimer.start();
+}
+
+void PasswordProvider::expirePassword()
{
+ removePasswordFromClipboard(mPassword);
+
+ mPassword.clear();
+ mTimer.stop();
+ Q_EMIT validChanged();
+ Q_EMIT passwordChanged();
+
+ // Delete the provider, it's no longer needed
+ deleteLater();
+}
+
+void PasswordProvider::requestPassword()
+{
+ setError({});
+ mPassword.clear();
+ mTimer.stop();
+ Q_EMIT validChanged();
+ Q_EMIT passwordChanged();
+
mTimer.setInterval(PasswordTimeoutUpdateInterval);
connect(&mTimer, &QTimer::timeout,
this, [this]() {
@@ -61,7 +120,8 @@ PasswordProvider::PasswordProvider(const QString &path, QObject *parent)
QStringLiteral("--yes"),
QStringLiteral("--compress-algo=none"),
QStringLiteral("--no-encrypt-to"),
- path };
+ QStringLiteral("--passphrase-fd=0"),
+ mPath };
if (isGpg2) {
args = QStringList{ QStringLiteral("--batch"), QStringLiteral("--use-agent") } + args;
}
@@ -95,55 +155,7 @@ PasswordProvider::PasswordProvider(const QString &path, QObject *parent)
});
mGpg->setProgram(gpgExe);
mGpg->setArguments(args);
- mGpg->start(QIODevice::ReadOnly);
-}
-
-PasswordProvider::~PasswordProvider()
-{
- if (mGpg) {
- mGpg->terminate();
- delete mGpg;
- }
-}
-
-bool PasswordProvider::isValid() const
-{
- return !mPassword.isNull();
-}
-
-QString PasswordProvider::password() const
-{
- return mPassword;
-}
-
-void PasswordProvider::setPassword(const QString &password)
-{
- qGuiApp->clipboard()->setText(password, QClipboard::Clipboard);
-
- if (qGuiApp->clipboard()->supportsSelection()) {
- qGuiApp->clipboard()->setText(password, QClipboard::Selection);
- }
-
- mPassword = password;
- Q_EMIT validChanged();
- Q_EMIT passwordChanged();
-
- mTimeout = defaultTimeout();
- Q_EMIT timeoutChanged();
- mTimer.start();
-}
-
-void PasswordProvider::expirePassword()
-{
- removePasswordFromClipboard(mPassword);
-
- mPassword.clear();
- mTimer.stop();
- Q_EMIT validChanged();
- Q_EMIT passwordChanged();
-
- // Delete the provider, it's no longer needed
- deleteLater();
+ mGpg->start(QIODevice::ReadWrite);
}
int PasswordProvider::timeout() const
@@ -172,6 +184,25 @@ void PasswordProvider::setError(const QString &error)
Q_EMIT errorChanged();
}
+void PasswordProvider::cancel()
+{
+ if (mGpg) {
+ mGpg->terminate();
+ delete mGpg;
+ }
+ setError(tr("Cancelled by user."));
+}
+
+void PasswordProvider::setPassphrase(const QString &passphrase)
+{
+ if (!mGpg) {
+ qWarning("Called PasswordProvider::setPassphrase without active GPG process");
+ return;
+ }
+
+ mGpg->write(passphrase.toUtf8());
+ mGpg->closeWriteChannel();
+}
void PasswordProvider::removePasswordFromClipboard(const QString &password)
{
diff --git a/src/passwordprovider.h b/src/passwordprovider.h
@@ -47,6 +47,12 @@ public:
bool hasError() const;
QString error() const;
+public Q_SLOTS:
+ void requestPassword();
+ void cancel();
+ void setPassphrase(const QString &passphrase);
+ void expirePassword();
+
Q_SIGNALS:
void passwordChanged();
void validChanged();
@@ -56,7 +62,6 @@ Q_SIGNALS:
private:
void setError(const QString &error);
void setPassword(const QString &password);
- void expirePassword();
void removePasswordFromClipboard(const QString &password);
void clearClipboard();