Confirmation dialog to list entries.

This commit is contained in:
Francois Ferrand 2013-04-11 23:19:00 +02:00
parent eef51f26f0
commit ad67eac257
8 changed files with 414 additions and 23 deletions

View File

@ -101,6 +101,8 @@ set(keepassx_SOURCES
gui/group/EditGroupWidget.cpp
gui/group/GroupModel.cpp
gui/group/GroupView.cpp
http/AccessControlDialog.cpp
http/EntryConfig.cpp
http/Protocol.cpp
http/Server.cpp
http/Service.cpp
@ -166,6 +168,8 @@ set(keepassx_MOC
gui/group/EditGroupWidget.h
gui/group/GroupModel.h
gui/group/GroupView.h
http/AccessControlDialog.h
http/EntryConfig.h
http/Protocol.h
http/Server.h
http/Service.h
@ -196,6 +200,7 @@ set(keepassx_FORMS
gui/entry/EditEntryWidgetHistory.ui
gui/entry/EditEntryWidgetMain.ui
gui/group/EditGroupWidgetMain.ui
http/AccessControlDialog.ui
)
if(MINGW)

View File

@ -0,0 +1,52 @@
/**
***************************************************************************
* @file AccessControlDialog.cpp
*
* @brief
*
* Copyright (C) 2013
*
* @author Francois Ferrand
* @date 4/2013
***************************************************************************
*/
#include "AccessControlDialog.h"
#include "ui_AccessControlDialog.h"
#include "core/Entry.h"
AccessControlDialog::AccessControlDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::AccessControlDialog)
{
ui->setupUi(this);
connect(ui->allowButton, SIGNAL(clicked()), this, SLOT(accept()));
connect(ui->denyButton, SIGNAL(clicked()), this, SLOT(reject()));
}
AccessControlDialog::~AccessControlDialog()
{
delete ui;
}
void AccessControlDialog::setUrl(const QString &url)
{
ui->label->setText(QString(tr("%1 has requested access to passwords for the following item(s).\n"
"Please select whether you want to allow access.")).arg(QUrl(url).host()));
}
void AccessControlDialog::setItems(const QList<Entry *> &items)
{
Q_FOREACH (Entry * entry, items)
ui->itemsList->addItem(entry->title() + " - " + entry->username());
}
bool AccessControlDialog::remember() const
{
return ui->rememberDecisionCheckBox->isChecked();
}
void AccessControlDialog::setRemember(bool r)
{
ui->rememberDecisionCheckBox->setChecked(r);
}

View File

@ -0,0 +1,42 @@
/**
***************************************************************************
* @file AccessControlDialog.h
*
* @brief
*
* Copyright (C) 2013
*
* @author Francois Ferrand
* @date 4/2013
***************************************************************************
*/
#ifndef ACCESSCONTROLDIALOG_H
#define ACCESSCONTROLDIALOG_H
#include <QtGui/QDialog>
class Entry;
namespace Ui {
class AccessControlDialog;
}
class AccessControlDialog : public QDialog
{
Q_OBJECT
public:
explicit AccessControlDialog(QWidget *parent = 0);
~AccessControlDialog();
void setUrl(const QString & url);
void setItems(const QList<Entry *> & items);
bool remember() const;
void setRemember(bool r);
private:
Ui::AccessControlDialog *ui;
};
#endif // ACCESSCONTROLDIALOG_H

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AccessControlDialog</class>
<widget class="QDialog" name="AccessControlDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>221</height>
</rect>
</property>
<property name="windowTitle">
<string>KeyPassX/Http: Confirm Access</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="itemsList"/>
</item>
<item>
<widget class="QCheckBox" name="rememberDecisionCheckBox">
<property name="text">
<string>Remember this decision</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="allowButton">
<property name="text">
<string>Allow</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="denyButton">
<property name="text">
<string>Deny</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

100
src/http/EntryConfig.cpp Normal file
View File

@ -0,0 +1,100 @@
/**
***************************************************************************
* @file EntryConfig.cpp
*
* @brief
*
* Copyright (C) 2013
*
* @author Francois Ferrand
* @date 4/2013
***************************************************************************
*/
#include "EntryConfig.h"
#include "core/Entry.h"
#include "core/EntryAttributes.h"
#include "qjson/parser.h"
#include "qjson/qobjecthelper.h"
#include "qjson/serializer.h"
static const char KEEPASSHTTP_NAME[] = "KeePassHttp Settings"; //TODO: duplicated string (also in Service.cpp)
EntryConfig::EntryConfig(QObject *parent) :
QObject(parent)
{
}
QStringList EntryConfig::allowedHosts() const
{
return m_allowedHosts.toList();
}
void EntryConfig::setAllowedHosts(const QStringList &allowedHosts)
{
m_allowedHosts = allowedHosts.toSet();
}
QStringList EntryConfig::deniedHosts() const
{
return m_deniedHosts.toList();
}
void EntryConfig::setDeniedHosts(const QStringList &deniedHosts)
{
m_deniedHosts = deniedHosts.toSet();
}
bool EntryConfig::isAllowed(const QString &host)
{
return m_allowedHosts.contains(host);
}
void EntryConfig::allow(const QString &host)
{
m_allowedHosts.insert(host);
m_deniedHosts.remove(host);
}
bool EntryConfig::isDenied(const QString &host)
{
return m_deniedHosts.contains(host);
}
void EntryConfig::deny(const QString &host)
{
m_deniedHosts.insert(host);
m_allowedHosts.remove(host);
}
QString EntryConfig::realm() const
{
return m_realm;
}
void EntryConfig::setRealm(const QString &realm)
{
m_realm = realm;
}
bool EntryConfig::load(const Entry *entry)
{
QString s = entry->attributes()->value(KEEPASSHTTP_NAME);
if (s.isEmpty())
return false;
bool isOk = false;
QVariant v = QJson::Parser().parse(s.toUtf8(), &isOk);
if (!isOk || !v.type() == QVariant::Map)
return false;
QJson::QObjectHelper::qvariant2qobject(v.toMap(), this);
return true;
}
void EntryConfig::save(Entry *entry)
{
QVariant v = QJson::QObjectHelper::qobject2qvariant(this, QJson::QObjectHelper::Flag_None);
QByteArray json = QJson::Serializer().serialize(v);
entry->attributes()->set(KEEPASSHTTP_NAME, json);
}

54
src/http/EntryConfig.h Normal file
View File

@ -0,0 +1,54 @@
/**
***************************************************************************
* @file EntryConfig.h
*
* @brief
*
* Copyright (C) 2013
*
* @author Francois Ferrand
* @date 4/2013
***************************************************************************
*/
#ifndef ENTRYCONFIG_H
#define ENTRYCONFIG_H
#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtCore/QStringList>
#include <QtCore/QSet>
class Entry;
class EntryConfig : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList Allow READ allowedHosts WRITE setAllowedHosts)
Q_PROPERTY(QStringList Deny READ deniedHosts WRITE setDeniedHosts )
Q_PROPERTY(QString Realm READ realm WRITE setRealm )
public:
EntryConfig(QObject * object = 0);
bool load(const Entry * entry);
void save(Entry * entry);
bool isAllowed(const QString & host);
void allow(const QString & host);
bool isDenied(const QString & host);
void deny(const QString & host);
QString realm() const;
void setRealm(const QString &realm);
private:
QStringList allowedHosts() const;
void setAllowedHosts(const QStringList &allowedHosts);
QStringList deniedHosts() const;
void setDeniedHosts(const QStringList &deniedHosts);
QSet<QString> m_allowedHosts;
QSet<QString> m_deniedHosts;
QString m_realm;
};
#endif // ENTRYCONFIG_H

