Skip to content

Commit

Permalink
Warn user if deleting entries that are referenced. Resolves #852.
Browse files Browse the repository at this point in the history
On warning, references can be replaced with original values or ignored.
Removal process can be also skipped for each conflicting entry.
  • Loading branch information
wgml committed Apr 7, 2018
1 parent 5a84978 commit 7576f39
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 14 deletions.
29 changes: 29 additions & 0 deletions src/core/Database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@
#include <QTextStream>
#include <QTimer>
#include <QXmlStreamReader>
#include <QtConcurrent>

#include "cli/Utils.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/Tools.h"
#include "crypto/kdf/AesKdf.h"
#include "format/KeePass2.h"
#include "format/KeePass2Reader.h"
Expand Down Expand Up @@ -200,6 +202,33 @@ Group* Database::findGroupRecursive(const Uuid& uuid, Group* group)
return nullptr;
}

QList<Entry*> Database::resolveReferences(const Uuid& uuid) const
{
return resolveReferences(uuid, m_rootGroup);
}

QList<Entry*> Database::resolveReferences(const Uuid& uuid, const Group* group) const
{
auto isReference = [&uuid](const Entry* e) { return e->hasReferencesTo(uuid); };

QList<Entry*> result = QtConcurrent::blockingFiltered(group->entries(), isReference);

for (Group* child : group->children()) {
result += resolveReferences(uuid, child);
}
return result;
}

void Database::replaceReferencesWithValues(Entry* entry, QList<Entry*> references)
{
for (Entry* reference : references) {
for (const QString& key : EntryAttributes::DefaultAttributes) {
if (reference->isAttributeReferenceOf(key, entry->uuid()))
reference->setDefaultAttribute(key, entry->attribute(key));
}
}
}

QList<DeletedObject> Database::deletedObjects()
{
return m_deletedObjects;
Expand Down
3 changes: 3 additions & 0 deletions src/core/Database.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ class Database : public QObject
Entry* resolveEntry(const Uuid& uuid);
Entry* resolveEntry(const QString& text, EntryReferenceType referenceType);
Group* resolveGroup(const Uuid& uuid);
QList<Entry*> resolveReferences(const Uuid& uuid) const;
QList<Entry*> resolveReferences(const Uuid& uuid, const Group* group) const;
void replaceReferencesWithValues(Entry* entry, QList<Entry*> references);
QList<DeletedObject> deletedObjects();
void addDeletedObject(const DeletedObject& delObj);
void addDeletedObject(const Uuid& uuid);
Expand Down
39 changes: 37 additions & 2 deletions src/core/Entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "core/DatabaseIcons.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/Tools.h"
#include "totp/totp.h"

#include <QRegularExpression>
Expand Down Expand Up @@ -299,11 +300,25 @@ QString Entry::notes() const
return m_attributes->value(EntryAttributes::NotesKey);
}

QString Entry::attribute(const QString& key) const
{
return m_attributes->value(key);
}

bool Entry::isExpired() const
{
return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < QDateTime::currentDateTimeUtc();
}

bool Entry::isAttributeReferenceOf(const QString& key, const Uuid& uuid) const
{
if (!m_attributes->isReference(key))
return false;

const QString ref = Tools::reference(uuid, key).toUpper();
return m_attributes->value(key) == ref;
}

bool Entry::hasReferences() const
{
const QList<QString> keyList = EntryAttributes::DefaultAttributes;
Expand All @@ -315,6 +330,16 @@ bool Entry::hasReferences() const
return false;
}

bool Entry::hasReferencesTo(const Uuid& uuid) const
{
const QList<QString> keyList = EntryAttributes::DefaultAttributes;
for (const QString& key : keyList) {
if (isAttributeReferenceOf(key, uuid))
return true;
}
return false;
}

EntryAttributes* Entry::attributes()
{
return m_attributes;
Expand Down Expand Up @@ -520,6 +545,16 @@ void Entry::setNotes(const QString& notes)
m_attributes->set(EntryAttributes::NotesKey, notes, m_attributes->isProtected(EntryAttributes::NotesKey));
}

