mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-12-26 15:59:50 -05:00
596d2cf425
Replaces all string configuration options with enum types that can be checked by the compiler. This prevents spelling errors, in-place configuration definitions, and inconsistent default values. The default value config getter signature was removed in favour of consistently and centrally default-initialised configuration values. Individual default values were adjusted for better security, such as the default password length, which was increased from 16 characters to 32. The already existing config option deprecation map was extended by a general migration procedure using configuration versioning. Settings were split into Roaming and Local settings, which go to their respective AppData locations on Windows. Fixes #2574 Fixes #2193
398 lines
14 KiB
C++
398 lines
14 KiB
C++
/*
|
|
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
|
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "TestAutoType.h"
|
|
#include "TestGlobal.h"
|
|
|
|
#include <QPluginLoader>
|
|
|
|
#include "autotype/AutoType.h"
|
|
#include "autotype/AutoTypePlatformPlugin.h"
|
|
#include "autotype/test/AutoTypeTestInterface.h"
|
|
#include "core/Config.h"
|
|
#include "core/Resources.h"
|
|
#include "crypto/Crypto.h"
|
|
#include "gui/MessageBox.h"
|
|
|
|
QTEST_GUILESS_MAIN(TestAutoType)
|
|
|
|
void TestAutoType::initTestCase()
|
|
{
|
|
QVERIFY(Crypto::init());
|
|
Config::createTempFileInstance();
|
|
config()->set(Config::AutoTypeDelay, 1);
|
|
config()->set(Config::Security_AutoTypeAsk, false);
|
|
AutoType::createTestInstance();
|
|
|
|
QPluginLoader loader(resources()->pluginPath("keepassx-autotype-test"));
|
|
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint);
|
|
QVERIFY(loader.instance());
|
|
|
|
m_platform = qobject_cast<AutoTypePlatformInterface*>(loader.instance());
|
|
QVERIFY(m_platform);
|
|
|
|
m_test = qobject_cast<AutoTypeTestInterface*>(loader.instance());
|
|
QVERIFY(m_test);
|
|
|
|
m_autoType = AutoType::instance();
|
|
}
|
|
|
|
void TestAutoType::init()
|
|
{
|
|
config()->set(Config::AutoTypeEntryTitleMatch, false);
|
|
m_test->clearActions();
|
|
|
|
m_db = QSharedPointer<Database>::create();
|
|
m_dbList.clear();
|
|
m_dbList.append(m_db);
|
|
m_group = new Group();
|
|
m_db->setRootGroup(m_group);
|
|
|
|
AutoTypeAssociations::Association association;
|
|
|
|
m_entry1 = new Entry();
|
|
m_entry1->setGroup(m_group);
|
|
m_entry1->setUsername("myuser");
|
|
m_entry1->setPassword("mypass");
|
|
association.window = "custom window";
|
|
association.sequence = "{username}association{password}";
|
|
m_entry1->autoTypeAssociations()->add(association);
|
|
|
|
m_entry2 = new Entry();
|
|
m_entry2->setGroup(m_group);
|
|
m_entry2->setPassword("myuser");
|
|
m_entry2->setTitle("entry title");
|
|
|
|
m_entry3 = new Entry();
|
|
m_entry3->setGroup(m_group);
|
|
m_entry3->setPassword("regex");
|
|
association.window = "//REGEX1//";
|
|
association.sequence = "regex1";
|
|
m_entry3->autoTypeAssociations()->add(association);
|
|
association.window = "//^REGEX2$//";
|
|
association.sequence = "regex2";
|
|
m_entry3->autoTypeAssociations()->add(association);
|
|
association.window = "//^REGEX3-([rd]\\d){2}$//";
|
|
association.sequence = "regex3";
|
|
m_entry3->autoTypeAssociations()->add(association);
|
|
|
|
m_entry4 = new Entry();
|
|
m_entry4->setGroup(m_group);
|
|
m_entry4->setPassword("custom_attr");
|
|
m_entry4->attributes()->set("CUSTOM", "Attribute", false);
|
|
m_entry4->attributes()->set("CustomAttrFirst", "AttrValueFirst", false);
|
|
m_entry4->attributes()->set("CustomAttrSecond", "AttrValueSecond", false);
|
|
m_entry4->attributes()->set("CustomAttrThird", "AttrValueThird", false);
|
|
association.window = "//^CustomAttr1$//";
|
|
association.sequence = "{PASSWORD}:{S:CUSTOM}";
|
|
m_entry4->autoTypeAssociations()->add(association);
|
|
association.window = "//^CustomAttr2$//";
|
|
association.sequence = "{S:CuStOm}";
|
|
m_entry4->autoTypeAssociations()->add(association);
|
|
association.window = "//^CustomAttr3$//";
|
|
association.sequence = "{PaSSworD}";
|
|
m_entry4->autoTypeAssociations()->add(association);
|
|
association.window = "//^{S:CustomAttrFirst}$//";
|
|
association.sequence = "custom_attr_first";
|
|
m_entry4->autoTypeAssociations()->add(association);
|
|
association.window = "//{S:CustomAttrFirst}And{S:CustomAttrSecond}//";
|
|
association.sequence = "custom_attr_first_and_second";
|
|
m_entry4->autoTypeAssociations()->add(association);
|
|
association.window = "//{S:CustomAttrThird}//";
|
|
association.sequence = "custom_attr_third";
|
|
m_entry4->autoTypeAssociations()->add(association);
|
|
|
|
m_entry5 = new Entry();
|
|
m_entry5->setGroup(m_group);
|
|
m_entry5->setPassword("example5");
|
|
m_entry5->setTitle("some title");
|
|
m_entry5->setUrl("http://example.org");
|
|
}
|
|
|
|
void TestAutoType::cleanup()
|
|
{
|
|
}
|
|
|
|
void TestAutoType::testInternal()
|
|
{
|
|
QVERIFY(m_platform->activeWindowTitle().isEmpty());
|
|
|
|
m_test->setActiveWindowTitle("Test");
|
|
QCOMPARE(m_platform->activeWindowTitle(), QString("Test"));
|
|
}
|
|
|
|
void TestAutoType::testSingleAutoType()
|
|
{
|
|
m_autoType->performAutoType(m_entry1, nullptr);
|
|
|
|
QCOMPARE(m_test->actionCount(), 14);
|
|
QCOMPARE(m_test->actionChars(),
|
|
QString("myuser%1mypass%2").arg(m_test->keyToString(Qt::Key_Tab)).arg(m_test->keyToString(Qt::Key_Enter)));
|
|
}
|
|
|
|
void TestAutoType::testGlobalAutoTypeWithNoMatch()
|
|
{
|
|
m_test->setActiveWindowTitle("nomatch");
|
|
MessageBox::setNextAnswer(MessageBox::Ok);
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
|
|
QCOMPARE(m_test->actionChars(), QString());
|
|
}
|
|
|
|
void TestAutoType::testGlobalAutoTypeWithOneMatch()
|
|
{
|
|
m_test->setActiveWindowTitle("custom window");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
|
|
QCOMPARE(m_test->actionChars(), QString("%1association%2").arg(m_entry1->username()).arg(m_entry1->password()));
|
|
}
|
|
|
|
void TestAutoType::testGlobalAutoTypeTitleMatch()
|
|
{
|
|
config()->set(Config::AutoTypeEntryTitleMatch, true);
|
|
|
|
m_test->setActiveWindowTitle("An Entry Title!");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
|
|
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry2->password(), m_test->keyToString(Qt::Key_Enter)));
|
|
}
|
|
|
|
void TestAutoType::testGlobalAutoTypeUrlMatch()
|
|
{
|
|
config()->set(Config::AutoTypeEntryTitleMatch, true);
|
|
|
|
m_test->setActiveWindowTitle("Dummy - http://example.org/ - <My Browser>");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
|
|
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter)));
|
|
}
|
|
|
|
void TestAutoType::testGlobalAutoTypeUrlSubdomainMatch()
|
|
{
|
|
config()->set(Config::AutoTypeEntryTitleMatch, true);
|
|
|
|
m_test->setActiveWindowTitle("Dummy - http://sub.example.org/ - <My Browser>");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
|
|
QCOMPARE(m_test->actionChars(), QString("%1%2").arg(m_entry5->password(), m_test->keyToString(Qt::Key_Enter)));
|
|
}
|
|
|
|
void TestAutoType::testGlobalAutoTypeTitleMatchDisabled()
|
|
{
|
|
m_test->setActiveWindowTitle("An Entry Title!");
|
|
m_test->triggerGlobalAutoType();
|
|
MessageBox::setNextAnswer(MessageBox::Ok);
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
|
|
QCOMPARE(m_test->actionChars(), QString());
|
|
}
|
|
|
|
void TestAutoType::testGlobalAutoTypeRegExp()
|
|
{
|
|
// substring matches are ok
|
|
m_test->setActiveWindowTitle("lorem REGEX1 ipsum");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
QCOMPARE(m_test->actionChars(), QString("regex1"));
|
|
m_test->clearActions();
|
|
|
|
// should be case-insensitive
|
|
m_test->setActiveWindowTitle("lorem regex1 ipsum");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
QCOMPARE(m_test->actionChars(), QString("regex1"));
|
|
m_test->clearActions();
|
|
|
|
// exact match
|
|
m_test->setActiveWindowTitle("REGEX2");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
QCOMPARE(m_test->actionChars(), QString("regex2"));
|
|
m_test->clearActions();
|
|
|
|
// a bit more complicated regex
|
|
m_test->setActiveWindowTitle("REGEX3-R2D2");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
QCOMPARE(m_test->actionChars(), QString("regex3"));
|
|
m_test->clearActions();
|
|
|
|
// with custom attributes
|
|
m_test->setActiveWindowTitle("CustomAttr1");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
QCOMPARE(m_test->actionChars(), QString("custom_attr:Attribute"));
|
|
m_test->clearActions();
|
|
|
|
// with (non uppercase) undefined custom attributes
|
|
m_test->setActiveWindowTitle("CustomAttr2");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
QCOMPARE(m_test->actionChars(), QString(""));
|
|
m_test->clearActions();
|
|
|
|
// with mixedcase default attributes
|
|
m_test->setActiveWindowTitle("CustomAttr3");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
QCOMPARE(m_test->actionChars(), QString("custom_attr"));
|
|
m_test->clearActions();
|
|
|
|
// with resolve placeholders in window association title
|
|
m_test->setActiveWindowTitle("AttrValueFirst");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
QCOMPARE(m_test->actionChars(), QString("custom_attr_first"));
|
|
m_test->clearActions();
|
|
|
|
m_test->setActiveWindowTitle("lorem AttrValueFirstAndAttrValueSecond ipsum");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
QCOMPARE(m_test->actionChars(), QString("custom_attr_first_and_second"));
|
|
m_test->clearActions();
|
|
|
|
m_test->setActiveWindowTitle("lorem AttrValueThird ipsum");
|
|
m_test->triggerGlobalAutoType();
|
|
m_autoType->performGlobalAutoType(m_dbList);
|
|
QCOMPARE(m_test->actionChars(), QString("custom_attr_third"));
|
|
m_test->clearActions();
|
|
}
|
|
|
|
void TestAutoType::testAutoTypeSyntaxChecks()
|
|
{
|
|
// Huge sequence
|
|
QVERIFY(AutoType::checkSyntax(
|
|
"{word 23}{F1 23}{~ 23}{% 23}{^}{F12}{(}{) 23}{[}{[}{]}{Delay=23}{+}{SUBTRACT}~+%@fixedstring"));
|
|
|
|
QVERIFY(AutoType::checkSyntax("{NUMPAD1 3}"));
|
|
|
|
QVERIFY(AutoType::checkSyntax("{S:SPECIALTOKEN}"));
|
|
QVERIFY(AutoType::checkSyntax("{S:SPECIAL TOKEN}"));
|
|
QVERIFY(AutoType::checkSyntax("{S:SPECIAL_TOKEN}"));
|
|
QVERIFY(AutoType::checkSyntax("{S:SPECIAL-TOKEN}"));
|
|
QVERIFY(AutoType::checkSyntax("{S:SPECIAL:TOKEN}"));
|
|
QVERIFY(AutoType::checkSyntax("{S:SPECIAL_TOKEN}{ENTER}"));
|
|
QVERIFY(AutoType::checkSyntax("{S:FOO}{S:HELLO WORLD}"));
|
|
QVERIFY(!AutoType::checkSyntax("{S:SPECIAL_TOKEN{}}"));
|
|
|
|
QVERIFY(AutoType::checkSyntax("{BEEP 3 3}"));
|
|
QVERIFY(!AutoType::checkSyntax("{BEEP 3}"));
|
|
|
|
QVERIFY(AutoType::checkSyntax("{VKEY 0x01}"));
|
|
QVERIFY(AutoType::checkSyntax("{VKEY VK_LBUTTON}"));
|
|
QVERIFY(AutoType::checkSyntax("{VKEY-EX 0x01}"));
|
|
// Bad sequence
|
|
QVERIFY(!AutoType::checkSyntax("{{{}}{}{}}{{}}"));
|
|
// Good sequence
|
|
QVERIFY(AutoType::checkSyntax("{{}{}}{}}{{}"));
|
|
QVERIFY(AutoType::checkSyntax("{]}{[}{[}{]}"));
|
|
QVERIFY(AutoType::checkSyntax("{)}{(}{(}{)}"));
|
|
// High DelAY / low delay
|
|
QVERIFY(AutoType::checkHighDelay("{DelAY 50000}"));
|
|
QVERIFY(!AutoType::checkHighDelay("{delay 50}"));
|
|
// Slow typing
|
|
QVERIFY(AutoType::checkSlowKeypress("{DelAY=50000}"));
|
|
QVERIFY(!AutoType::checkSlowKeypress("{delay=50}"));
|
|
// Many repetition / few repetition / delay not repetition
|
|
QVERIFY(AutoType::checkHighRepetition("{LEFT 50000000}"));
|
|
QVERIFY(!AutoType::checkHighRepetition("{SPACE 10}{TAB 3}{RIGHT 50}"));
|
|
QVERIFY(!AutoType::checkHighRepetition("{delay 5000000000}"));
|
|
}
|
|
|
|
void TestAutoType::testAutoTypeEffectiveSequences()
|
|
{
|
|
QString defaultSequence("{USERNAME}{TAB}{PASSWORD}{ENTER}");
|
|
QString sequenceG1("{TEST_GROUP1}");
|
|
QString sequenceG3("{TEST_GROUP3}");
|
|
QString sequenceE2("{TEST_ENTRY2}");
|
|
QString sequenceDisabled("{TEST_DISABLED}");
|
|
QString sequenceOrphan("{TEST_ORPHAN}");
|
|
|
|
QScopedPointer<Database> db(new Database());
|
|
QPointer<Group> rootGroup = db->rootGroup();
|
|
|
|
// Group with autotype enabled and custom default sequence
|
|
QPointer<Group> group1 = new Group();
|
|
group1->setParent(rootGroup);
|
|
group1->setDefaultAutoTypeSequence(sequenceG1);
|
|
|
|
// Child group with inherit
|
|
QPointer<Group> group2 = new Group();
|
|
group2->setParent(group1);
|
|
|
|
// Group with autotype disabled and custom default sequence
|
|
QPointer<Group> group3 = new Group();
|
|
group3->setParent(group1);
|
|
group3->setAutoTypeEnabled(Group::Disable);
|
|
group3->setDefaultAutoTypeSequence(sequenceG3);
|
|
|
|
QCOMPARE(rootGroup->defaultAutoTypeSequence(), QString());
|
|
QCOMPARE(rootGroup->effectiveAutoTypeSequence(), defaultSequence);
|
|
QCOMPARE(group1->defaultAutoTypeSequence(), sequenceG1);
|
|
QCOMPARE(group1->effectiveAutoTypeSequence(), sequenceG1);
|
|
QCOMPARE(group2->defaultAutoTypeSequence(), QString());
|
|
QCOMPARE(group2->effectiveAutoTypeSequence(), sequenceG1);
|
|
QCOMPARE(group3->defaultAutoTypeSequence(), sequenceG3);
|
|
QCOMPARE(group3->effectiveAutoTypeSequence(), QString());
|
|
|
|
// Entry from root group
|
|
QPointer<Entry> entry1 = new Entry();
|
|
entry1->setGroup(rootGroup);
|
|
|
|
// Entry with custom default sequence
|
|
QPointer<Entry> entry2 = new Entry();
|
|
entry2->setDefaultAutoTypeSequence(sequenceE2);
|
|
entry2->setGroup(rootGroup);
|
|
|
|
// Entry from enabled child group
|
|
QPointer<Entry> entry3 = new Entry();
|
|
entry3->setGroup(group2);
|
|
|
|
// Entry from disabled group
|
|
QPointer<Entry> entry4 = new Entry();
|
|
entry4->setDefaultAutoTypeSequence(sequenceDisabled);
|
|
entry4->setGroup(group3);
|
|
|
|
// Entry from enabled group with disabled autotype
|
|
QPointer<Entry> entry5 = new Entry();
|
|
entry5->setGroup(group2);
|
|
entry5->setDefaultAutoTypeSequence(sequenceDisabled);
|
|
entry5->setAutoTypeEnabled(false);
|
|
|
|
// Entry with no parent
|
|
QScopedPointer<Entry> entry6(new Entry());
|
|
entry6->setDefaultAutoTypeSequence(sequenceOrphan);
|
|
|
|
QCOMPARE(entry1->defaultAutoTypeSequence(), QString());
|
|
QCOMPARE(entry1->effectiveAutoTypeSequence(), defaultSequence);
|
|
QCOMPARE(entry2->defaultAutoTypeSequence(), sequenceE2);
|
|
QCOMPARE(entry2->effectiveAutoTypeSequence(), sequenceE2);
|
|
QCOMPARE(entry3->defaultAutoTypeSequence(), QString());
|
|
QCOMPARE(entry3->effectiveAutoTypeSequence(), sequenceG1);
|
|
QCOMPARE(entry4->defaultAutoTypeSequence(), sequenceDisabled);
|
|
QCOMPARE(entry4->effectiveAutoTypeSequence(), QString());
|
|
QCOMPARE(entry5->defaultAutoTypeSequence(), sequenceDisabled);
|
|
QCOMPARE(entry5->effectiveAutoTypeSequence(), QString());
|
|
QCOMPARE(entry6->defaultAutoTypeSequence(), sequenceOrphan);
|
|
QCOMPARE(entry6->effectiveAutoTypeSequence(), QString());
|
|
}
|