Improve existing code prior to implementing FDO Secrets

* DatabaseTabWidget::newDatabase returns the created DatabaseWidget
* Emit DatabaseTabWidget::databaseOpened signal before a new tab is added
* EntrySearcher can now search attribute values including custom ones
* Add Group::applyGroupIconTo to set the group icon on the supplied entry
* Implement desktop notifications through the system tray icon
* Add DatabaseWidget::deleteEntries to delete a list of entries
* Add Aes128 in SymmetricCipher::algorithmIvSize
* Add DatabaseWidget::databaseReplaced signal
* Add a helper class to override the message box's parent (prevent bugs)
This commit is contained in:
Aetf 2019-02-21 00:51:23 -05:00 committed by Jonathan White
parent bc891761b6
commit d93f33f514
15 changed files with 154 additions and 30 deletions

View File

@ -140,12 +140,20 @@ bool EntrySearcher::searchEntryImpl(Entry* entry)
case Field::Notes:
found = term->regex.match(entry->notes()).hasMatch();
break;
case Field::Attribute:
case Field::AttributeKey:
found = !attributes.filter(term->regex).empty();
break;
case Field::Attachment:
found = !attachments.filter(term->regex).empty();
break;
case Field::AttributeValue:
// skip protected attributes
if (entry->attributes()->isProtected(term->word)) {
continue;
}
found = entry->attributes()->contains(term->word)
&& term->regex.match(entry->attributes()->value(term->word)).hasMatch();
break;
default:
// Terms without a specific field try to match title, username, url, and notes
found = term->regex.match(entry->resolvePlaceholder(entry->title())).hasMatch()
@ -207,12 +215,18 @@ void EntrySearcher::parseSearchTerms(const QString& searchString)
} else if (field.compare("notes", cs) == 0) {
term->field = Field::Notes;
} else if (field.startsWith("attr", cs)) {
term->field = Field::Attribute;
term->field = Field::AttributeKey;
} else if (field.startsWith("attach", cs)) {
term->field = Field::Attachment;
} else {
term->field = Field::Undefined;
} else if (field.startsWith("_", cs)) {
term->field = Field::AttributeValue;
// searching a custom attribute
// in this case term->word is the attribute key (removing the leading "_")
// and term->regex is used to match attribute value
term->word = field.mid(1);
}
} else {
term->field = Field::Undefined;
}
m_searchTerms.append(term);

View File

@ -48,8 +48,9 @@ private:
Password,
Url,
Notes,
Attribute,
Attachment
AttributeKey,
Attachment,
AttributeValue
};
struct SearchTerm

View File

