summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnne Jan Brouwer <brouwer@annejan.com>2015-04-15 01:33:35 +0200
committerAnne Jan Brouwer <brouwer@annejan.com>2015-04-15 01:33:35 +0200
commite8a8abb73015c8d627618df33f521e39499c1103 (patch)
treed53179603d0a3e5fd56ebd1e7e456b82b1bcc39b
parent553d052ec77b37c3cda189d7d6c4f8d7c718ff65 (diff)
parent4d43c777c7b344aef731907cda361e5c2835f978 (diff)
Merge pull request #23 from rdoeffinger/useredit
Support for editing .gpg-id via GUI with public keyring list.
-rw-r--r--README.md2
-rw-r--r--mainwindow.cpp130
-rw-r--r--mainwindow.h7
-rw-r--r--mainwindow.ui7
-rw-r--r--qtpass.pro9
-rw-r--r--usersdialog.cpp41
-rw-r--r--usersdialog.h38
-rw-r--r--usersdialog.ui64
8 files changed, 265 insertions, 33 deletions
diff --git a/README.md b/README.md
index 20b5ce2a..5851df0c 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ TODO
2. ~~multi-lingual~~
3. ~~filtering and autocomplete~~
4. ~~edit, insert~~
-5. gpg-id management (per-folder)
+5. ~~gpg-id management (per-folder)~~
Instalation
-----------
diff --git a/mainwindow.cpp b/mainwindow.cpp
index 44003833..03899c66 100644
--- a/mainwindow.cpp
+++ b/mainwindow.cpp
@@ -1,6 +1,7 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "dialog.h"
+#include "usersdialog.h"
#include "util.h"
#include <QClipboard>
#include <QInputDialog>
@@ -271,7 +272,9 @@ void MainWindow::executeWrapper(QString app, QString args, QString input) {
* @brief MainWindow::readyRead
*/
void MainWindow::readyRead(bool finished = false) {
- QString output = process->readAllStandardOutput();
+ QString output;
+ if (currentAction != GPG_INTERNAL) {
+ output = process->readAllStandardOutput();
if (finished && currentAction == GPG) {
lastDecrypt = output;
if (useClipboard) {
@@ -294,6 +297,7 @@ void MainWindow::readyRead(bool finished = false) {
}
output.replace(QRegExp("<"), "&lt;");
output.replace(QRegExp(">"), "&gt;");
+ }
QString error = process->readAllStandardError();
if (error.size() > 0) {
@@ -347,6 +351,7 @@ void MainWindow::enableUiElements(bool state) {
ui->treeView->setEnabled(state);
ui->lineEdit->setEnabled(state);
ui->addButton->setEnabled(state);
+ ui->usersButton->setEnabled(state);
state &= ui->treeView->currentIndex().isValid();
ui->deleteButton->setEnabled(state);
ui->editButton->setEnabled(state);
@@ -480,6 +485,35 @@ void MainWindow::on_clearButton_clicked()
ui->lineEdit->clear();
}
+QString MainWindow::getRecipientString(QString for_file, QString separator)
+{
+ QDir gpgIdPath(QFileInfo(for_file.startsWith(passStore) ? for_file : passStore + for_file).absoluteDir());
+ bool found = false;
+ while (gpgIdPath.exists() && gpgIdPath.absolutePath().startsWith(passStore))
+ {
+ if (QFile(gpgIdPath.absoluteFilePath(".gpg-id")).exists()) {
+ found = true;
+ break;
+ }
+ if (!gpgIdPath.cdUp()) {
+ break;
+ }
+ }
+ QFile gpgId(found ? gpgIdPath.absoluteFilePath(".gpg-id") : passStore + ".gpg-id");
+ if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ return QString();
+ }
+ QString recipients;
+ while (!gpgId.atEnd()) {
+ QString recipient(gpgId.readLine());
+ recipient = recipient.trimmed();
+ if (!recipient.isEmpty()) {
+ recipients += separator + '"' + recipient + '"';
+ }
+ }
+ return recipients;
+}
+
void MainWindow::setPassword(QString file, bool overwrite)
{
bool ok;
@@ -500,35 +534,10 @@ void MainWindow::setPassword(QString file, bool overwrite)
QString force(overwrite ? " -f " : " ");
executePass("insert" + force + "-m \"" + file + '"', newValue);
} else {
- QDir gpgIdPath(QFileInfo(file.startsWith(passStore) ? file : passStore + file).absoluteDir());
- bool found = false;
- while (gpgIdPath.exists() && gpgIdPath.absolutePath().startsWith(passStore))
- {
- if (QFile(gpgIdPath.absoluteFilePath(".gpg-id")).exists()) {
- found = true;
- break;
- }
- if (!gpgIdPath.cdUp()) {
- break;
- }
- }
- QFile gpgId(found ? gpgIdPath.absoluteFilePath(".gpg-id") : passStore + ".gpg-id");
- if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) {
- QMessageBox::critical(this, tr("Can not edit"),
- tr("Password store lacks .gpg-id specifying encryption key"));
- return;
- }
- QString recipients;
- while (!gpgId.atEnd()) {
- QString recipient(gpgId.readLine());
- recipient = recipient.trimmed();
- if (!recipient.isEmpty()) {
- recipients += " -r \"" + recipient + '"';
- }
- }
+ QString recipients = getRecipientString(file, " -r ");
if (recipients.isEmpty()) {
QMessageBox::critical(this, tr("Can not edit"),
- tr("Could not read encryption key to use"));
+ tr("Could not read encryption key to use, .gpg-id file missing or invalid."));
return;
}
QString force(overwrite ? " --yes " : " ");
@@ -538,6 +547,8 @@ void MainWindow::setPassword(QString file, bool overwrite)
void MainWindow::on_addButton_clicked()
{
+
+
bool ok;
QString file = QInputDialog::getText(this, tr("New file"),
tr("New password file:"), QLineEdit::Normal,
@@ -582,6 +593,69 @@ void MainWindow::on_editButton_clicked()
setPassword(file, true);
}
+QList<UserInfo> MainWindow::listKeys(QString keystring)
+{
+ QList<UserInfo> users;
+ currentAction = GPG_INTERNAL;
+ executeWrapper(gpgExecutable , "--no-tty --with-colons --list-keys " + keystring);
+ process->waitForFinished(2000);
+ if (process->exitStatus() != QProcess::NormalExit) {
+ return users;
+ }
+ QStringList keys = QString(process->readAllStandardOutput()).split(QRegExp("[\r\n]"), QString::SkipEmptyParts);
+ foreach (QString key, keys) {
+ QStringList props = key.split(':');
+ if (props.size() < 10 || props[0] != "pub") {
+ continue;
+ }
+ UserInfo i;
+ i.name = props[9];
+ i.key_id = props[4];
+ users.append(i);
+ }
+ return users;
+}
+
+void MainWindow::on_usersButton_clicked()
+{
+ QList<UserInfo> users = listKeys();
+ if (users.size() == 0) {
+ QMessageBox::critical(this, tr("Can not get key list"),
+ tr("Unable to get list of available gpg keys"));
+ return;
+ }
+ QList<UserInfo> selected_users;
+ QString dir = getDir(ui->treeView->currentIndex(), false);
+ QString recipients = getRecipientString(dir.isEmpty() ? "" : dir);
+ if (!recipients.isEmpty()) {
+ selected_users = listKeys(recipients);
+ }
+ foreach (const UserInfo &sel, selected_users) {
+ for (QList<UserInfo>::iterator it = users.begin(); it != users.end(); ++it) {
+ if (sel.key_id == it->key_id) it->enabled = true;
+ }
+ }
+ UsersDialog d(this);
+ d.setUsers(&users);
+ if (!d.exec()) {
+ d.setUsers(NULL);
+ return;
+ }
+ d.setUsers(NULL);
+ QFile gpgId(dir + ".gpg-id");
+ if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ QMessageBox::critical(this, tr("Cannot update"),
+ tr("Failed to open .gpg-id for writing."));
+ return;
+ }
+ foreach (const UserInfo &user, users) {
+ if (user.enabled) {
+ gpgId.write((user.key_id + "\n").toUtf8());
+ }
+ }
+ gpgId.close();
+}
+
/**
* @brief MainWindow::setApp
* @param app
diff --git a/mainwindow.h b/mainwindow.h
index 66c05a8b..ccbd55a2 100644
--- a/mainwindow.h
+++ b/mainwindow.h
@@ -13,11 +13,13 @@ namespace Ui {
class MainWindow;
}
+struct UserInfo;
+
class MainWindow : public QMainWindow
{
Q_OBJECT
-enum actionType { GPG, GIT, EDIT, DELETE };
+enum actionType { GPG, GIT, EDIT, DELETE, GPG_INTERNAL };
public:
explicit MainWindow(QWidget *parent = 0);
@@ -43,6 +45,7 @@ private slots:
void on_addButton_clicked();
void on_deleteButton_clicked();
void on_editButton_clicked();
+ void on_usersButton_clicked();
void messageAvailable(QString message);
private:
@@ -78,6 +81,8 @@ private:
void setPassword(QString, bool);
void normalizePassStore();
QSettings &getSettings();
+ QList<UserInfo> listKeys(QString keystring = "");
+ QString getRecipientString(QString for_file, QString separator = " ");
};
#endif // MAINWINDOW_H
diff --git a/mainwindow.ui b/mainwindow.ui
index fee1d276..96d23633 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -77,6 +77,13 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QToolButton" name="usersButton">
+ <property name="text">
+ <string>U</string>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
<item row="2" column="1">
diff --git a/qtpass.pro b/qtpass.pro
index 01093637..d6b9f0f6 100644
--- a/qtpass.pro
+++ b/qtpass.pro
@@ -21,16 +21,19 @@ SOURCES += main.cpp\
dialog.cpp \
storemodel.cpp \
singleapplication.cpp \
- util.cpp
+ util.cpp \
+ usersdialog.cpp
HEADERS += mainwindow.h \
dialog.h \
storemodel.h \
singleapplication.h \
- util.h
+ util.h \
+ usersdialog.h
FORMS += mainwindow.ui \
- dialog.ui
+ dialog.ui \
+ usersdialog.ui
TRANSLATIONS += localization/localization_nl_NL.ts \
localization/localization_de_DE.ts \
diff --git a/usersdialog.cpp b/usersdialog.cpp
new file mode 100644
index 00000000..767c7486
--- /dev/null
+++ b/usersdialog.cpp
@@ -0,0 +1,41 @@
+#include "usersdialog.h"
+#include "ui_usersdialog.h"
+
+UsersDialog::UsersDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::UsersDialog)
+{
+ ui->setupUi(this);
+ connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
+ connect(ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(ui->listWidget, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(itemChange(QListWidgetItem *)));
+}
+
+UsersDialog::~UsersDialog()
+{
+ delete ui;
+}
+
+Q_DECLARE_METATYPE(UserInfo *)
+
+void UsersDialog::itemChange(QListWidgetItem *item)
+{
+ if (!item) return;
+ UserInfo *info = item->data(Qt::UserRole).value<UserInfo *>();
+ if (!info) return;
+ info->enabled = item->checkState() == Qt::Checked;
+}
+
+void UsersDialog::setUsers(QList<UserInfo> *users)
+{
+ ui->listWidget->clear();
+ if (users) {
+ for (QList<UserInfo>::iterator it = users->begin(); it != users->end(); ++it) {
+ UserInfo &user(*it);
+ QListWidgetItem *item = new QListWidgetItem(user.name + "\n" + user.key_id, ui->listWidget);
+ item->setCheckState(user.enabled ? Qt::Checked : Qt::Unchecked);
+ item->setData(Qt::UserRole, QVariant::fromValue(&user));
+ ui->listWidget->addItem(item);
+ }
+ }
+}
diff --git a/usersdialog.h b/usersdialog.h
new file mode 100644
index 00000000..54fadfa8
--- /dev/null
+++ b/usersdialog.h
@@ -0,0 +1,38 @@
+#ifndef USERSDIALOG_H
+#define USERSDIALOG_H
+
+//#include <QAbstractListModel>
+#include <QDialog>
+#include <QList>
+#include <QStandardItemModel>
+
+namespace Ui {
+class UsersDialog;
+}
+
+class QListWidgetItem;
+
+struct UserInfo {
+ UserInfo() : enabled(false) {}
+ QString name;
+ QString key_id;
+ bool enabled;
+};
+
+class UsersDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit UsersDialog(QWidget *parent = 0);
+ ~UsersDialog();
+ void setUsers(QList<UserInfo> *);
+
+private slots:
+ void itemChange(QListWidgetItem *);
+
+private:
+ Ui::UsersDialog *ui;
+};
+
+#endif // USERSDIALOG_H
diff --git a/usersdialog.ui b/usersdialog.ui
new file mode 100644
index 00000000..232b9426
--- /dev/null
+++ b/usersdialog.ui
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UsersDialog</class>
+ <widget class="QDialog" name="UsersDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>599</width>
+ <height>583</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Read access users</string>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Select which users should be able to decrypt passwords stored in this folder.
+Note: Existing files will not be modified and retain the old permissions until you edit them.</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListWidget" name="listWidget">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>