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 authored and droidmonkey committed Dec 23, 2018
1 parent 4d4c839 commit 7f22151
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 47 deletions.
28 changes: 28 additions & 0 deletions src/core/Database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <QTimer>
#include <QXmlStreamReader>
#include <QFileInfo>
#include <QtConcurrent>

QHash<QUuid, QPointer<Database>> Database::s_uuidMap;
QHash<QString, QPointer<Database>> Database::s_filePathMap;
Expand Down Expand Up @@ -397,6 +398,33 @@ void Database::setFilePath(const QString& filePath)
emit filePathChanged(oldPath, filePath);
}

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

QList<Entry*> Database::resolveReferences(const QUuid& 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 @@ -92,6 +92,9 @@ class Database : public QObject
void recycleGroup(Group* group);
void recycleEntry(Entry* entry);
void emptyRecycleBin();
QList<Entry*> resolveReferences(const QUuid& uuid) const;
QList<Entry*> resolveReferences(const QUuid& uuid, const Group* group) const;
void replaceReferencesWithValues(Entry* entry, QList<Entry*> references);
QList<DeletedObject> deletedObjects();
const QList<DeletedObject>& deletedObjects() const;
void addDeletedObject(const DeletedObject& delObj);
Expand Down
76 changes: 70 additions & 6 deletions src/core/Entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "core/DatabaseIcons.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/Tools.h"
#include "totp/totp.h"

#include <QDir>
Expand Down Expand Up @@ -100,6 +101,32 @@ void Entry::setUpdateTimeinfo(bool value)
m_updateTimeinfo = value;
}

QString Entry::buildReference(const QUuid& uuid, const QString& field)
{
Q_ASSERT(EntryAttributes::DefaultAttributes.count(field) > 0);

QString uuidStr = Tools::uuidToHex(uuid);
QString shortField;

if (field == EntryAttributes::TitleKey) {
shortField = "T";
} else if (field == EntryAttributes::UserNameKey) {
shortField = "U";
} else if (field == EntryAttributes::PasswordKey) {
shortField = "P";
} else if (field == EntryAttributes::URLKey) {
shortField = "A";
} else if (field == EntryAttributes::NotesKey) {
shortField = "N";
}

if (shortField.isEmpty()) {
return {};
}

return QString("{REF:%1@I:%2}").arg(shortField, uuidStr);
}

EntryReferenceType Entry::referenceType(const QString& referenceStr)
{
const QString referenceLowerStr = referenceStr.toLower();
Expand Down Expand Up @@ -130,7 +157,7 @@ const QUuid& Entry::uuid() const

const QString Entry::uuidToHex() const
{
return QString::fromLatin1(m_uuid.toRfc4122().toHex());
return Tools::uuidToHex(m_uuid);
}

QImage Entry::icon() const
Expand Down Expand Up @@ -304,11 +331,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() < Clock::currentDateTimeUtc();
}

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

return m_attributes->value(key).contains(Tools::uuidToHex(uuid), Qt::CaseInsensitive);
}

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

bool Entry::hasReferencesTo(const QUuid& 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 @@ -496,6 +548,17 @@ 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 @@ -654,16 +717,17 @@ Entry* Entry::clone(CloneFlags flags) const
entry->m_attachments->copyDataFrom(m_attachments);

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

if (flags & ClonePassAsRef) {
QString password = "{REF:P@I:" + uuidToHex() + "}";
entry->m_attributes->set(
EntryAttributes::PasswordKey, password.toUpper(), m_attributes->isProtected(EntryAttributes::PasswordKey));
EntryAttributes::PasswordKey,
buildReference(uuid(), EntryAttributes::PasswordKey),
m_attributes->isProtected(EntryAttributes::PasswordKey));
}

entry->m_autoTypeAssociations->copyDataFrom(m_autoTypeAssociations);
Expand Down
5 changes: 5 additions & 0 deletions src/core/Entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,15 @@ class Entry : public QObject
QString username() const;
QString password() const;
QString notes() const;
QString attribute(const QString& key) const;
QString totp() const;
QSharedPointer<Totp::Settings> totpSettings() const;

