diff --git a/share/icons/application/16x16/actions/group-empty-trash.png b/share/icons/application/16x16/actions/group-empty-trash.png new file mode 100644 index 000000000..aa9d7321f Binary files /dev/null and b/share/icons/application/16x16/actions/group-empty-trash.png differ diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 4aa9e3f5b..5b5a707f9 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -308,6 +308,22 @@ void Database::recycleGroup(Group* group) } } +void Database::emptyRecycleBin() +{ + if (m_metadata->recycleBinEnabled() && m_metadata->recycleBin()) { + // destroying direct entries of the recycle bin + QList subEntries = m_metadata->recycleBin()->entries(); + for (Entry* entry : subEntries) { + delete entry; + } + // destroying direct subgroups of the recycle bin + QList subGroups = m_metadata->recycleBin()->children(); + for (Group* group : subGroups) { + delete group; + } + } +} + void Database::merge(const Database* other) { m_rootGroup->merge(other->rootGroup()); diff --git a/src/core/Database.h b/src/core/Database.h index 16c149608..7728d14c8 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -108,6 +108,7 @@ public: bool verifyKey(const CompositeKey& key) const; void recycleEntry(Entry* entry); void recycleGroup(Group* group); + void emptyRecycleBin(); void setEmitModified(bool value); void copyAttributesFrom(const Database* other); void merge(const Database* other); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index b4f623155..115c560f0 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -1267,3 +1267,25 @@ void DatabaseWidget::hideMessage() m_messageWidget->animatedHide(); } } + +bool DatabaseWidget::isRecycleBinSelected() const +{ + return m_groupView->currentGroup() && m_groupView->currentGroup() == m_db->metadata()->recycleBin(); +} + +void DatabaseWidget::emptyRecycleBin() +{ + if(!isRecycleBinSelected()) { + return; + } + + QMessageBox::StandardButton result = MessageBox::question( + this, tr("Empty recycle bin?"), + tr("Are you sure you want to permanently delete everything from your recycle bin?"), + QMessageBox::Yes | QMessageBox::No); + + if (result == QMessageBox::Yes) { + m_db->emptyRecycleBin(); + refreshSearch(); + } +} diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index d98cb0722..3add336f0 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -102,6 +102,7 @@ public: void closeUnlockDialog(); void blockAutoReload(bool block = true); void refreshSearch(); + bool isRecycleBinSelected() const; signals: void closeRequest(); @@ -152,6 +153,7 @@ public slots: void switchToImportKeepass1(const QString& fileName); void databaseModified(); void databaseSaved(); + void emptyRecycleBin(); // Search related slots void search(const QString& searchtext); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 814501f6a..6883f8a7e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -197,6 +197,7 @@ MainWindow::MainWindow() m_ui->actionGroupNew->setIcon(filePath()->icon("actions", "group-new", false)); m_ui->actionGroupEdit->setIcon(filePath()->icon("actions", "group-edit", false)); m_ui->actionGroupDelete->setIcon(filePath()->icon("actions", "group-delete", false)); + m_ui->actionGroupEmptyRecycleBin->setIcon(filePath()->icon("actions", "group-empty-trash", false)); m_ui->actionSettings->setIcon(filePath()->icon("actions", "configure")); m_ui->actionSettings->setMenuRole(QAction::PreferencesRole); @@ -295,6 +296,8 @@ MainWindow::MainWindow() SLOT(switchToGroupEdit())); m_actionMultiplexer.connect(m_ui->actionGroupDelete, SIGNAL(triggered()), SLOT(deleteGroup())); + m_actionMultiplexer.connect(m_ui->actionGroupEmptyRecycleBin, SIGNAL(triggered()), + SLOT(emptyRecycleBin())); connect(m_ui->actionSettings, SIGNAL(triggered()), SLOT(switchToSettings())); connect(m_ui->actionPasswordGenerator, SIGNAL(toggled(bool)), SLOT(switchToPasswordGen(bool))); @@ -413,6 +416,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) bool singleEntrySelected = dbWidget->numberOfSelectedEntries() == 1; bool entriesSelected = dbWidget->numberOfSelectedEntries() > 0; bool groupSelected = dbWidget->isGroupSelected(); + bool recycleBinSelected = dbWidget->isRecycleBinSelected(); m_ui->actionEntryNew->setEnabled(!inSearch); m_ui->actionEntryClone->setEnabled(singleEntrySelected); @@ -429,6 +433,8 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionGroupNew->setEnabled(groupSelected); m_ui->actionGroupEdit->setEnabled(groupSelected); m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); + m_ui->actionGroupEmptyRecycleBin->setVisible(recycleBinSelected); + m_ui->actionGroupEmptyRecycleBin->setEnabled(recycleBinSelected); m_ui->actionChangeMasterKey->setEnabled(true); m_ui->actionChangeDatabaseSettings->setEnabled(true); m_ui->actionDatabaseSave->setEnabled(true); diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui index c44bee9e7..384586e8d 100644 --- a/src/gui/MainWindow.ui +++ b/src/gui/MainWindow.ui @@ -162,7 +162,7 @@ 0 0 800 - 29 + 21 @@ -235,8 +235,10 @@ &Groups + + @@ -521,6 +523,14 @@ Re&pair database + + + Empty recycle bin + + + false + + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2ea50424a..1c9f1c0f5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -177,6 +177,9 @@ add_unit_test(NAME testykchallengeresponsekey SOURCES TestYkChallengeResponseKey.cpp TestYkChallengeResponseKey.h LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp + LIBS ${TEST_LIBRARIES}) + if(WITH_GUI_TESTS) add_subdirectory(gui) endif(WITH_GUI_TESTS) diff --git a/tests/TestDatabase.cpp b/tests/TestDatabase.cpp new file mode 100644 index 000000000..a70ada19d --- /dev/null +++ b/tests/TestDatabase.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2017 Vladimir Svyatski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "TestDatabase.h" + +#include +#include +#include + +#include "config-keepassx-tests.h" +#include "core/Database.h" +#include "crypto/Crypto.h" +#include "keys/PasswordKey.h" +#include "core/Metadata.h" +#include "core/Group.h" +#include "format/KeePass2Writer.h" + +QTEST_GUILESS_MAIN(TestDatabase) + +void TestDatabase::initTestCase() +{ + QVERIFY(Crypto::init()); +} + +void TestDatabase::testEmptyRecycleBinOnDisabled() +{ + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinDisabled.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("123")); + Database* db = Database::openDatabaseFile(filename, key); + QVERIFY(db); + + QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); + + db->emptyRecycleBin(); + //The database must be unmodified in this test after emptying the recycle bin. + QCOMPARE(spyModified.count(), 0); + + delete db; +} + +void TestDatabase::testEmptyRecycleBinOnNotCreated() +{ + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinNotYetCreated.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("123")); + Database* db = Database::openDatabaseFile(filename, key); + QVERIFY(db); + + QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); + + db->emptyRecycleBin(); + //The database must be unmodified in this test after emptying the recycle bin. + QCOMPARE(spyModified.count(), 0); + + delete db; +} + +void TestDatabase::testEmptyRecycleBinOnEmpty() +{ + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinEmpty.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("123")); + Database* db = Database::openDatabaseFile(filename, key); + QVERIFY(db); + + QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); + + db->emptyRecycleBin(); + //The database must be unmodified in this test after emptying the recycle bin. + QCOMPARE(spyModified.count(), 0); + + delete db; +} + +void TestDatabase::testEmptyRecycleBinWithHierarchicalData() +{ + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinWithData.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("123")); + Database* db = Database::openDatabaseFile(filename, key); + QVERIFY(db); + + QFile originalFile(filename); + qint64 initialSize = originalFile.size(); + + db->emptyRecycleBin(); + QVERIFY(db->metadata()->recycleBin()); + QVERIFY(db->metadata()->recycleBin()->entries().empty()); + QVERIFY(db->metadata()->recycleBin()->children().empty()); + + QTemporaryFile afterCleanup; + KeePass2Writer writer; + writer.writeDatabase(&afterCleanup, db); + QVERIFY(afterCleanup.size() < initialSize); + + delete db; +} diff --git a/tests/TestDatabase.h b/tests/TestDatabase.h new file mode 100644 index 000000000..dc9609d72 --- /dev/null +++ b/tests/TestDatabase.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 Vladimir Svyatski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_TESTDATABASE_H +#define KEEPASSX_TESTDATABASE_H + +#include + +class TestDatabase : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void testEmptyRecycleBinOnDisabled(); + void testEmptyRecycleBinOnNotCreated(); + void testEmptyRecycleBinOnEmpty(); + void testEmptyRecycleBinWithHierarchicalData(); +}; + +#endif // KEEPASSX_TESTDATABASE_H diff --git a/tests/data/RecycleBinDisabled.kdbx b/tests/data/RecycleBinDisabled.kdbx new file mode 100644 index 000000000..0bbfb3efe Binary files /dev/null and b/tests/data/RecycleBinDisabled.kdbx differ diff --git a/tests/data/RecycleBinEmpty.kdbx b/tests/data/RecycleBinEmpty.kdbx new file mode 100644 index 000000000..7d264fb3e Binary files /dev/null and b/tests/data/RecycleBinEmpty.kdbx differ diff --git a/tests/data/RecycleBinNotYetCreated.kdbx b/tests/data/RecycleBinNotYetCreated.kdbx new file mode 100644 index 000000000..90771e504 Binary files /dev/null and b/tests/data/RecycleBinNotYetCreated.kdbx differ diff --git a/tests/data/RecycleBinWithData.kdbx b/tests/data/RecycleBinWithData.kdbx new file mode 100644 index 000000000..66d1c9302 Binary files /dev/null and b/tests/data/RecycleBinWithData.kdbx differ