Merge 1153b10bb0ccece00371157b458587d5b1cb51aa into af2479da8dc0ff0c7104c4ccde1715b1c562dfdc

This commit is contained in:
Matteson 2025-04-04 14:49:34 +00:00 committed by GitHub
commit f40805663f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 94 additions and 1 deletions

View File

@ -9248,6 +9248,10 @@ This option is deprecated, use --set-key-file instead.</source>
<source>Tags</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Password is %1 old</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtIOCompressor</name>

View File

@ -243,6 +243,30 @@ const QSharedPointer<PasswordHealth> Entry::passwordHealth() const
return m_data.passwordHealth;
}
int Entry::passwordAgeSeconds() const
{
QListIterator<Entry*> i(m_history);
i.toBack();
const Entry* curr = nullptr;
const Entry* previous = this;
while (i.hasPrevious()) {
curr = previous;
previous = i.previous();
if (previous->password() != curr->password()) {
// Found last change in history
return curr->timeInfo().lastModificationTime().secsTo(Clock::currentDateTime());
}
}
if (previous != this) {
// If no change in history, password is from oldest history entry.
// Not using creation time here because that changes when an entry is cloned
return previous->timeInfo().lastModificationTime().secsTo(Clock::currentDateTime());
}
// If no history, creation time is when the password appeared
return this->timeInfo().creationTime().secsTo(Clock::currentDateTime());
}
bool Entry::excludeFromReports() const
{
return m_data.excludeFromReports

View File

@ -119,6 +119,7 @@ public:
QString path() const;
const QSharedPointer<PasswordHealth> passwordHealth();
const QSharedPointer<PasswordHealth> passwordHealth() const;
int passwordAgeSeconds() const;
bool excludeFromReports() const;
void setExcludeFromReports(bool state);

View File

@ -20,6 +20,7 @@
#include "Clock.h"
#include "Group.h"
#include "PasswordHealth.h"
#include "Tools.h"
#include "zxcvbn.h"
namespace
@ -199,6 +200,14 @@ QSharedPointer<PasswordHealth> HealthChecker::evaluate(const Entry* entry) const
}
}
// Fourth, add note if password is two or more years old.
int ageInSeconds = entry->passwordAgeSeconds();
// Unfortunately, Qt doesn't seem to have a utility for seconds->year.
// (365 days)(24 hours/day)(3600 s/hr) is approximately a year and gets compiled away.
if (ageInSeconds / (365 * 24 * 3600) > 1) {
health->addScoreReason(QObject::tr("Password is %1 old").arg(Tools::humanReadableTimeDifference(ageInSeconds)));
}
// Return the result
return health;
}

View File

@ -168,7 +168,7 @@ if(WITH_XC_SSHAGENT)
endif()
add_unit_test(NAME testentry SOURCES TestEntry.cpp
LIBS ${TEST_LIBRARIES})
LIBS testsupport ${TEST_LIBRARIES})
add_unit_test(NAME testmerge SOURCES TestMerge.cpp
LIBS testsupport ${TEST_LIBRARIES})

View File

@ -19,6 +19,8 @@
#include <QTest>
#include "TestEntry.h"
#include "mock/MockClock.h"
#include "core/Clock.h"
#include "core/Group.h"
#include "core/Metadata.h"
@ -47,6 +49,58 @@ void TestEntry::testHistoryItemDeletion()
QVERIFY(historyEntry.isNull());
}
void TestEntry::testPasswordAgeSeconds()
{
MockClock* mockClock = new MockClock(2022, 2, 4, 17, 00, 00);
MockClock::setup(mockClock);
// Old password updated 100 seconds ago
QPointer<Entry> historyEntry = new Entry();
historyEntry->setPassword("oldpassword");
mockClock->advanceSecond(500);
QScopedPointer<Entry> entry(new Entry());
entry->setPassword("newpassword");
entry->addHistoryItem(historyEntry);
mockClock->advanceSecond(100);
QCOMPARE(entry->passwordAgeSeconds(), 100);
QPointer<Entry> historyEntry2 = new Entry();
historyEntry2->setPassword("oldpassword");
mockClock->advanceSecond(500);
QScopedPointer<Entry> entry2(new Entry());
entry2->setPassword("oldpassword");
// No history, password just created
QCOMPARE(entry2->passwordAgeSeconds(), 0);
mockClock->advanceSecond(100);
// 100 seconds pass since creation
QCOMPARE(entry2->passwordAgeSeconds(), 100);
entry2->addHistoryItem(historyEntry2);
// History entry shows password is actually
// 500 seconds older than the creation time
QCOMPARE(entry2->passwordAgeSeconds(), 600);
// Bury password change in history
entry2->setPassword("newpassword");
QPointer<Entry> historyEntry3 = new Entry();
historyEntry3->setPassword("newpassword");
entry->addHistoryItem(historyEntry3);
mockClock->advanceSecond(400);
QPointer<Entry> historyEntry4 = new Entry();
historyEntry4->setPassword("newpassword");
entry->addHistoryItem(historyEntry4);
mockClock->advanceSecond(400);
QCOMPARE(entry2->passwordAgeSeconds(), 800);
// Second test where current password is the latest
entry2->setPassword("newerpassword");
QCOMPARE(entry2->passwordAgeSeconds(), 0);
MockClock::teardown();
}
void TestEntry::testCopyDataFrom()
{
QScopedPointer<Entry> entry(new Entry());

View File

@ -29,6 +29,7 @@ class TestEntry : public QObject
private slots:
void initTestCase();
void testHistoryItemDeletion();
void testPasswordAgeSeconds();
void testCopyDataFrom();
void testClone();
void testResolveUrl();