bool hasTotp() const;
bool isExpired() const;
bool isAttributeReferenceOf(const QString& key, const QUuid& uuid) const;
bool hasReferences() const;
bool hasReferencesTo(const QUuid& uuid) const;
EntryAttributes* attributes();
const EntryAttributes* attributes() const;
EntryAttachments* attachments();
Expand Down Expand Up @@ -139,6 +142,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(QSharedPointer<Totp::Settings> settings);
Expand Down Expand Up @@ -237,6 +241,7 @@ private slots:
QString resolveReferencePlaceholderRecursive(const QString& placeholder, int maxDepth) const;
QString referenceFieldValue(EntryReferenceType referenceType) const;

static QString buildReference(const QUuid& uuid, const QString& field);
static EntryReferenceType referenceType(const QString& referenceStr);

template <class T> bool set(T& property, const T& value);
Expand Down
3 changes: 2 additions & 1 deletion src/core/Group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "core/DatabaseIcons.h"
#include "core/Global.h"
#include "core/Metadata.h"
#include "core/Tools.h"

const int Group::DefaultIconNumber = 48;
const int Group::RecycleBinIconNumber = 43;
Expand Down Expand Up @@ -119,7 +120,7 @@ const QUuid& Group::uuid() const

const QString Group::uuidToHex() const
{
return QString::fromLatin1(m_uuid.toRfc4122().toHex());
return Tools::uuidToHex(m_uuid);
}

QString Group::name() const
Expand Down
46 changes: 25 additions & 21 deletions src/core/Tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <QLocale>
#include <QRegularExpression>
#include <QStringList>
#include <QUuid>
#include <cctype>

#ifdef Q_OS_WIN
Expand Down Expand Up @@ -197,31 +198,34 @@ namespace Tools
}
}

// Escape common regex symbols except for *, ?, and |
auto regexEscape = QRegularExpression(R"re(([-[\]{}()+.,\\\/^$#]))re");
// Escape common regex symbols except for *, ?, and |
auto regexEscape = QRegularExpression(R"re(([-[\]{}()+.,\\\/^$#]))re");

QRegularExpression convertToRegex(const QString& string, bool useWildcards, bool exactMatch, bool caseSensitive)
{
QString pattern = string;
QRegularExpression convertToRegex(const QString& string, bool useWildcards, bool exactMatch, bool caseSensitive)
{
QString pattern = string;

// Wildcard support (*, ?, |)
if (useWildcards) {
pattern.replace(regexEscape, "\\\\1");
pattern.replace("*", ".*");
pattern.replace("?", ".");
}
// Wildcard support (*, ?, |)
if (useWildcards) {
pattern.replace(regexEscape, "\\\\1");
pattern.replace("*", ".*");
pattern.replace("?", ".");
}

// Exact modifier
if (exactMatch) {
pattern = "^" + pattern + "$";
}
// Exact modifier
if (exactMatch) {
pattern = "^" + pattern + "$";
}

auto regex = QRegularExpression(pattern);
if (!caseSensitive) {
regex.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
}
auto regex = QRegularExpression(pattern);
if (!caseSensitive) {
regex.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
}

return regex;
}
return regex;
}

QString uuidToHex(const QUuid& uuid) {
return QString::fromLatin1(uuid.toRfc4122().toHex());
}
} // namespace Tools
3 changes: 2 additions & 1 deletion src/core/Tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ namespace Tools
bool isBase64(const QByteArray& ba);
void sleep(int ms);
void wait(int ms);
QRegularExpression convertToRegex(const QString& string, bool useWildcards = false,
QString uuidToHex(const QUuid& uuid);
QRegularExpression convertToRegex(const QString& string, bool useWildcards = false,
bool exactMatch = false, bool caseSensitive = false);

template <typename RandomAccessIterator, typename T>
Expand Down
Loading

0 comments on commit 7f22151

Please sign in to comment.