void Entry::setDefaultAttribute(const QString& attribute, const QString& value)
{
Q_ASSERT(EntryAttributes::isDefaultAttribute(attribute));

if (!EntryAttributes::isDefaultAttribute(attribute))
return;

m_attributes->set(attribute, value, m_attributes->isProtected(attribute));
}

void Entry::setExpires(const bool& value)
{
if (m_data.timeInfo.expires() != value) {
Expand Down Expand Up @@ -643,13 +678,13 @@ Entry* Entry::clone(CloneFlags flags) const

if (flags & CloneUserAsRef) {
// Build the username reference
QString username = "{REF:U@I:" + m_uuid.toHex() + "}";
QString username = Tools::reference(m_uuid, EntryAttributes::UserNameKey);
entry->m_attributes->set(
EntryAttributes::UserNameKey, username.toUpper(), m_attributes->isProtected(EntryAttributes::UserNameKey));
}

if (flags & ClonePassAsRef) {
QString password = "{REF:P@I:" + m_uuid.toHex() + "}";
QString password = Tools::reference(m_uuid, EntryAttributes::PasswordKey);
entry->m_attributes->set(
EntryAttributes::PasswordKey, password.toUpper(), m_attributes->isProtected(EntryAttributes::PasswordKey));
}
Expand Down
4 changes: 4 additions & 0 deletions src/core/Entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,17 @@ class Entry : public QObject
QString username() const;
QString password() const;
QString notes() const;
QString attribute(const QString& key) const;
QString totp() const;
QString totpSeed() const;
quint8 totpDigits() const;
quint8 totpStep() const;

bool hasTotp() const;
bool isExpired() const;
bool isAttributeReferenceOf(const QString& key, const Uuid& uuid) const;
bool hasReferences() const;
bool hasReferencesTo(const Uuid& uuid) const;
EntryAttributes* attributes();
const EntryAttributes* attributes() const;
EntryAttachments* attachments();
Expand Down Expand Up @@ -133,6 +136,7 @@ class Entry : public QObject
void setUsername(const QString& username);
void setPassword(const QString& password);
void setNotes(const QString& notes);
void setDefaultAttribute(const QString& attribute, const QString& value);
void setExpires(const bool& value);
void setExpiryTime(const QDateTime& dateTime);
void setTotp(const QString& seed, quint8& step, quint8& digits);
Expand Down
18 changes: 18 additions & 0 deletions src/core/Tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <time.h> // for nanosleep()
#endif

#include "EntryAttributes.h"
#include "config-keepassx.h"

#if defined(HAVE_RLIMIT_CORE)
Expand Down Expand Up @@ -343,4 +344,21 @@ namespace Tools
return bSuccess;
}

QString reference(const Uuid& uuid, const QString& field)
{
Q_ASSERT(EntryAttributes::DefaultAttributes.count(field) > 0);

if (field == EntryAttributes::TitleKey)
return "{REF:T@I:" + uuid.toHex() + "}";
if (field == EntryAttributes::UserNameKey)
return "{REF:U@I:" + uuid.toHex() + "}";
if (field == EntryAttributes::PasswordKey)
return "{REF:P@I:" + uuid.toHex() + "}";
if (field == EntryAttributes::URLKey)
return "{REF:A@I:" + uuid.toHex() + "}";
if (field == EntryAttributes::NotesKey)
return "{REF:N@I:" + uuid.toHex() + "}";

__builtin_unreachable();
}
} // namespace Tools
2 changes: 2 additions & 0 deletions src/core/Tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#define KEEPASSX_TOOLS_H

#include "core/Global.h"
#include "core/Uuid.h"

#include <QDateTime>
#include <QObject>
Expand All @@ -44,6 +45,7 @@ namespace Tools
void disableCoreDumps();
void setupSearchPaths();
bool createWindowsDACL();
QString reference(const Uuid& uuid, const QString& field);

template <typename RandomAccessIterator, typename T>
RandomAccessIterator binaryFind(RandomAccessIterator begin, RandomAccessIterator end, const T& value)
Expand Down
67 changes: 55 additions & 12 deletions src/gui/DatabaseWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,42 +472,85 @@ void DatabaseWidget::deleteEntries()
selectedEntries.append(m_entryView->entryFromIndex(index));
}

