Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for wordlists in user configuration directory #6799

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions COPYING
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ Files: share/icons/application/scalable/actions/chevron-double-down.svg
share/icons/application/scalable/actions/statistics.svg
share/icons/application/scalable/actions/system-help.svg
share/icons/application/scalable/actions/system-search.svg
share/icons/application/scalable/actions/trash.svg
share/icons/application/scalable/actions/url-copy.svg
share/icons/application/scalable/actions/username-copy.svg
share/icons/application/scalable/actions/view-history.svg
Expand Down
1 change: 1 addition & 0 deletions share/icons/application/scalable/actions/trash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions share/icons/icons.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<file>application/scalable/actions/system-help.svg</file>
<file>application/scalable/actions/system-search.svg</file>
<file>application/scalable/actions/system-software-update.svg</file>
<file>application/scalable/actions/trash.svg</file>
<file>application/scalable/actions/url-copy.svg</file>
<file>application/scalable/actions/user-guide.svg</file>
<file>application/scalable/actions/username-copy.svg</file>
Expand Down
49 changes: 49 additions & 0 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5677,6 +5677,34 @@ We recommend you use the AppImage available on our downloads page.</source>
<source>Wordlist:</source>
<translation>Wordlist:</translation>
</message>
<message>
<source>Delete selected wordlist</source>
<translation>Delete selected wordlist</translation>
</message>
<message>
<source>Do you really want to delete the wordlist &quot;%1&quot;?</source>
<translation>Do you really want to delete the wordlist &quot;%1&quot;?</translation>
</message>
<message>
<source>Failed to delete wordlist</source>
<translation>Failed to delete wordlist</translation>
</message>
<message>
<source>Add custom wordlist</source>
<translation>Add custom wordlist</translation>
</message>
<message>
<source>Wordlists</source>
<translation>Wordlists</translation>
</message>
<message>
<source>All files</source>
<translation>All files</translation>
</message>
<message>
<source>Failed to add wordlist</source>
<translation>Failed to add wordlist</translation>
</message>
<message>
<source>Word Separator:</source>
<translation>Word Separator:</translation>
Expand Down Expand Up @@ -5861,6 +5889,27 @@ We recommend you use the AppImage available on our downloads page.</source>
<source>character</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>(SYSTEM)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Confirm Delete Wordlist</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Select Custom Wordlist</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Overwrite Wordlist?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Wordlist &quot;%1&quot; already exists as a custom wordlist.
Do you want to overwrite it?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>PickcharsDialog</name>
Expand Down
7 changes: 7 additions & 0 deletions src/core/Resources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <QLibrary>

#include "config-keepassx.h"
#include "core/Config.h"
#include "core/Global.h"

Resources* Resources::m_instance(nullptr);
Expand Down Expand Up @@ -91,6 +92,12 @@ QString Resources::wordlistPath(const QString& name) const
return dataPath(QStringLiteral("wordlists/%1").arg(name));
}

QString Resources::userWordlistPath(const QString& name) const
{
QString configPath = QFileInfo(config()->getFileName()).absolutePath();
return configPath + QStringLiteral("/wordlists/%1").arg(name);
}