View File

@ -11,17 +11,22 @@
***************************************************************************
*/
#include <QtGui/QInputDialog>
#include <QtGui/QMessageBox>
#include <QtCore/QDebug>
#include "Service.h"
#include "Protocol.h"
#include "EntryConfig.h"
#include "AccessControlDialog.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/Uuid.h"
#include "core/PasswordGenerator.h"
#include <QtGui/QInputDialog>
#include <QtGui/QMessageBox>
#include <QtCore/QDebug>
Service::Service(DatabaseTabWidget *parent) :
KeepassHttpProtocol::Server(parent),
@ -121,8 +126,8 @@ QString Service::storeKey(const QString &key)
//Indicate who wants to associate, and request user to enter the 'name' of association key
id = QInputDialog::getText(0, tr("KeyPassX/Http: New key association request"),
tr("You have received an association request for the above key. If you would like to "
"allow it access to your KeePassX database give it a unique name to identify and a"
"ccept it."),
"allow it access to your KeePassX database give it a unique name to identify and"
"accept it."),
QLineEdit::Normal, QString(), &ok);
if (!ok || id.isEmpty())
return QString();
@ -182,31 +187,83 @@ QList<Entry*> Service::searchEntries(const QString &text)
return entries;
}
Service::Access Service::checkAccess(const Entry *entry, const QString & host, const QString & submitHost, const QString & realm)
{
EntryConfig config;
if (!config.load(entry))
return Unknown; //not configured
if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost)))
return Allowed; //allowed
if ((config.isDenied(host)) || (!submitHost.isEmpty() && config.isDenied(submitHost)))
return Denied; //denied
if (!realm.isEmpty() && config.realm() != realm)
return Denied;
return Unknown; //not configured for this host
}
QList<KeepassHttpProtocol::Entry> Service::findMatchingEntries(const QString &id, const QString &url, const QString &submitUrl, const QString &realm)
{
QList<KeepassHttpProtocol::Entry> result;
QList<Entry*> pwEntriesToConfirm;
bool autoAccept = false; //TODO: setting!
const QString host = QUrl(url).host();
const QString submitHost = QUrl(submitUrl).host();
const QList<Entry*> pwEntries = searchEntries(url);
Q_FOREACH (Entry * entry, pwEntries) {
//Filter accepted/denied entries
// if (c.Allow.Contains(formHost) && (submitHost == null || c.Allow.Contains(submitHost)))
// return true;
// if (c.Deny.Contains(formHost) || (submitHost != null && c.Deny.Contains(submitHost)))
// return false;
// if (realm != null && c.Realm != realm)
// return false;
//If we are unsure for some entries:
//- balloon to grant accessc if possible
//- if clicked, show confirmation dialog --> accept/reject (w/ list of items?)
// The website XXX wants to access your credentials
// MORE (---> if clicked, shows the list of returned entries)
// [x] Ask me again [Allow] [Deny]
// If accepted, store that entry can be accessed without confirmation
//- else, show only items which do not require validation
//Check entries for authorization
Q_FOREACH (Entry * entry, pwEntries) {
switch(checkAccess(entry, host, submitHost, realm)) {
case Denied:
continue;
//TODO: sort [--> need a flag], or do this in Server class [--> need an extra 'sort order' key in Entry, and we always compute it]
result << KeepassHttpProtocol::Entry(entry->title(), entry->username(), entry->password(), entry->uuid().toHex());
case Unknown:
if (!autoAccept) {
pwEntriesToConfirm.append(entry);
break;
}
//fall through
case Allowed:
result << KeepassHttpProtocol::Entry(entry->title(), entry->username(), entry->password(), entry->uuid().toHex());
break;
}
}
//If unsure, ask user for confirmation
if (!pwEntriesToConfirm.isEmpty()) {
//TODO: balloon to grant access + timeout
AccessControlDialog dlg;
dlg.setUrl(url);
dlg.setItems(pwEntriesToConfirm);
//dlg.setRemember(); //TODO: setting!
int res = dlg.exec();
if (dlg.remember()) {
Q_FOREACH (Entry * entry, pwEntries) {
EntryConfig config;
config.load(entry);
if (res == QDialog::Accepted) {
config.allow(host);
if (!submitHost.isEmpty() && host != submitHost)
config.allow(submitHost);
} else if (res == QDialog::Rejected) {
config.deny(host);
if (!submitHost.isEmpty() && host != submitHost)
config.deny(submitHost);
}
if (!realm.isEmpty())
config.setRealm(realm);
config.save(entry);
}
}
if (res == QDialog::Accepted) {
Q_FOREACH (Entry * entry, pwEntries)
result << KeepassHttpProtocol::Entry(entry->title(), entry->username(), entry->password(), entry->uuid().toHex());
}
}
//TODO: sort [--> need a flag], or do this in Server class [--> need an extra 'sort order' key in Entry, and we always compute it]
return result;
}
@ -259,6 +316,16 @@ void Service::addEntry(const QString &id, const QString &login, const QString &p
entry->setUsername(login);
entry->setPassword(password);
entry->setGroup(group);
const QString host = QUrl(url).host();
const QString submitHost = QUrl(submitUrl).host();
EntryConfig config;
config.allow(host);
if (!submitHost.isEmpty())
config.allow(submitHost);
if (!realm.isEmpty())
config.setRealm(realm);
config.save(entry);
}
}

View File

@ -39,8 +39,10 @@ public:
virtual QString generatePassword();
private:
enum Access { Denied, Unknown, Allowed};
Entry* getConfigEntry(bool create = false);
bool matchUrlScheme(const QString &url);
Access checkAccess(const Entry *entry, const QString & host, const QString & submitHost, const QString & realm);
bool removeFirstDomain(QString &hostname);
Group *findCreateAddEntryGroup();
QList<Entry *> searchEntries(const QString &text);