bool inRecycleBin = Tools::hasChild(m_db->metadata()->recycleBin(), selectedEntries.first());
auto it = selectedEntries.begin();
while (it != selectedEntries.end()) {
QList<Entry*> references = m_db->resolveReferences((*it)->uuid());
for (const auto& el : selectedEntries) {
references.removeAll(el);
}

if (!references.isEmpty()) {
if (handleEntryWithReferences(*it, references))
it++;
else
it = selectedEntries.erase(it);
} else {
it++;
}
}

if (!selectedEntries.isEmpty())
deleteEntriesWithNoReferences(selectedEntries);

refreshSearch();
}

bool DatabaseWidget::handleEntryWithReferences(Entry* entry, QList<Entry*> references)
{
QMessageBox::StandardButton result = MessageBox::question(
this,
tr("Replace references to entry?"),
tr("Entry \"%1\" has %2 references. "
"You can either copy values into references, ignore them or cancel deletion of the original.")
.arg(entry->title().toHtmlEscaped())
.arg(references.size()),
QMessageBox::Apply | QMessageBox::Ignore | QMessageBox::Cancel);

if (result == QMessageBox::Apply)
m_db->replaceReferencesWithValues(entry, references);

return result != QMessageBox::Cancel;
}

void DatabaseWidget::deleteEntriesWithNoReferences(QList<Entry*> entries)
{
Q_ASSERT(!entries.isEmpty());

bool inRecycleBin = Tools::hasChild(m_db->metadata()->recycleBin(), entries.first());
if (inRecycleBin || !m_db->metadata()->recycleBinEnabled()) {
QString prompt;
if (selected.size() == 1) {
if (entries.size() == 1) {
prompt = tr("Do you really want to delete the entry \"%1\" for good?")
.arg(selectedEntries.first()->title().toHtmlEscaped());
.arg(entries.first()->title().toHtmlEscaped());
} else {
prompt = tr("Do you really want to delete %n entry(s) for good?", "", selected.size());
prompt = tr("Do you really want to delete %n entry(s) for good?", "", entries.size());
}

QMessageBox::StandardButton result = MessageBox::question(
this, tr("Delete entry(s)?", "", selected.size()), prompt, QMessageBox::Yes | QMessageBox::No);
this, tr("Delete entry(s)?", "", entries.size()), prompt, QMessageBox::Yes | QMessageBox::No);

if (result == QMessageBox::Yes) {
for (Entry* entry : asConst(selectedEntries)) {
for (Entry* entry : asConst(entries)) {
delete entry;
}
refreshSearch();
}
} else {
QString prompt;
if (selected.size() == 1) {
if (entries.size() == 1) {
prompt = tr("Do you really want to move entry \"%1\" to the recycle bin?")
.arg(selectedEntries.first()->title().toHtmlEscaped());
.arg(entries.first()->title().toHtmlEscaped());
} else {
prompt = tr("Do you really want to move %n entry(s) to the recycle bin?", "", selected.size());
prompt = tr("Do you really want to move %n entry(s) to the recycle bin?", "", entries.size());
}

QMessageBox::StandardButton result = MessageBox::question(
this, tr("Move entry(s) to recycle bin?", "", selected.size()), prompt, QMessageBox::Yes | QMessageBox::No);
this, tr("Move entry(s) to recycle bin?", "", entries.size()), prompt, QMessageBox::Yes | QMessageBox::No);

if (result == QMessageBox::No) {
return;
}

for (Entry* entry : asConst(selectedEntries)) {
for (Entry* entry : asConst(entries)) {
m_db->recycleEntry(entry);
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/gui/DatabaseWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <QScopedPointer>
#include <QStackedWidget>
#include <QTimer>
#include <QtWidgets/QtWidgets>

#include "core/Uuid.h"

Expand Down Expand Up @@ -212,6 +213,9 @@ private slots:
void setIconFromParent();
void replaceDatabase(Database* db);

bool handleEntryWithReferences(Entry* entry, QList<Entry*> references);
void deleteEntriesWithNoReferences(QList<Entry*> entries);

Database* m_db;
QWidget* m_mainWidget;
EditEntryWidget* m_editEntryWidget;
Expand Down

0 comments on commit 7576f39

Please sign in to comment.