Resources::Resources()
{
const QString appDirPath = QCoreApplication::applicationDirPath();
Expand Down
1 change: 1 addition & 0 deletions src/core/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Resources
QString dataPath(const QString& name) const;
QString pluginPath(const QString& name) const;
QString wordlistPath(const QString& name) const;
QString userWordlistPath(const QString& name) const;

static Resources* instance();

Expand Down
123 changes: 110 additions & 13 deletions src/gui/PasswordGeneratorWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
#include "core/PasswordHealth.h"
#include "core/Resources.h"
#include "gui/Clipboard.h"
#include "gui/FileDialog.h"
#include "gui/Icons.h"
#include "gui/MessageBox.h"
#include "gui/styles/StateColorPalette.h"

PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
Expand All @@ -42,6 +44,8 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
m_ui->buttonGenerate->setToolTip(
tr("Regenerate password (%1)").arg(m_ui->buttonGenerate->shortcut().toString(QKeySequence::NativeText)));
m_ui->buttonCopy->setIcon(icons()->icon("clipboard-text"));
m_ui->buttonDeleteWordList->setIcon(icons()->icon("trash"));
m_ui->buttonAddWordList->setIcon(icons()->icon("document-new"));
m_ui->buttonClose->setShortcut(Qt::Key_Escape);

// Add two shortcuts to save the form CTRL+Enter and CTRL+S
Expand All @@ -59,6 +63,8 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
connect(m_ui->buttonApply, SIGNAL(clicked()), SLOT(applyPassword()));
connect(m_ui->buttonCopy, SIGNAL(clicked()), SLOT(copyPassword()));
connect(m_ui->buttonGenerate, SIGNAL(clicked()), SLOT(regeneratePassword()));
connect(m_ui->buttonDeleteWordList, SIGNAL(clicked()), SLOT(deleteWordList()));
connect(m_ui->buttonAddWordList, SIGNAL(clicked()), SLOT(addWordList()));
connect(m_ui->buttonClose, SIGNAL(clicked()), SIGNAL(closed()));

connect(m_ui->sliderLength, SIGNAL(valueChanged(int)), SLOT(passwordLengthChanged(int)));
Expand Down Expand Up @@ -91,15 +97,18 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
m_ui->wordCaseComboBox->addItem(tr("UPPER CASE"), PassphraseGenerator::UPPERCASE);
m_ui->wordCaseComboBox->addItem(tr("Title Case"), PassphraseGenerator::TITLECASE);

// load system-wide wordlists
QDir path(resources()->wordlistPath(""));
QStringList files = path.entryList(QDir::Files);
m_ui->comboBoxWordList->addItems(files);
if (files.size() > 1) {
m_ui->comboBoxWordList->setVisible(true);
m_ui->labelWordList->setVisible(true);
} else {
m_ui->comboBoxWordList->setVisible(false);
m_ui->labelWordList->setVisible(false);
for (const auto& fileName : path.entryList(QDir::Files)) {
m_ui->comboBoxWordList->addItem(tr("(SYSTEM)") + " " + fileName, fileName);
}

m_firstCustomWordlistIndex = m_ui->comboBoxWordList->count();

// load user-provided wordlists
path = QDir(resources()->userWordlistPath(""));
for (const auto& fileName : path.entryList(QDir::Files)) {
m_ui->comboBoxWordList->addItem(fileName, path.absolutePath() + QDir::separator() + fileName);
}

loadSettings();
Expand Down Expand Up @@ -156,7 +165,10 @@ void PasswordGeneratorWidget::loadSettings()
// Diceware config
m_ui->spinBoxWordCount->setValue(config()->get(Config::PasswordGenerator_WordCount).toInt());
m_ui->editWordSeparator->setText(config()->get(Config::PasswordGenerator_WordSeparator).toString());
m_ui->comboBoxWordList->setCurrentText(config()->get(Config::PasswordGenerator_WordList).toString());
int i = m_ui->comboBoxWordList->findData(config()->get(Config::PasswordGenerator_WordList).toString());
if (i > -1) {
m_ui->comboBoxWordList->setCurrentIndex(i);
}
m_ui->wordCaseComboBox->setCurrentIndex(config()->get(Config::PasswordGenerator_WordCase).toInt());

// Password or diceware?
Expand Down Expand Up @@ -197,7 +209,7 @@ void PasswordGeneratorWidget::saveSettings()
// Diceware config
config()->set(Config::PasswordGenerator_WordCount, m_ui->spinBoxWordCount->value());
config()->set(Config::PasswordGenerator_WordSeparator, m_ui->editWordSeparator->text());
config()->set(Config::PasswordGenerator_WordList, m_ui->comboBoxWordList->currentText());
config()->set(Config::PasswordGenerator_WordList, m_ui->comboBoxWordList->currentData());
config()->set(Config::PasswordGenerator_WordCase, m_ui->wordCaseComboBox->currentIndex());

// Password or diceware?
Expand Down Expand Up @@ -321,6 +333,86 @@ bool PasswordGeneratorWidget::isPasswordVisible() const
return m_ui->editNewPassword->isPasswordVisible();
}

void PasswordGeneratorWidget::deleteWordList()
{
if (m_ui->comboBoxWordList->currentIndex() < m_firstCustomWordlistIndex) {
return;
}

QFile file(m_ui->comboBoxWordList->currentData().toString());
if (!file.exists()) {
return;
}

auto result = MessageBox::question(this,
tr("Confirm Delete Wordlist"),
tr("Do you really want to delete the wordlist \"%1\"?").arg(file.fileName()),
MessageBox::Delete | MessageBox::Cancel,
MessageBox::Cancel);
if (result != MessageBox::Delete) {
return;
}

if (!file.remove()) {
MessageBox::critical(this, tr("Failed to delete wordlist"), file.errorString());
return;
}

m_ui->comboBoxWordList->removeItem(m_ui->comboBoxWordList->currentIndex());
updateGenerator();
}

void PasswordGeneratorWidget::addWordList()
{
auto filter = QString("%1 (*.txt *.asc *.wordlist);;%2 (*)").arg(tr("Wordlists"), tr("All files"));
auto filePath = fileDialog()->getOpenFileName(this, tr("Select Custom Wordlist"), "", filter);
if (filePath.isEmpty()) {
return;
}

// create directory for user-specified wordlists, if necessary
QDir destDir(resources()->userWordlistPath(""));
destDir.mkpath(".");

// check if destination wordlist already exists
QString fileName = QFileInfo(filePath).fileName();
QString destPath = destDir.absolutePath() + QDir::separator() + fileName;
QFile dest(destPath);
if (dest.exists()) {
auto response = MessageBox::warning(this,
tr("Overwrite Wordlist?"),
tr("Wordlist \"%1\" already exists as a custom wordlist.\n"
"Do you want to overwrite it?")
.arg(fileName),
MessageBox::Overwrite | MessageBox::Cancel,
MessageBox::Cancel);
if (response != MessageBox::Overwrite) {
return;
}
if (!dest.remove()) {
MessageBox::critical(this, tr("Failed to delete wordlist"), dest.errorString());
return;
}
}

// copy wordlist to destination path and add corresponding item to the combo box
QFile file(filePath);
if (!file.copy(destPath)) {
MessageBox::critical(this, tr("Failed to add wordlist"), file.errorString());
return;
}

auto index = m_ui->comboBoxWordList->findData(destPath);
if (index == -1) {
m_ui->comboBoxWordList->addItem(fileName, destPath);
index = m_ui->comboBoxWordList->count() - 1;
}
m_ui->comboBoxWordList->setCurrentIndex(index);

// update the password generator
updateGenerator();
}

void PasswordGeneratorWidget::setAdvancedMode(bool advanced)
{
saveSettings();
Expand Down Expand Up @@ -532,10 +624,15 @@ void PasswordGeneratorWidget::updateGenerator()
static_cast<PassphraseGenerator::PassphraseWordCase>(m_ui->wordCaseComboBox->currentData().toInt()));

m_dicewareGenerator->setWordCount(m_ui->spinBoxWordCount->value());
if (!m_ui->comboBoxWordList->currentText().isEmpty()) {
QString path = resources()->wordlistPath(m_ui->comboBoxWordList->currentText());
m_dicewareGenerator->setWordList(path);
auto path = m_ui->comboBoxWordList->currentData().toString();
if (m_ui->comboBoxWordList->currentIndex() < m_firstCustomWordlistIndex) {
path = resources()->wordlistPath(path);
m_ui->buttonDeleteWordList->setEnabled(false);
} else {
m_ui->buttonDeleteWordList->setEnabled(true);
}
m_dicewareGenerator->setWordList(path);

m_dicewareGenerator->setWordSeparator(m_ui->editWordSeparator->text());

if (m_dicewareGenerator->isValid()) {
Expand Down
3 changes: 3 additions & 0 deletions src/gui/PasswordGeneratorWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public slots:
void applyPassword();
void copyPassword();
void setPasswordVisible(bool visible);
void deleteWordList();
void addWordList();

signals:
void appliedPassword(const QString& password);
Expand All @@ -80,6 +82,7 @@ private slots:

private:
bool m_standalone = false;
int m_firstCustomWordlistIndex;

PasswordGenerator::CharClasses charClasses();
PasswordGenerator::GeneratorFlags generatorFlags();
Expand Down
48 changes: 40 additions & 8 deletions src/gui/PasswordGeneratorWidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -862,14 +862,44 @@ QProgressBar::chunk {
</layout>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="comboBoxWordList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QComboBox" name="comboBoxWordList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonDeleteWordList">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="toolTip">
<string>Delete selected wordlist</string>
</property>
<property name="accessibleDescription">
<string>Delete selected wordlist</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonAddWordList">
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
<property name="toolTip">
<string>Add custom wordlist</string>
</property>
<property name="accessibleDescription">
<string>Add custom wordlist</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="2" alignment="Qt::AlignLeft">
<widget class="QLabel" name="charactersInPassphraseLabel">
Expand Down Expand Up @@ -990,6 +1020,8 @@ QProgressBar::chunk {
<tabstop>checkBoxExcludeAlike</tabstop>
<tabstop>checkBoxEnsureEvery</tabstop>
<tabstop>comboBoxWordList</tabstop>
<tabstop>buttonDeleteWordList</tabstop>
<tabstop>buttonAddWordList</tabstop>
<tabstop>sliderWordCount</tabstop>
<tabstop>spinBoxWordCount</tabstop>
<tabstop>editWordSeparator</tabstop>
Expand Down