@ -1057,6 +1057,23 @@ Entry* Group::addEntryWithPath(const QString& entryPath)
return entry;
}
void Group::applyGroupIconTo(Entry* entry)
{
if (!config()->get("UseGroupIconOnEntryCreation").toBool()) {
return;
}
if (iconNumber() == Group::DefaultIconNumber && iconUuid().isNull()) {
return;
}
if (iconUuid().isNull()) {
entry->setIcon(iconNumber());
} else {
entry->setIcon(iconUuid());
}
}
bool Group::GroupData::operator==(const Group::GroupData& other) const
{
return equals(other, CompareItemDefault);

View File

@ -167,6 +167,8 @@ public:
void addEntry(Entry* entry);
void removeEntry(Entry* entry);
void applyGroupIconTo(Entry* entry);
signals:
void groupDataChanged(Group* group);
void groupAboutToAdd(Group* group, int index);

View File

@ -126,6 +126,8 @@ int SymmetricCipher::algorithmIvSize(Algorithm algo)
switch (algo) {
case ChaCha20:
return 12;
case Aes128:
return 16;
case Aes256:
return 16;
case Twofish:

View File

@ -116,15 +116,17 @@ QSharedPointer<Database> DatabaseTabWidget::execNewDatabaseWizard()
return db;
}
void DatabaseTabWidget::newDatabase()
DatabaseWidget* DatabaseTabWidget::newDatabase()
{
auto db = execNewDatabaseWizard();
if (!db) {
return;
return nullptr;
}
addDatabaseTab(new DatabaseWidget(db, this));
auto dbWidget = new DatabaseWidget(db, this);
addDatabaseTab(dbWidget);
db->markAsModified();
return dbWidget;
}
void DatabaseTabWidget::openDatabase()
@ -187,10 +189,12 @@ void DatabaseTabWidget::addDatabaseTab(DatabaseWidget* dbWidget, bool inBackgrou
{
Q_ASSERT(dbWidget->database());
// emit before index change
emit databaseOpened(dbWidget);
int index = addTab(dbWidget, "");
updateTabName(index);
toggleTabbar();
if (!inBackground) {
setCurrentIndex(index);
}

View File

@ -60,7 +60,7 @@ public slots:
bool closeDatabaseTabFromSender();
void updateTabName(int index = -1);
void newDatabase();
DatabaseWidget* newDatabase();
void openDatabase();
void mergeDatabase();
void importCsv();
@ -80,6 +80,7 @@ public slots:
void performGlobalAutoType();
signals:
void databaseOpened(DatabaseWidget* dbWidget);
void databaseClosed(const QString& filePath);
void databaseUnlocked(DatabaseWidget* dbWidget);
void databaseLocked(DatabaseWidget* dbWidget);

View File

@ -362,27 +362,10 @@ void DatabaseWidget::createEntry()
m_newEntry->setUuid(QUuid::createUuid());
m_newEntry->setUsername(m_db->metadata()->defaultUserName());
m_newParent = m_groupView->currentGroup();
setIconFromParent();
m_newParent->applyGroupIconTo(m_newEntry.data());
switchToEntryEdit(m_newEntry.data(), true);
}
void DatabaseWidget::setIconFromParent()
{
if (!config()->get("UseGroupIconOnEntryCreation").toBool()) {
return;
}
if (m_newParent->iconNumber() == Group::DefaultIconNumber && m_newParent->iconUuid().isNull()) {
return;
}
if (m_newParent->iconUuid().isNull()) {
m_newEntry->setIcon(m_newParent->iconNumber());
} else {
m_newEntry->setIcon(m_newParent->iconUuid());
}
}
void DatabaseWidget::replaceDatabase(QSharedPointer<Database> db)
{
// TODO: instead of increasing the ref count temporarily, there should be a clean
@ -393,6 +376,9 @@ void DatabaseWidget::replaceDatabase(QSharedPointer<Database> db)
connectDatabaseSignals();
m_groupView->changeDatabase(m_db);
processAutoOpen();
emit databaseReplaced(oldDb, m_db);
#if defined(WITH_XC_KEESHARE)
KeeShare::instance()->connectDatabase(m_db, oldDb);
#else
@ -461,6 +447,11 @@ void DatabaseWidget::deleteSelectedEntries()
selectedEntries.append(m_entryView->entryFromIndex(index));
}
deleteEntries(std::move(selectedEntries));
}
void DatabaseWidget::deleteEntries(QList<Entry*> selectedEntries)
{
// Confirm entry removal before moving forward
auto* recycleBin = m_db->metadata()->recycleBin();
bool permanent = (recycleBin && recycleBin->findEntryByUuid(selectedEntries.first()->uuid()))

View File

@ -125,6 +125,9 @@ signals:
void databaseUnlocked();
void databaseLocked();
// Emitted in replaceDatabase, may be caused by lock, reload, unlock, load.
void databaseReplaced(const QSharedPointer<Database>& oldDb, const QSharedPointer<Database>& newDb);
void closeRequest();
void currentModeChanged(DatabaseWidget::Mode mode);
void groupChanged();
@ -151,6 +154,7 @@ public slots:
void createEntry();
void cloneEntry();
void deleteSelectedEntries();
void deleteEntries(QList<Entry*> entries);
void setFocus();
void copyTitle();
void copyUsername();
@ -223,7 +227,6 @@ private slots:
private:
int addChildWidget(QWidget* w);
void setClipboardTextAndMinimize(const QString& text);
void setIconFromParent();
void processAutoOpen();
bool confirmDeleteEntries(QList<Entry*> entries, bool permanent);

View File

@ -1261,3 +1261,20 @@ void MainWindow::lockAllDatabases()
{
lockDatabasesAfterInactivity();
}
void MainWindow::displayDesktopNotification(const QString& msg, QString title, int msTimeoutHint)
{
if (!m_trayIcon || !QSystemTrayIcon::supportsMessages()) {
return;
}
if (title.isEmpty()) {
title = BaseWindowTitle;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
m_trayIcon->showMessage(title, msg, filePath()->applicationIcon(), msTimeoutHint);
#else
m_trayIcon->showMessage(title, msg, QSystemTrayIcon::Information, msTimeoutHint);
#endif
}

View File

@ -74,6 +74,7 @@ public slots:
void bringToFront();
void closeAllDatabases();
void lockAllDatabases();
void displayDesktopNotification(const QString& msg, QString title = "", int msTimeoutHint = 10000);
protected:
void closeEvent(QCloseEvent* event) override;

View File

@ -18,6 +18,10 @@
#include "MessageBox.h"
#include <QWindow>
QWindow* MessageBox::m_overrideParent(nullptr);
MessageBox::Button MessageBox::m_nextAnswer(MessageBox::NoButton);
QHash<QAbstractButton*, MessageBox::Button> MessageBox::m_addedButtonLookup =
@ -81,6 +85,14 @@ MessageBox::Button MessageBox::messageBox(QWidget* parent,
msgBox.setWindowTitle(title);
msgBox.setText(text);
if (m_overrideParent) {
// Force the creation of the QWindow, without this windowHandle() will return nullptr
msgBox.winId();
auto msgBoxWindow = msgBox.windowHandle();
Q_ASSERT(msgBoxWindow);
msgBoxWindow->setTransientParent(m_overrideParent);
}
for (uint64_t b = First; b <= Last; b <<= 1) {
if (b & buttons) {
QString text = m_buttonDefs[static_cast<Button>(b)].first;
@ -160,3 +172,14 @@ void MessageBox::setNextAnswer(MessageBox::Button button)
{
m_nextAnswer = button;
}
MessageBox::OverrideParent::OverrideParent(QWindow* newParent)
: m_oldParent(MessageBox::m_overrideParent)
{
MessageBox::m_overrideParent = newParent;
}
MessageBox::OverrideParent::~OverrideParent()
{
MessageBox::m_overrideParent = m_oldParent;
}

View File

@ -101,7 +101,18 @@ public:
Button defaultButton = MessageBox::NoButton,
Action action = MessageBox::None);
class OverrideParent
{
public:
explicit OverrideParent(QWindow* newParent);
~OverrideParent();
private:
QWindow* m_oldParent;
};
private:
static QWindow* m_overrideParent;
static Button m_nextAnswer;
static QHash<QAbstractButton*, Button> m_addedButtonLookup;
static QMap<Button, std::pair<QString, QMessageBox::ButtonRole>> m_buttonDefs;

View File

@ -210,4 +210,40 @@ void TestEntrySearcher::testSearchTermParser()
QCOMPARE(terms[1]->field, EntrySearcher::Field::Username);
QCOMPARE(terms[1]->regex.pattern(), QString("\\d+\\w{2}"));
// Test custom attribute search terms
m_entrySearcher.parseSearchTerms("+_abc:efg _def:\"ddd\"");
terms = m_entrySearcher.m_searchTerms;
QCOMPARE(terms.length(), 2);
QCOMPARE(terms[0]->field, EntrySearcher::Field::AttributeValue);
QCOMPARE(terms[0]->word, QString("abc"));
QCOMPARE(terms[0]->regex.pattern(), QString("^efg$"));
QCOMPARE(terms[1]->field, EntrySearcher::Field::AttributeValue);
QCOMPARE(terms[1]->word, QString("def"));
QCOMPARE(terms[1]->regex.pattern(), QString("ddd"));
}
void TestEntrySearcher::testCustomAttributesAreSearched()
{
QScopedPointer<Entry> e1(new Entry());
e1->setGroup(m_rootGroup);
e1->attributes()->set("testAttribute", "testE1");
e1->attributes()->set("testProtected", "testP", true);
QScopedPointer<Entry> e2(new Entry());
e2->setGroup(m_rootGroup);
e2->attributes()->set("testAttribute", "testE2");
e2->attributes()->set("testProtected", "testP2", true);
// search for custom entries
m_searchResult = m_entrySearcher.search("_testAttribute:test", m_rootGroup);
QCOMPARE(m_searchResult.count(), 2);
// protected attributes are ignored
m_searchResult = m_entrySearcher.search("_testAttribute:test _testProtected:testP2", m_rootGroup);
QCOMPARE(m_searchResult.count(), 2);
}

View File

@ -35,6 +35,7 @@ private slots:
void testSearch();
void testAllAttributesAreSearched();
void testSearchTermParser();
void testCustomAttributesAreSearched();
private:
Group* m_rootGroup;