From 065a85e05c9422df33a936b30f871abb8655b5f4 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Tue, 16 Jan 2018 17:23:29 +0100 Subject: [PATCH 01/22] fix effective autotype sequence --- src/core/Entry.cpp | 38 ++++++++++---------- src/core/Group.cpp | 4 +++ tests/TestAutoType.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++ tests/TestAutoType.h | 1 + 4 files changed, 103 insertions(+), 20 deletions(-) diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index d30adeeca..951c2184b 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -218,31 +218,29 @@ QString Entry::defaultAutoTypeSequence() const return m_data.defaultAutoTypeSequence; } +/** + * Determine the effective sequence that will be injected + * This function return an empty string if a parent group has autotype disabled or if the entry has no parent + */ QString Entry::effectiveAutoTypeSequence() const { + if (autoTypeEnabled() == false) { + return QString(); + } + + const Group* parent = group(); + if (!parent) { + return QString(); + } + + QString sequence = parent->effectiveAutoTypeSequence(); + if (sequence.isEmpty()) { + return QString(); + } + if (!m_data.defaultAutoTypeSequence.isEmpty()) { return m_data.defaultAutoTypeSequence; } - QString sequence; - - const Group* grp = group(); - if(grp) { - sequence = grp->effectiveAutoTypeSequence(); - } else { - return QString(); - } - - if (sequence.isEmpty() && (!username().isEmpty() || !password().isEmpty())) { - if (username().isEmpty()) { - sequence = "{PASSWORD}{ENTER}"; - } - else if (password().isEmpty()) { - sequence = "{USERNAME}{ENTER}"; - } - else { - sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; - } - } return sequence; } diff --git a/src/core/Group.cpp b/src/core/Group.cpp index bdcfeff73..e75f45268 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -192,6 +192,10 @@ QString Group::defaultAutoTypeSequence() const return m_data.defaultAutoTypeSequence; } +/** + * Determine the effective sequence that will be injected + * This function return an empty string if the current group or any parent has autotype disabled + */ QString Group::effectiveAutoTypeSequence() const { QString sequence; diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 9d2f063e8..7d26a6afe 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -310,4 +310,84 @@ void TestAutoType::testAutoTypeSyntaxChecks() QCOMPARE(true, AutoType::checkHighRepetition("{LEFT 50000000}")); QCOMPARE(false, AutoType::checkHighRepetition("{SPACE 10}{TAB 3}{RIGHT 50}")); QCOMPARE(false, 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}"); + + Database* db = new Database(); + QPointer rootGroup = db->rootGroup(); + + // Group with autotype enabled and custom default sequence + QPointer group1 = new Group(); + group1->setParent(rootGroup); + group1->setDefaultAutoTypeSequence(sequenceG1); + + // Child group with inherit + QPointer group2 = new Group(); + group2->setParent(group1); + + // Group with autotype disabled and custom default sequence + QPointer 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 entry1 = new Entry(); + entry1->setGroup(rootGroup); + + // Entry with custom default sequence + QPointer entry2 = new Entry(); + entry2->setDefaultAutoTypeSequence(sequenceE2); + entry2->setGroup(rootGroup); + + // Entry from enabled child group + QPointer entry3 = new Entry(); + entry3->setGroup(group2); + + // Entry from disabled group + QPointer entry4 = new Entry(); + entry4->setDefaultAutoTypeSequence(sequenceDisabled); + entry4->setGroup(group3); + + // Entry from enabled group with disabled autotype + QPointer entry5 = new Entry(); + entry5->setGroup(group2); + entry5->setDefaultAutoTypeSequence(sequenceDisabled); + entry5->setAutoTypeEnabled(false); + + // Entry with no parent + QPointer 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()); + + delete db; } \ No newline at end of file diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index b7c33823b..516481282 100644 --- a/tests/TestAutoType.h +++ b/tests/TestAutoType.h @@ -48,6 +48,7 @@ private slots: void testGlobalAutoTypeTitleMatchDisabled(); void testGlobalAutoTypeRegExp(); void testAutoTypeSyntaxChecks(); + void testAutoTypeEffectiveSequences(); private: AutoTypePlatformInterface* m_platform; From b5cabbeb43fb655a93d2a4cd14fc98a40e356f58 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Tue, 16 Jan 2018 22:05:58 +0100 Subject: [PATCH 02/22] add support for multiple autotype sequence, fix #559 --- src/CMakeLists.txt | 3 + src/autotype/AutoType.cpp | 96 +++++------- src/autotype/AutoType.h | 6 +- src/autotype/AutoTypeSelectDialog.cpp | 31 ++-- src/autotype/AutoTypeSelectDialog.h | 14 +- src/autotype/AutoTypeSelectView.cpp | 11 +- src/autotype/AutoTypeSelectView.h | 8 +- src/core/AutoTypeMatch.cpp | 39 +++++ src/core/AutoTypeMatch.h | 41 ++++++ src/gui/entry/AutoTypeMatchModel.cpp | 202 ++++++++++++++++++++++++++ src/gui/entry/AutoTypeMatchModel.h | 66 +++++++++ src/gui/entry/AutoTypeMatchView.cpp | 116 +++++++++++++++ src/gui/entry/AutoTypeMatchView.h | 58 ++++++++ 13 files changed, 599 insertions(+), 92 deletions(-) create mode 100644 src/core/AutoTypeMatch.cpp create mode 100644 src/core/AutoTypeMatch.h create mode 100644 src/gui/entry/AutoTypeMatchModel.cpp create mode 100644 src/gui/entry/AutoTypeMatchModel.h create mode 100644 src/gui/entry/AutoTypeMatchView.cpp create mode 100644 src/gui/entry/AutoTypeMatchView.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ccdc955f2..4b7c07cd6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,6 +39,7 @@ configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY) set(keepassx_SOURCES core/AutoTypeAssociations.cpp core/AsyncTask.h + core/AutoTypeMatch.cpp core/Config.cpp core/CsvParser.cpp core/Database.cpp @@ -137,6 +138,8 @@ set(keepassx_SOURCES gui/csvImport/CsvImportWizard.cpp gui/csvImport/CsvParserModel.cpp gui/entry/AutoTypeAssociationsModel.cpp + gui/entry/AutoTypeMatchModel.cpp + gui/entry/AutoTypeMatchView.cpp gui/entry/EditEntryWidget.cpp gui/entry/EditEntryWidget_p.h gui/entry/EntryAttachmentsModel.cpp diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 2a77c4c1d..e3b5b54c0 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -27,6 +27,7 @@ #include "autotype/AutoTypePlatformPlugin.h" #include "autotype/AutoTypeSelectDialog.h" #include "autotype/WildcardMatcher.h" +#include "core/AutoTypeMatch.h" #include "core/Config.h" #include "core/Database.h" #include "core/Entry.h" @@ -136,7 +137,12 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c QString sequence; if (customSequence.isEmpty()) { - sequence = autoTypeSequence(entry); + QList sequences = autoTypeSequences(entry); + if(sequences.isEmpty()) { + sequence = ""; + } else { + sequence = sequences.first(); + } } else { sequence = customSequence; } @@ -199,36 +205,36 @@ void AutoType::performGlobalAutoType(const QList& dbList) m_inAutoType = true; - QList entryList; - QHash sequenceHash; + QList matchList; for (Database* db : dbList) { const QList dbEntries = db->rootGroup()->entriesRecursive(); for (Entry* entry : dbEntries) { - QString sequence = autoTypeSequence(entry, windowTitle); - if (!sequence.isEmpty()) { - entryList << entry; - sequenceHash.insert(entry, sequence); + const QList sequences = autoTypeSequences(entry, windowTitle); + for (QString sequence : sequences) { + if (!sequence.isEmpty()) { + matchList << AutoTypeMatch(entry,sequence); + } } } } - if (entryList.isEmpty()) { + if (matchList.isEmpty()) { m_inAutoType = false; QString message = tr("Couldn't find an entry that matches the window title:"); message.append("\n\n"); message.append(windowTitle); MessageBox::information(nullptr, tr("Auto-Type - KeePassXC"), message); - } else if ((entryList.size() == 1) && !config()->get("security/autotypeask").toBool()) { + } else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { m_inAutoType = false; - performAutoType(entryList.first(), nullptr, sequenceHash[entryList.first()]); + performAutoType(matchList.first().entry, nullptr, matchList.first().sequence); } else { m_windowFromGlobal = m_plugin->activeWindow(); AutoTypeSelectDialog* selectDialog = new AutoTypeSelectDialog(); - connect( - selectDialog, SIGNAL(entryActivated(Entry*, QString)), SLOT(performAutoTypeFromGlobal(Entry*, QString))); + connect(selectDialog, SIGNAL(matchActivated(AutoTypeMatch)), + SLOT(performAutoTypeFromGlobal(AutoTypeMatch))); connect(selectDialog, SIGNAL(rejected()), SLOT(resetInAutoType())); - selectDialog->setEntries(entryList, sequenceHash); + selectDialog->setMatchList(matchList); #if defined(Q_OS_MAC) m_plugin->raiseOwnWindow(); Tools::wait(500); @@ -239,7 +245,7 @@ void AutoType::performGlobalAutoType(const QList& dbList) } } -void AutoType::performAutoTypeFromGlobal(Entry* entry, const QString& sequence) +void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) { Q_ASSERT(m_inAutoType); @@ -247,7 +253,7 @@ void AutoType::performAutoTypeFromGlobal(Entry* entry, const QString& sequence) m_inAutoType = false; - performAutoType(entry, nullptr, sequence, m_windowFromGlobal); + performAutoType(match.entry, nullptr, match.sequence, m_windowFromGlobal); } void AutoType::resetInAutoType() @@ -506,78 +512,56 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c return list; } -QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitle) +QList AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle) { + QList sequenceList; + if (!entry->autoTypeEnabled()) { - return QString(); + return sequenceList; } - bool enableSet = false; - QString sequence; if (!windowTitle.isEmpty()) { - bool match = false; const QList assocList = entry->autoTypeAssociations()->getAll(); for (const AutoTypeAssociations::Association& assoc : assocList) { const QString window = entry->resolveMultiplePlaceholders(assoc.window); if (windowMatches(windowTitle, window)) { if (!assoc.sequence.isEmpty()) { - sequence = assoc.sequence; + sequenceList.append(assoc.sequence); } else { - sequence = entry->defaultAutoTypeSequence(); + sequenceList.append(entry->effectiveAutoTypeSequence()); } - match = true; - break; } } - if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && + if (config()->get("AutoTypeEntryTitleMatch").toBool() && windowMatchesTitle(windowTitle, entry->resolvePlaceholder(entry->title()))) { - sequence = entry->defaultAutoTypeSequence(); - match = true; + sequenceList.append(entry->effectiveAutoTypeSequence()); } - if (!match && config()->get("AutoTypeEntryURLMatch").toBool() && + if (config()->get("AutoTypeEntryURLMatch").toBool() && windowMatchesUrl(windowTitle, entry->resolvePlaceholder(entry->url()))) { - sequence = entry->defaultAutoTypeSequence(); - match = true; + sequenceList.append(entry->effectiveAutoTypeSequence()); } - if (!match) { - return QString(); + if (sequenceList.isEmpty()) { + return sequenceList; } } else { - sequence = entry->defaultAutoTypeSequence(); + sequenceList.append(entry->effectiveAutoTypeSequence()); } const Group* group = entry->group(); do { - if (!enableSet) { - if (group->autoTypeEnabled() == Group::Disable) { - return QString(); - } else if (group->autoTypeEnabled() == Group::Enable) { - enableSet = true; - } + if (group->autoTypeEnabled() == Group::Disable) { + return QList(); + } else if (group->autoTypeEnabled() == Group::Enable) { + return sequenceList; } - - if (sequence.isEmpty()) { - sequence = group->defaultAutoTypeSequence(); - } - group = group->parentGroup(); - } while (group && (!enableSet || sequence.isEmpty())); - if (sequence.isEmpty() && (!entry->resolvePlaceholder(entry->username()).isEmpty() || - !entry->resolvePlaceholder(entry->password()).isEmpty())) { - if (entry->resolvePlaceholder(entry->username()).isEmpty()) { - sequence = "{PASSWORD}{ENTER}"; - } else if (entry->resolvePlaceholder(entry->password()).isEmpty()) { - sequence = "{USERNAME}{ENTER}"; - } else { - sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; - } - } + } while (group); - return sequence; + return sequenceList; } bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPattern) diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index eb366ae9c..5c89b4fa6 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -23,6 +23,8 @@ #include #include +#include "core/AutoTypeMatch.h" + class AutoTypeAction; class AutoTypeExecutor; class AutoTypePlatformInterface; @@ -69,7 +71,7 @@ signals: void globalShortcutTriggered(); private slots: - void performAutoTypeFromGlobal(Entry* entry, const QString& sequence); + void performAutoTypeFromGlobal(AutoTypeMatch match); void resetInAutoType(); void unloadPlugin(); @@ -79,7 +81,7 @@ private: void loadPlugin(const QString& pluginPath); bool parseActions(const QString& sequence, const Entry* entry, QList& actions); QList createActionFromTemplate(const QString& tmpl, const Entry* entry); - QString autoTypeSequence(const Entry* entry, const QString& windowTitle = QString()); + QList autoTypeSequences(const Entry* entry, const QString& windowTitle = QString()); bool windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle); bool windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl); bool windowMatches(const QString& windowTitle, const QString& windowPattern); diff --git a/src/autotype/AutoTypeSelectDialog.cpp b/src/autotype/AutoTypeSelectDialog.cpp index b39c78e1d..3ef086481 100644 --- a/src/autotype/AutoTypeSelectDialog.cpp +++ b/src/autotype/AutoTypeSelectDialog.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * 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 @@ -25,14 +26,15 @@ #include #include "autotype/AutoTypeSelectView.h" +#include "core/AutoTypeMatch.h" #include "core/Config.h" #include "core/FilePath.h" -#include "gui/entry/EntryModel.h" +#include "gui/entry/AutoTypeMatchModel.h" AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) : QDialog(parent) , m_view(new AutoTypeSelectView(this)) - , m_entryActivatedEmitted(false) + , m_matchActivatedEmitted(false) { setAttribute(Qt::WA_DeleteOnClose); // Places the window on the active (virtual) desktop instead of where the main window is. @@ -42,7 +44,7 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) setWindowIcon(filePath()->applicationIcon()); QRect screenGeometry = QApplication::desktop()->availableGeometry(QCursor::pos()); - QSize size = config()->get("GUI/AutoTypeSelectDialogSize", QSize(400, 250)).toSize(); + QSize size = config()->get("GUI/AutoTypeSelectDialogSize", QSize(600, 250)).toSize(); size.setWidth(qMin(size.width(), screenGeometry.width())); size.setHeight(qMin(size.height(), screenGeometry.height())); resize(size); @@ -56,10 +58,10 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) QLabel* descriptionLabel = new QLabel(tr("Select entry to Auto-Type:"), this); layout->addWidget(descriptionLabel); - connect(m_view, SIGNAL(activated(QModelIndex)), SLOT(emitEntryActivated(QModelIndex))); - connect(m_view, SIGNAL(clicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex))); + connect(m_view, SIGNAL(activated(QModelIndex)), SLOT(emitMatchActivated(QModelIndex))); + connect(m_view, SIGNAL(clicked(QModelIndex)), SLOT(emitMatchActivated(QModelIndex))); + connect(m_view->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(matchRemoved())); connect(m_view, SIGNAL(rejected()), SLOT(reject())); - connect(m_view->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(entryRemoved())); layout->addWidget(m_view); QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, Qt::Horizontal, this); @@ -67,10 +69,9 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) layout->addWidget(buttonBox); } -void AutoTypeSelectDialog::setEntries(const QList& entries, const QHash& sequences) +void AutoTypeSelectDialog::setMatchList(const QList& matchList) { - m_sequences = sequences; - m_view->setEntryList(entries); + m_view->setMatchList(matchList); m_view->header()->resizeSections(QHeaderView::ResizeToContents); } @@ -82,20 +83,20 @@ void AutoTypeSelectDialog::done(int r) QDialog::done(r); } -void AutoTypeSelectDialog::emitEntryActivated(const QModelIndex& index) +void AutoTypeSelectDialog::emitMatchActivated(const QModelIndex& index) { // make sure we don't emit the signal twice when both activated() and clicked() are triggered - if (m_entryActivatedEmitted) { + if (m_matchActivatedEmitted) { return; } - m_entryActivatedEmitted = true; + m_matchActivatedEmitted = true; - Entry* entry = m_view->entryFromIndex(index); + AutoTypeMatch match = m_view->matchFromIndex(index); accept(); - emit entryActivated(entry, m_sequences[entry]); + emit matchActivated(match); } -void AutoTypeSelectDialog::entryRemoved() +void AutoTypeSelectDialog::matchRemoved() { if (m_view->model()->rowCount() == 0) { reject(); diff --git a/src/autotype/AutoTypeSelectDialog.h b/src/autotype/AutoTypeSelectDialog.h index 3d9c684ed..83abd2d80 100644 --- a/src/autotype/AutoTypeSelectDialog.h +++ b/src/autotype/AutoTypeSelectDialog.h @@ -22,8 +22,9 @@ #include #include +#include "core/AutoTypeMatch.h" + class AutoTypeSelectView; -class Entry; class AutoTypeSelectDialog : public QDialog { @@ -31,22 +32,21 @@ class AutoTypeSelectDialog : public QDialog public: explicit AutoTypeSelectDialog(QWidget* parent = nullptr); - void setEntries(const QList& entries, const QHash& sequences); + void setMatchList(const QList& matchList); signals: - void entryActivated(Entry* entry, const QString& sequence); + void matchActivated(AutoTypeMatch match); public slots: void done(int r) override; private slots: - void emitEntryActivated(const QModelIndex& index); - void entryRemoved(); + void emitMatchActivated(const QModelIndex& index); + void matchRemoved(); private: AutoTypeSelectView* const m_view; - QHash m_sequences; - bool m_entryActivatedEmitted; + bool m_matchActivatedEmitted; }; #endif // KEEPASSX_AUTOTYPESELECTDIALOG_H diff --git a/src/autotype/AutoTypeSelectView.cpp b/src/autotype/AutoTypeSelectView.cpp index 7d9db4130..e4dba0515 100644 --- a/src/autotype/AutoTypeSelectView.cpp +++ b/src/autotype/AutoTypeSelectView.cpp @@ -21,15 +21,12 @@ #include AutoTypeSelectView::AutoTypeSelectView(QWidget* parent) - : EntryView(parent) + : AutoTypeMatchView(parent) { - hideColumn(3); setMouseTracking(true); setAllColumnsShowFocus(true); - setDragEnabled(false); - setSelectionMode(QAbstractItemView::SingleSelection); - connect(model(), SIGNAL(modelReset()), SLOT(selectFirstEntry())); + connect(model(), SIGNAL(modelReset()), SLOT(selectFirstMatch())); } void AutoTypeSelectView::mouseMoveEvent(QMouseEvent* event) @@ -44,10 +41,10 @@ void AutoTypeSelectView::mouseMoveEvent(QMouseEvent* event) unsetCursor(); } - EntryView::mouseMoveEvent(event); + AutoTypeMatchView::mouseMoveEvent(event); } -void AutoTypeSelectView::selectFirstEntry() +void AutoTypeSelectView::selectFirstMatch() { QModelIndex index = model()->index(0, 0); diff --git a/src/autotype/AutoTypeSelectView.h b/src/autotype/AutoTypeSelectView.h index aadf99fa6..e6a2ec652 100644 --- a/src/autotype/AutoTypeSelectView.h +++ b/src/autotype/AutoTypeSelectView.h @@ -18,11 +18,9 @@ #ifndef KEEPASSX_AUTOTYPESELECTVIEW_H #define KEEPASSX_AUTOTYPESELECTVIEW_H -#include "gui/entry/EntryView.h" +#include "gui/entry/AutoTypeMatchView.h" -class Entry; - -class AutoTypeSelectView : public EntryView +class AutoTypeSelectView : public AutoTypeMatchView { Q_OBJECT @@ -34,7 +32,7 @@ protected: void keyReleaseEvent(QKeyEvent* e) override; private slots: - void selectFirstEntry(); + void selectFirstMatch(); signals: void rejected(); diff --git a/src/core/AutoTypeMatch.cpp b/src/core/AutoTypeMatch.cpp new file mode 100644 index 000000000..c1faab9e6 --- /dev/null +++ b/src/core/AutoTypeMatch.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 KeePassXC Team + * + * 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 "AutoTypeMatch.h" + +AutoTypeMatch::AutoTypeMatch() + : entry(nullptr), + sequence() +{} + +AutoTypeMatch::AutoTypeMatch(Entry* entry, QString sequence) + : entry(entry), + sequence(sequence) +{} + +bool AutoTypeMatch::operator==(const AutoTypeMatch& other) const +{ + return entry == other.entry && sequence == other.sequence; +} + +bool AutoTypeMatch::operator!=(const AutoTypeMatch& other) const +{ + return entry != other.entry || sequence != other.sequence; +} diff --git a/src/core/AutoTypeMatch.h b/src/core/AutoTypeMatch.h new file mode 100644 index 000000000..768cf1682 --- /dev/null +++ b/src/core/AutoTypeMatch.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 KeePassXC Team + * + * 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_AUTOTYPEMATCH_H +#define KEEPASSX_AUTOTYPEMATCH_H + +#include +#include + +class Entry; + +struct AutoTypeMatch +{ + Entry* entry; + QString sequence; + + AutoTypeMatch(); + AutoTypeMatch(Entry* entry, QString sequence); + + bool operator==(const AutoTypeMatch& other) const; + bool operator!=(const AutoTypeMatch& other) const; +}; + +Q_DECLARE_TYPEINFO(AutoTypeMatch, Q_MOVABLE_TYPE); + +#endif // KEEPASSX_AUTOTYPEMATCH_H diff --git a/src/gui/entry/AutoTypeMatchModel.cpp b/src/gui/entry/AutoTypeMatchModel.cpp new file mode 100644 index 000000000..3a48e1737 --- /dev/null +++ b/src/gui/entry/AutoTypeMatchModel.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 KeePassXC Team + * + * 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 "AutoTypeMatchModel.h" + +#include + +#include "core/DatabaseIcons.h" +#include "core/Entry.h" +#include "core/Global.h" +#include "core/Group.h" +#include "core/Metadata.h" + +AutoTypeMatchModel::AutoTypeMatchModel(QObject* parent) + : QAbstractTableModel(parent) +{ +} + +AutoTypeMatch AutoTypeMatchModel::matchFromIndex(const QModelIndex& index) const +{ + Q_ASSERT(index.isValid() && index.row() < m_matches.size()); + return m_matches.at(index.row()); +} + +QModelIndex AutoTypeMatchModel::indexFromMatch(AutoTypeMatch match) const +{ + int row = m_matches.indexOf(match); + Q_ASSERT(row != -1); + return index(row, 1); +} + +void AutoTypeMatchModel::setMatchList(const QList& matches) +{ + beginResetModel(); + + severConnections(); + + m_allGroups.clear(); + m_matches = matches; + + QSet databases; + + for (AutoTypeMatch match : asConst(m_matches)) { + databases.insert(match.entry->group()->database()); + } + + for (Database* db : asConst(databases)) { + Q_ASSERT(db); + for (const Group* group : db->rootGroup()->groupsRecursive(true)) { + m_allGroups.append(group); + } + + if (db->metadata()->recycleBin()) { + m_allGroups.removeOne(db->metadata()->recycleBin()); + } + } + + for (const Group* group : asConst(m_allGroups)) { + makeConnections(group); + } + + endResetModel(); +} + +int AutoTypeMatchModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) { + return 0; + } else { + return m_matches.size(); + } +} + +int AutoTypeMatchModel::columnCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent); + + return 4; +} + +QVariant AutoTypeMatchModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + AutoTypeMatch match = matchFromIndex(index); + + if (role == Qt::DisplayRole) { + QString result; + switch (index.column()) { + case ParentGroup: + if (match.entry->group()) { + return match.entry->group()->name(); + } + break; + case Title: + return match.entry->resolveMultiplePlaceholders(match.entry->title()); + case Username: + return match.entry->resolveMultiplePlaceholders(match.entry->username()); + case Sequence: + return match.sequence; + } + } else if (role == Qt::DecorationRole) { + switch (index.column()) { + case ParentGroup: + if (match.entry->group()) { + return match.entry->group()->iconScaledPixmap(); + } + break; + case Title: + if (match.entry->isExpired()) { + return databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex); + } else { + return match.entry->iconScaledPixmap(); + } + } + } else if (role == Qt::FontRole) { + QFont font; + if (match.entry->isExpired()) { + font.setStrikeOut(true); + } + return font; + } + + return QVariant(); +} + +QVariant AutoTypeMatchModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + switch (section) { + case ParentGroup: + return tr("Group"); + case Title: + return tr("Title"); + case Username: + return tr("Username"); + case Sequence: + return tr("Sequence"); + } + } + + return QVariant(); +} + +void AutoTypeMatchModel::entryDataChanged(Entry* entry) +{ + for (int row = 0; row < m_matches.size(); row++) { + AutoTypeMatch match = m_matches[row]; + if (match.entry == entry) { + emit dataChanged(index(row, 0), index(row, columnCount()-1)); + } + } +} + + +void AutoTypeMatchModel::entryAboutToRemove(Entry* entry) +{ + for (int row = 0; row < m_matches.size(); row++) { + AutoTypeMatch match = m_matches[row]; + if (match.entry == entry) { + beginRemoveRows(QModelIndex(), row, row); + m_matches.removeAt(row); + endRemoveRows(); + row--; + } + } +} + +void AutoTypeMatchModel::entryRemoved() +{ +} + +void AutoTypeMatchModel::severConnections() +{ + for (const Group* group : asConst(m_allGroups)) { + disconnect(group, nullptr, this, nullptr); + } +} + +void AutoTypeMatchModel::makeConnections(const Group* group) +{ + connect(group, SIGNAL(entryAboutToRemove(Entry*)), SLOT(entryAboutToRemove(Entry*))); + connect(group, SIGNAL(entryRemoved(Entry*)), SLOT(entryRemoved())); + connect(group, SIGNAL(entryDataChanged(Entry*)), SLOT(entryDataChanged(Entry*))); +} diff --git a/src/gui/entry/AutoTypeMatchModel.h b/src/gui/entry/AutoTypeMatchModel.h new file mode 100644 index 000000000..8d341f5f4 --- /dev/null +++ b/src/gui/entry/AutoTypeMatchModel.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 KeePassXC Team + * + * 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_AUTOTYPEMATCHMODEL_H +#define KEEPASSX_AUTOTYPEMATCHMODEL_H + +#include + +#include "core/AutoTypeMatch.h" + +class Entry; +class Group; + +class AutoTypeMatchModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + enum ModelColumn + { + ParentGroup = 0, + Title = 1, + Username = 2, + Sequence = 3 + }; + + explicit AutoTypeMatchModel(QObject* parent = nullptr); + AutoTypeMatch matchFromIndex(const QModelIndex& index) const; + QModelIndex indexFromMatch(AutoTypeMatch match) const; + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + void setMatchList(const QList& matches); + +private Q_SLOTS: + void entryAboutToRemove(Entry* entry); + void entryRemoved(); + void entryDataChanged(Entry* entry); + +private: + void severConnections(); + void makeConnections(const Group* group); + + QList m_matches; + QList m_allGroups; +}; + +#endif // KEEPASSX_AUTOTYPEMATCHMODEL_H diff --git a/src/gui/entry/AutoTypeMatchView.cpp b/src/gui/entry/AutoTypeMatchView.cpp new file mode 100644 index 000000000..013d192fc --- /dev/null +++ b/src/gui/entry/AutoTypeMatchView.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 KeePassXC Team + * + * 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 "AutoTypeMatchView.h" + +#include +#include + +#include "gui/SortFilterHideProxyModel.h" + +AutoTypeMatchView::AutoTypeMatchView(QWidget* parent) + : QTreeView(parent) + , m_model(new AutoTypeMatchModel(this)) + , m_sortModel(new SortFilterHideProxyModel(this)) +{ + m_sortModel->setSourceModel(m_model); + m_sortModel->setDynamicSortFilter(true); + m_sortModel->setSortLocaleAware(true); + m_sortModel->setSortCaseSensitivity(Qt::CaseInsensitive); + QTreeView::setModel(m_sortModel); + + setUniformRowHeights(true); + setRootIsDecorated(false); + setAlternatingRowColors(true); + setDragEnabled(false); + setSortingEnabled(true); + setSelectionMode(QAbstractItemView::SingleSelection); + header()->setDefaultSectionSize(150); + + connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(emitMatchActivated(QModelIndex))); + connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), SIGNAL(matchSelectionChanged())); +} + +void AutoTypeMatchView::keyPressEvent(QKeyEvent* event) +{ + if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && currentIndex().isValid()) { + emitMatchActivated(currentIndex()); + } + + QTreeView::keyPressEvent(event); +} + +void AutoTypeMatchView::setMatchList(const QList& matches) +{ + m_model->setMatchList(matches); + for (int i = 0; i < m_model->columnCount(); ++i) { + resizeColumnToContents(i); + if (columnWidth(i) > 250) { + setColumnWidth(i, 250); + } + } + setFirstMatchActive(); +} + +void AutoTypeMatchView::setFirstMatchActive() +{ + if (m_model->rowCount() > 0) { + QModelIndex index = m_sortModel->mapToSource(m_sortModel->index(0, 0)); + setCurrentMatch(m_model->matchFromIndex(index)); + } else { + emit matchSelectionChanged(); + } +} + +void AutoTypeMatchView::emitMatchActivated(const QModelIndex& index) +{ + AutoTypeMatch match = matchFromIndex(index); + + emit matchActivated(match); +} + +void AutoTypeMatchView::setModel(QAbstractItemModel* model) +{ + Q_UNUSED(model); + Q_ASSERT(false); +} + +AutoTypeMatch AutoTypeMatchView::currentMatch() +{ + QModelIndexList list = selectionModel()->selectedRows(); + if (list.size() == 1) { + return m_model->matchFromIndex(m_sortModel->mapToSource(list.first())); + } else { + return AutoTypeMatch(); + } +} + +void AutoTypeMatchView::setCurrentMatch(AutoTypeMatch match) +{ + selectionModel()->setCurrentIndex(m_sortModel->mapFromSource(m_model->indexFromMatch(match)), + QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); +} + +AutoTypeMatch AutoTypeMatchView::matchFromIndex(const QModelIndex& index) +{ + if (index.isValid()) { + return m_model->matchFromIndex(m_sortModel->mapToSource(index)); + } else { + return AutoTypeMatch(); + } +} diff --git a/src/gui/entry/AutoTypeMatchView.h b/src/gui/entry/AutoTypeMatchView.h new file mode 100644 index 000000000..08c177005 --- /dev/null +++ b/src/gui/entry/AutoTypeMatchView.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 David Wu + * Copyright (C) 2017 KeePassXC Team + * + * 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_AUTOTYPEMATCHVIEW_H +#define KEEPASSX_AUTOTYPEMATCHVIEW_H + +#include + +#include "core/AutoTypeMatch.h" + +#include "gui/entry/AutoTypeMatchModel.h" + +class SortFilterHideProxyModel; + +class AutoTypeMatchView : public QTreeView +{ + Q_OBJECT + +public: + explicit AutoTypeMatchView(QWidget* parent = nullptr); + void setModel(QAbstractItemModel* model) override; + AutoTypeMatch currentMatch(); + void setCurrentMatch(AutoTypeMatch match); + AutoTypeMatch matchFromIndex(const QModelIndex& index); + void setMatchList(const QList& matches); + void setFirstMatchActive(); + +Q_SIGNALS: + void matchActivated(AutoTypeMatch match); + void matchSelectionChanged(); + +protected: + void keyPressEvent(QKeyEvent* event) override; + +private Q_SLOTS: + void emitMatchActivated(const QModelIndex& index); + +private: + AutoTypeMatchModel* const m_model; + SortFilterHideProxyModel* const m_sortModel; +}; + +#endif // KEEPASSX_AUTOTYPEMATCHVIEW_H From a9479fd66260495133a4f516b69b31ff91495040 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 18 Jan 2018 23:45:09 +0100 Subject: [PATCH 03/22] refactor autotype sequences and entry-point functions --- src/autotype/AutoType.cpp | 108 ++++++++++++++++++++++++++------------ src/autotype/AutoType.h | 12 ++--- src/core/Entry.cpp | 13 ++++- src/core/Entry.h | 3 ++ src/core/Group.cpp | 3 +- src/core/Group.h | 1 + tests/TestAutoType.cpp | 13 +---- tests/TestAutoType.h | 3 +- 8 files changed, 99 insertions(+), 57 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index e3b5b54c0..c72b61ef6 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -128,31 +128,16 @@ QStringList AutoType::windowTitles() return m_plugin->windowTitles(); } -void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& customSequence, WId window) +/** + * Core Autotype function that will execute actions + */ +void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window) { - if (m_inAutoType || !m_plugin) { + // no edit to the sequence beyond this point + if (!verifyAutoTypeSyntax(sequence)) { + m_inAutoType = false; // TODO: make this automatic return; } - m_inAutoType = true; - - QString sequence; - if (customSequence.isEmpty()) { - QList sequences = autoTypeSequences(entry); - if(sequences.isEmpty()) { - sequence = ""; - } else { - sequence = sequences.first(); - } - } else { - sequence = customSequence; - } - - if (!checkSyntax(sequence)) { - return; - } - - sequence.replace("{{}", "{LEFTBRACE}"); - sequence.replace("{}}", "{RIGHTBRACE}"); QList actions; ListDeleter actionsDeleter(&actions); @@ -191,6 +176,30 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c m_inAutoType = false; } +/** + * Single Autotype entry-point function + * Perfom autotype sequence in the active window + */ +void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) +{ + if (m_inAutoType || !m_plugin) { + return; + } + + QList sequences = autoTypeSequences(entry); + if(sequences.isEmpty()) { + return; + } + + m_inAutoType = true; + + executeAutoTypeActions(entry, hideWindow, sequences.first()); +} + +/** + * Global Autotype entry-point funcion + * Perform global autotype on the active window + */ void AutoType::performGlobalAutoType(const QList& dbList) { if (m_inAutoType || !m_plugin) { @@ -227,7 +236,7 @@ void AutoType::performGlobalAutoType(const QList& dbList) MessageBox::information(nullptr, tr("Auto-Type - KeePassXC"), message); } else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { m_inAutoType = false; - performAutoType(matchList.first().entry, nullptr, matchList.first().sequence); + executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence); } else { m_windowFromGlobal = m_plugin->activeWindow(); AutoTypeSelectDialog* selectDialog = new AutoTypeSelectDialog(); @@ -253,7 +262,7 @@ void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) m_inAutoType = false; - performAutoType(match.entry, nullptr, match.sequence, m_windowFromGlobal); + executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal); } void AutoType::resetInAutoType() @@ -283,6 +292,7 @@ void AutoType::unloadPlugin() } } + bool AutoType::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) { Q_ASSERT(key); @@ -325,12 +335,19 @@ int AutoType::callEventFilter(void* event) return m_plugin->platformEventFilter(event); } -bool AutoType::parseActions(const QString& sequence, const Entry* entry, QList& actions) +/** + * Parse an autotype sequence and resolve its Template/command inside as AutoTypeActions + */ +bool AutoType::parseActions(const QString& actionSequence, const Entry* entry, QList& actions) { QString tmpl; bool inTmpl = false; m_autoTypeDelay = config()->get("AutoTypeDelay").toInt(); + QString sequence = actionSequence; + sequence.replace("{{}", "{LEFTBRACE}"); + sequence.replace("{}}", "{RIGHTBRACE}"); + for (const QChar& ch : sequence) { if (inTmpl) { if (ch == '{') { @@ -369,6 +386,9 @@ bool AutoType::parseActions(const QString& sequence, const Entry* entry, QList AutoType::createActionFromTemplate(const QString& tmpl, const Entry* entry) { QString tmplName = tmpl; @@ -512,6 +532,9 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c return list; } +/** + * Retrive the autotype sequences matches for a given windowTitle + */ QList AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle) { QList sequenceList; @@ -564,6 +587,9 @@ QList AutoType::autoTypeSequences(const Entry* entry, const QString& wi return sequenceList; } +/** + * Checks if a window title matches a pattern + */ bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPattern) { if (windowPattern.startsWith("//") && windowPattern.endsWith("//") && windowPattern.size() >= 4) { @@ -574,11 +600,19 @@ bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPa } } +/** + * Checks if a window title matches an entry Title + * The entry title should be Spr-compiled by the caller + */ bool AutoType::windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle) { return !resolvedTitle.isEmpty() && windowTitle.contains(resolvedTitle, Qt::CaseInsensitive); } +/** + * Checks if a window title matches an entry URL + * The entry URL should be Spr-compiled by the caller + */ bool AutoType::windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl) { if (!resolvedUrl.isEmpty() && windowTitle.contains(resolvedUrl, Qt::CaseInsensitive)) { @@ -593,6 +627,9 @@ bool AutoType::windowMatchesUrl(const QString& windowTitle, const QString& resol return false; } +/** + * Checks if the overall syntax of an autotype sequence is fine + */ bool AutoType::checkSyntax(const QString& string) { QString allowRepetition = "(?:\\s\\d+)?"; @@ -618,6 +655,9 @@ bool AutoType::checkSyntax(const QString& string) return match.hasMatch(); } +/** + * Checks an autotype sequence for high delay + */ bool AutoType::checkHighDelay(const QString& string) { // 5 digit numbers(10 seconds) are too much @@ -626,6 +666,9 @@ bool AutoType::checkHighDelay(const QString& string) return match.hasMatch(); } +/** + * Checks an autotype sequence for slow keypress + */ bool AutoType::checkSlowKeypress(const QString& string) { // 3 digit numbers(100 milliseconds) are too much @@ -634,6 +677,9 @@ bool AutoType::checkSlowKeypress(const QString& string) return match.hasMatch(); } +/** + * Checks an autotype sequence for high repetition command + */ bool AutoType::checkHighRepetition(const QString& string) { // 3 digit numbers are too much @@ -642,6 +688,9 @@ bool AutoType::checkHighRepetition(const QString& string) return match.hasMatch(); } +/** + * Verify if the syntax of an autotype sequence is correct and doesn't have silly parameters + */ bool AutoType::verifyAutoTypeSyntax(const QString& sequence) { if (!AutoType::checkSyntax(sequence)) { @@ -675,12 +724,3 @@ bool AutoType::verifyAutoTypeSyntax(const QString& sequence) } return true; } - - -void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow, const QString& customSequence, WId window) -{ - auto sequence = entry->effectiveAutoTypeSequence(); - if (verifyAutoTypeSyntax(sequence)) { - executeAutoTypeActions(entry, hideWindow, customSequence, window); - } -} diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index 5c89b4fa6..db60133ae 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -38,10 +38,6 @@ class AutoType : public QObject public: QStringList windowTitles(); - void executeAutoTypeActions(const Entry* entry, - QWidget* hideWindow = nullptr, - const QString& customSequence = QString(), - WId window = 0); bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers); void unregisterGlobalShortcut(); int callEventFilter(void* event); @@ -51,9 +47,7 @@ public: static bool checkHighDelay(const QString& string); static bool verifyAutoTypeSyntax(const QString& sequence); void performAutoType(const Entry* entry, - QWidget* hideWindow = nullptr, - const QString& customSequence = QString(), - WId window = 0); + QWidget* hideWindow = nullptr); inline bool isAvailable() { @@ -79,6 +73,10 @@ private: explicit AutoType(QObject* parent = nullptr, bool test = false); ~AutoType(); void loadPlugin(const QString& pluginPath); + void executeAutoTypeActions(const Entry* entry, + QWidget* hideWindow = nullptr, + const QString& customSequence = QString(), + WId window = 0); bool parseActions(const QString& sequence, const Entry* entry, QList& actions); QList createActionFromTemplate(const QString& tmpl, const Entry* entry); QList autoTypeSequences(const Entry* entry, const QString& windowTitle = QString()); diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index 951c2184b..e4c9e720e 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -29,6 +29,8 @@ const int Entry::DefaultIconNumber = 0; const int Entry::ResolveMaximumDepth = 10; +const QString Entry::AutoTypeSequenceUsername = "{USERNAME}{ENTER}"; +const QString Entry::AutoTypeSequencePassword = "{PASSWORD}{ENTER}"; Entry::Entry() @@ -232,7 +234,7 @@ QString Entry::effectiveAutoTypeSequence() const if (!parent) { return QString(); } - + QString sequence = parent->effectiveAutoTypeSequence(); if (sequence.isEmpty()) { return QString(); @@ -242,6 +244,15 @@ QString Entry::effectiveAutoTypeSequence() const return m_data.defaultAutoTypeSequence; } + if (sequence == Group::RootAutoTypeSequence && (!username().isEmpty() || !password().isEmpty())) { + if (username().isEmpty()) { + return AutoTypeSequencePassword; + } else if (password().isEmpty()) { + return AutoTypeSequenceUsername; + } + return Group::RootAutoTypeSequence; + } + return sequence; } diff --git a/src/core/Entry.h b/src/core/Entry.h index 7b995b7ae..8579f9533 100644 --- a/src/core/Entry.h +++ b/src/core/Entry.h @@ -85,6 +85,7 @@ public: int autoTypeObfuscation() const; QString defaultAutoTypeSequence() const; QString effectiveAutoTypeSequence() const; + QString effectiveNewAutoTypeSequence() const; AutoTypeAssociations* autoTypeAssociations(); const AutoTypeAssociations* autoTypeAssociations() const; QString title() const; @@ -109,6 +110,8 @@ public: static const int DefaultIconNumber; static const int ResolveMaximumDepth; + static const QString AutoTypeSequenceUsername; + static const QString AutoTypeSequencePassword; void setUuid(const Uuid& uuid); void setIcon(int iconNumber); diff --git a/src/core/Group.cpp b/src/core/Group.cpp index e75f45268..51b24c199 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -25,6 +25,7 @@ const int Group::DefaultIconNumber = 48; const int Group::RecycleBinIconNumber = 43; +const QString Group::RootAutoTypeSequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; Group::CloneFlags Group::DefaultCloneFlags = static_cast( Group::CloneNewUuid | Group::CloneResetTimeInfo | Group::CloneIncludeEntries); @@ -211,7 +212,7 @@ QString Group::effectiveAutoTypeSequence() const } while (group && sequence.isEmpty()); if (sequence.isEmpty()) { - sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}"; + sequence = RootAutoTypeSequence; } return sequence; diff --git a/src/core/Group.h b/src/core/Group.h index 70f033196..b1654a236 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -88,6 +88,7 @@ public: static const int RecycleBinIconNumber; static CloneFlags DefaultCloneFlags; static Entry::CloneFlags DefaultEntryCloneFlags; + static const QString RootAutoTypeSequence; Group* findChildByName(const QString& name); Group* findChildByUuid(const Uuid& uuid); diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 7d26a6afe..53f455ce6 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -136,7 +136,7 @@ void TestAutoType::testInternal() QCOMPARE(m_platform->activeWindowTitle(), QString("Test")); } -void TestAutoType::testAutoTypeWithoutSequence() +void TestAutoType::testSingleAutoType() { m_autoType->performAutoType(m_entry1, nullptr); @@ -147,17 +147,6 @@ void TestAutoType::testAutoTypeWithoutSequence() .arg(m_test->keyToString(Qt::Key_Enter))); } -void TestAutoType::testAutoTypeWithSequence() -{ - m_autoType->performAutoType(m_entry1, nullptr, "{Username}abc{PaSsWoRd}"); - - QCOMPARE(m_test->actionCount(), 15); - QCOMPARE(m_test->actionChars(), - QString("%1abc%2") - .arg(m_entry1->username()) - .arg(m_entry1->password())); -} - void TestAutoType::testGlobalAutoTypeWithNoMatch() { m_test->setActiveWindowTitle("nomatch"); diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index 516481282..93a7d682c 100644 --- a/tests/TestAutoType.h +++ b/tests/TestAutoType.h @@ -38,8 +38,7 @@ private slots: void cleanup(); void testInternal(); - void testAutoTypeWithoutSequence(); - void testAutoTypeWithSequence(); + void testSingleAutoType(); void testGlobalAutoTypeWithNoMatch(); void testGlobalAutoTypeWithOneMatch(); void testGlobalAutoTypeTitleMatch(); From 12a4b9aaa3bdbb2ca6036b17244bd19d32e493dc Mon Sep 17 00:00:00 2001 From: thez3ro Date: Thu, 18 Jan 2018 23:47:24 +0100 Subject: [PATCH 04/22] reorder functions by logic --- src/autotype/AutoType.cpp | 139 +++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 70 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index c72b61ef6..5c936bf13 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -103,6 +103,19 @@ void AutoType::loadPlugin(const QString& pluginPath) } } +void AutoType::unloadPlugin() +{ + if (m_executor) { + delete m_executor; + m_executor = nullptr; + } + + if (m_plugin) { + m_plugin->unload(); + m_plugin = nullptr; + } +} + AutoType* AutoType::instance() { if (!m_instance) { @@ -128,6 +141,62 @@ QStringList AutoType::windowTitles() return m_plugin->windowTitles(); } +void AutoType::resetInAutoType() +{ + Q_ASSERT(m_inAutoType); + + m_inAutoType = false; +} + +void AutoType::raiseWindow() +{ +#if defined(Q_OS_MAC) + m_plugin->raiseOwnWindow(); +#endif +} + +bool AutoType::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) +{ + Q_ASSERT(key); + Q_ASSERT(modifiers); + + if (!m_plugin) { + return false; + } + + if (key != m_currentGlobalKey || modifiers != m_currentGlobalModifiers) { + if (m_currentGlobalKey && m_currentGlobalModifiers) { + m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers); + } + + if (m_plugin->registerGlobalShortcut(key, modifiers)) { + m_currentGlobalKey = key; + m_currentGlobalModifiers = modifiers; + return true; + } else { + return false; + } + } else { + return true; + } +} + +void AutoType::unregisterGlobalShortcut() +{ + if (m_plugin && m_currentGlobalKey && m_currentGlobalModifiers) { + m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers); + } +} + +int AutoType::callEventFilter(void* event) +{ + if (!m_plugin) { + return -1; + } + + return m_plugin->platformEventFilter(event); +} + /** * Core Autotype function that will execute actions */ @@ -265,76 +334,6 @@ void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal); } -void AutoType::resetInAutoType() -{ - Q_ASSERT(m_inAutoType); - - m_inAutoType = false; -} - -void AutoType::raiseWindow() -{ -#if defined(Q_OS_MAC) - m_plugin->raiseOwnWindow(); -#endif -} - -void AutoType::unloadPlugin() -{ - if (m_executor) { - delete m_executor; - m_executor = nullptr; - } - - if (m_plugin) { - m_plugin->unload(); - m_plugin = nullptr; - } -} - - -bool AutoType::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) -{ - Q_ASSERT(key); - Q_ASSERT(modifiers); - - if (!m_plugin) { - return false; - } - - if (key != m_currentGlobalKey || modifiers != m_currentGlobalModifiers) { - if (m_currentGlobalKey && m_currentGlobalModifiers) { - m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers); - } - - if (m_plugin->registerGlobalShortcut(key, modifiers)) { - m_currentGlobalKey = key; - m_currentGlobalModifiers = modifiers; - return true; - } else { - return false; - } - } else { - return true; - } -} - -void AutoType::unregisterGlobalShortcut() -{ - if (m_plugin && m_currentGlobalKey && m_currentGlobalModifiers) { - m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers); - } -} - -int AutoType::callEventFilter(void* event) -{ - if (!m_plugin) { - return -1; - } - - return m_plugin->platformEventFilter(event); -} - /** * Parse an autotype sequence and resolve its Template/command inside as AutoTypeActions */ From ba4ef52e9ead8d33edb30d4bd23796706c6f3ba8 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 19 Jan 2018 00:50:22 +0100 Subject: [PATCH 05/22] improve Window Associations UI/UX --- src/gui/entry/EditEntryWidget.cpp | 21 ++++++++------------- src/gui/entry/EditEntryWidgetAutoType.ui | 12 ++---------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index c146da691..71366651f 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -170,8 +170,7 @@ void EditEntryWidget::setupAutoType() m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->inheritSequenceButton); m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->customSequenceButton); - m_autoTypeWindowSequenceGroup->addButton(m_autoTypeUi->defaultWindowSequenceButton); - m_autoTypeWindowSequenceGroup->addButton(m_autoTypeUi->customWindowSequenceButton); + //m_autoTypeWindowSequenceGroup->addButton(m_autoTypeUi->customWindowSequenceButton); m_autoTypeAssocModel->setAutoTypeAssociations(m_autoTypeAssoc); m_autoTypeUi->assocView->setModel(m_autoTypeAssocModel); m_autoTypeUi->assocView->setColumnHidden(1, true); @@ -190,8 +189,6 @@ void EditEntryWidget::setupAutoType() connect(m_autoTypeAssocModel, SIGNAL(modelReset()), SLOT(clearCurrentAssoc())); connect(m_autoTypeUi->windowTitleCombo, SIGNAL(editTextChanged(QString)), SLOT(applyCurrentAssoc())); - connect(m_autoTypeUi->defaultWindowSequenceButton, SIGNAL(toggled(bool)), - SLOT(applyCurrentAssoc())); connect(m_autoTypeUi->windowSequenceEdit, SIGNAL(textChanged(QString)), SLOT(applyCurrentAssoc())); } @@ -644,7 +641,7 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore) } m_autoTypeUi->sequenceEdit->setText(entry->effectiveAutoTypeSequence()); m_autoTypeUi->windowTitleCombo->lineEdit()->clear(); - m_autoTypeUi->defaultWindowSequenceButton->setChecked(true); + m_autoTypeUi->customWindowSequenceButton->setChecked(false); m_autoTypeUi->windowSequenceEdit->setText(""); m_autoTypeAssoc->copyDataFrom(entry->autoTypeAssociations()); m_autoTypeAssocModel->setEntry(entry); @@ -998,7 +995,6 @@ void EditEntryWidget::updateAutoTypeEnabled() m_autoTypeUi->windowTitleLabel->setEnabled(autoTypeEnabled && validIndex); m_autoTypeUi->windowTitleCombo->setEnabled(autoTypeEnabled && validIndex); - m_autoTypeUi->defaultWindowSequenceButton->setEnabled(!m_history && autoTypeEnabled && validIndex); m_autoTypeUi->customWindowSequenceButton->setEnabled(!m_history && autoTypeEnabled && validIndex); m_autoTypeUi->windowSequenceEdit->setEnabled(autoTypeEnabled && validIndex && m_autoTypeUi->customWindowSequenceButton->isChecked()); @@ -1029,16 +1025,15 @@ void EditEntryWidget::loadCurrentAssoc(const QModelIndex& current) AutoTypeAssociations::Association assoc = m_autoTypeAssoc->get(current.row()); m_autoTypeUi->windowTitleCombo->setEditText(assoc.window); if (assoc.sequence.isEmpty()) { - m_autoTypeUi->defaultWindowSequenceButton->setChecked(true); - } - else { + m_autoTypeUi->customWindowSequenceButton->setChecked(false); + m_autoTypeUi->windowSequenceEdit->setText(m_entry->effectiveAutoTypeSequence()); + } else { m_autoTypeUi->customWindowSequenceButton->setChecked(true); + m_autoTypeUi->windowSequenceEdit->setText(assoc.sequence); } - m_autoTypeUi->windowSequenceEdit->setText(assoc.sequence); updateAutoTypeEnabled(); - } - else { + } else { clearCurrentAssoc(); } } @@ -1047,7 +1042,7 @@ void EditEntryWidget::clearCurrentAssoc() { m_autoTypeUi->windowTitleCombo->setEditText(""); - m_autoTypeUi->defaultWindowSequenceButton->setChecked(true); + m_autoTypeUi->customWindowSequenceButton->setChecked(false); m_autoTypeUi->windowSequenceEdit->setText(""); updateAutoTypeEnabled(); diff --git a/src/gui/entry/EditEntryWidgetAutoType.ui b/src/gui/entry/EditEntryWidgetAutoType.ui index a8090f768..3d4ec7a3e 100644 --- a/src/gui/entry/EditEntryWidgetAutoType.ui +++ b/src/gui/entry/EditEntryWidgetAutoType.ui @@ -203,16 +203,9 @@ - + - Use default se&quence - - - - - - - Set custo&m sequence: + Use a specific sequence for this association: @@ -277,7 +270,6 @@ sequenceEdit assocView windowTitleCombo - defaultWindowSequenceButton customWindowSequenceButton windowSequenceEdit assocAddButton From a76c92ed9a3eb9d087b6a3f06836bbcac77fc492 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 24 Jan 2018 20:08:56 +0100 Subject: [PATCH 06/22] change inAutotype logic, preventing multiple autotype call --- src/autotype/AutoType.cpp | 47 ++++++++++++++-------------- src/core/Entry.cpp | 8 ++--- src/gui/entry/AutoTypeMatchModel.cpp | 17 +++++----- src/gui/entry/AutoTypeMatchModel.h | 2 +- src/gui/entry/AutoTypeMatchView.cpp | 14 ++------- src/gui/entry/AutoTypeMatchView.h | 5 ++- src/gui/entry/EditEntryWidget.cpp | 1 - tests/TestAutoType.cpp | 6 ++-- 8 files changed, 44 insertions(+), 56 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 5c936bf13..5bd10115a 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -173,12 +173,10 @@ bool AutoType::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifie m_currentGlobalKey = key; m_currentGlobalModifiers = modifiers; return true; - } else { - return false; } - } else { - return true; + return false; } + return true; } void AutoType::unregisterGlobalShortcut() @@ -202,9 +200,13 @@ int AutoType::callEventFilter(void* event) */ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window) { + Q_ASSERT(m_inAutoType); + if (!m_inAutoType) { + return; + } + // no edit to the sequence beyond this point if (!verifyAutoTypeSyntax(sequence)) { - m_inAutoType = false; // TODO: make this automatic return; } @@ -212,7 +214,6 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c ListDeleter actionsDeleter(&actions); if (!parseActions(sequence, entry, actions)) { - m_inAutoType = false; // TODO: make this automatic return; } @@ -241,8 +242,6 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c action->accept(m_executor); QCoreApplication::processEvents(QEventLoop::AllEvents, 10); } - - m_inAutoType = false; } /** @@ -256,13 +255,15 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) } QList sequences = autoTypeSequences(entry); - if(sequences.isEmpty()) { + if (sequences.isEmpty()) { return; } m_inAutoType = true; executeAutoTypeActions(entry, hideWindow, sequences.first()); + + m_inAutoType = false; } /** @@ -304,8 +305,8 @@ void AutoType::performGlobalAutoType(const QList& dbList) message.append(windowTitle); MessageBox::information(nullptr, tr("Auto-Type - KeePassXC"), message); } else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { - m_inAutoType = false; executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence); + m_inAutoType = false; } else { m_windowFromGlobal = m_plugin->activeWindow(); AutoTypeSelectDialog* selectDialog = new AutoTypeSelectDialog(); @@ -329,9 +330,9 @@ void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) m_plugin->raiseWindow(m_windowFromGlobal); - m_inAutoType = false; - executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal); + + m_inAutoType = false; } /** @@ -542,6 +543,17 @@ QList AutoType::autoTypeSequences(const Entry* entry, const QString& wi return sequenceList; } + const Group* group = entry->group(); + do { + if (group->autoTypeEnabled() == Group::Disable) { + return sequenceList; + } else if (group->autoTypeEnabled() == Group::Enable) { + break; + } + group = group->parentGroup(); + + } while (group); + if (!windowTitle.isEmpty()) { const QList assocList = entry->autoTypeAssociations()->getAll(); for (const AutoTypeAssociations::Association& assoc : assocList) { @@ -572,17 +584,6 @@ QList AutoType::autoTypeSequences(const Entry* entry, const QString& wi sequenceList.append(entry->effectiveAutoTypeSequence()); } - const Group* group = entry->group(); - do { - if (group->autoTypeEnabled() == Group::Disable) { - return QList(); - } else if (group->autoTypeEnabled() == Group::Enable) { - return sequenceList; - } - group = group->parentGroup(); - - } while (group); - return sequenceList; } diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp index e4c9e720e..8db955c93 100644 --- a/src/core/Entry.cpp +++ b/src/core/Entry.cpp @@ -226,18 +226,18 @@ QString Entry::defaultAutoTypeSequence() const */ QString Entry::effectiveAutoTypeSequence() const { - if (autoTypeEnabled() == false) { - return QString(); + if (!autoTypeEnabled()) { + return {}; } const Group* parent = group(); if (!parent) { - return QString(); + return {}; } QString sequence = parent->effectiveAutoTypeSequence(); if (sequence.isEmpty()) { - return QString(); + return {}; } if (!m_data.defaultAutoTypeSequence.isEmpty()) { diff --git a/src/gui/entry/AutoTypeMatchModel.cpp b/src/gui/entry/AutoTypeMatchModel.cpp index 3a48e1737..6a370dea5 100644 --- a/src/gui/entry/AutoTypeMatchModel.cpp +++ b/src/gui/entry/AutoTypeMatchModel.cpp @@ -55,7 +55,7 @@ void AutoTypeMatchModel::setMatchList(const QList& matches) QSet databases; - for (AutoTypeMatch match : asConst(m_matches)) { + for (AutoTypeMatch& match : m_matches) { databases.insert(match.entry->group()->database()); } @@ -81,9 +81,8 @@ int AutoTypeMatchModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; - } else { - return m_matches.size(); } + return m_matches.size(); } int AutoTypeMatchModel::columnCount(const QModelIndex& parent) const @@ -96,7 +95,7 @@ int AutoTypeMatchModel::columnCount(const QModelIndex& parent) const QVariant AutoTypeMatchModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { - return QVariant(); + return {}; } AutoTypeMatch match = matchFromIndex(index); @@ -138,7 +137,7 @@ QVariant AutoTypeMatchModel::data(const QModelIndex& index, int role) const return font; } - return QVariant(); + return {}; } QVariant AutoTypeMatchModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -156,12 +155,12 @@ QVariant AutoTypeMatchModel::headerData(int section, Qt::Orientation orientation } } - return QVariant(); + return {}; } void AutoTypeMatchModel::entryDataChanged(Entry* entry) { - for (int row = 0; row < m_matches.size(); row++) { + for (int row = 0; row < m_matches.size(); ++row) { AutoTypeMatch match = m_matches[row]; if (match.entry == entry) { emit dataChanged(index(row, 0), index(row, columnCount()-1)); @@ -172,13 +171,13 @@ void AutoTypeMatchModel::entryDataChanged(Entry* entry) void AutoTypeMatchModel::entryAboutToRemove(Entry* entry) { - for (int row = 0; row < m_matches.size(); row++) { + for (int row = 0; row < m_matches.size(); ++row) { AutoTypeMatch match = m_matches[row]; if (match.entry == entry) { beginRemoveRows(QModelIndex(), row, row); m_matches.removeAt(row); endRemoveRows(); - row--; + --row; } } } diff --git a/src/gui/entry/AutoTypeMatchModel.h b/src/gui/entry/AutoTypeMatchModel.h index 8d341f5f4..791dbc3df 100644 --- a/src/gui/entry/AutoTypeMatchModel.h +++ b/src/gui/entry/AutoTypeMatchModel.h @@ -50,7 +50,7 @@ public: void setMatchList(const QList& matches); -private Q_SLOTS: +private slots: void entryAboutToRemove(Entry* entry); void entryRemoved(); void entryDataChanged(Entry* entry); diff --git a/src/gui/entry/AutoTypeMatchView.cpp b/src/gui/entry/AutoTypeMatchView.cpp index 013d192fc..ad7d16ddc 100644 --- a/src/gui/entry/AutoTypeMatchView.cpp +++ b/src/gui/entry/AutoTypeMatchView.cpp @@ -61,7 +61,7 @@ void AutoTypeMatchView::setMatchList(const QList& matches) for (int i = 0; i < m_model->columnCount(); ++i) { resizeColumnToContents(i); if (columnWidth(i) > 250) { - setColumnWidth(i, 250); + setColumnWidth(i, 250); } } setFirstMatchActive(); @@ -84,20 +84,13 @@ void AutoTypeMatchView::emitMatchActivated(const QModelIndex& index) emit matchActivated(match); } -void AutoTypeMatchView::setModel(QAbstractItemModel* model) -{ - Q_UNUSED(model); - Q_ASSERT(false); -} - AutoTypeMatch AutoTypeMatchView::currentMatch() { QModelIndexList list = selectionModel()->selectedRows(); if (list.size() == 1) { return m_model->matchFromIndex(m_sortModel->mapToSource(list.first())); - } else { - return AutoTypeMatch(); } + return AutoTypeMatch(); } void AutoTypeMatchView::setCurrentMatch(AutoTypeMatch match) @@ -110,7 +103,6 @@ AutoTypeMatch AutoTypeMatchView::matchFromIndex(const QModelIndex& index) { if (index.isValid()) { return m_model->matchFromIndex(m_sortModel->mapToSource(index)); - } else { - return AutoTypeMatch(); } + return AutoTypeMatch(); } diff --git a/src/gui/entry/AutoTypeMatchView.h b/src/gui/entry/AutoTypeMatchView.h index 08c177005..14ad9ea2a 100644 --- a/src/gui/entry/AutoTypeMatchView.h +++ b/src/gui/entry/AutoTypeMatchView.h @@ -33,21 +33,20 @@ class AutoTypeMatchView : public QTreeView public: explicit AutoTypeMatchView(QWidget* parent = nullptr); - void setModel(QAbstractItemModel* model) override; AutoTypeMatch currentMatch(); void setCurrentMatch(AutoTypeMatch match); AutoTypeMatch matchFromIndex(const QModelIndex& index); void setMatchList(const QList& matches); void setFirstMatchActive(); -Q_SIGNALS: +signals: void matchActivated(AutoTypeMatch match); void matchSelectionChanged(); protected: void keyPressEvent(QKeyEvent* event) override; -private Q_SLOTS: +private slots: void emitMatchActivated(const QModelIndex& index); private: diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp index 71366651f..bab5a0728 100644 --- a/src/gui/entry/EditEntryWidget.cpp +++ b/src/gui/entry/EditEntryWidget.cpp @@ -170,7 +170,6 @@ void EditEntryWidget::setupAutoType() m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->inheritSequenceButton); m_autoTypeDefaultSequenceGroup->addButton(m_autoTypeUi->customSequenceButton); - //m_autoTypeWindowSequenceGroup->addButton(m_autoTypeUi->customWindowSequenceButton); m_autoTypeAssocModel->setAutoTypeAssociations(m_autoTypeAssoc); m_autoTypeUi->assocView->setModel(m_autoTypeAssocModel); m_autoTypeUi->assocView->setColumnHidden(1, true); diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 53f455ce6..7590bc613 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -310,7 +310,7 @@ void TestAutoType::testAutoTypeEffectiveSequences() QString sequenceDisabled("{TEST_DISABLED}"); QString sequenceOrphan("{TEST_ORPHAN}"); - Database* db = new Database(); + QScopedPointer db(new Database()); QPointer rootGroup = db->rootGroup(); // Group with autotype enabled and custom default sequence @@ -362,7 +362,7 @@ void TestAutoType::testAutoTypeEffectiveSequences() entry5->setAutoTypeEnabled(false); // Entry with no parent - QPointer entry6 = new Entry(); + QScopedPointer entry6(new Entry()); entry6->setDefaultAutoTypeSequence(sequenceOrphan); QCOMPARE(entry1->defaultAutoTypeSequence(), QString()); @@ -377,6 +377,4 @@ void TestAutoType::testAutoTypeEffectiveSequences() QCOMPARE(entry5->effectiveAutoTypeSequence(), QString()); QCOMPARE(entry6->defaultAutoTypeSequence(), sequenceOrphan); QCOMPARE(entry6->effectiveAutoTypeSequence(), QString()); - - delete db; } \ No newline at end of file From b4cf98998e579704c453f03897c354e5d01a1f49 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Mon, 29 Jan 2018 19:44:32 +0100 Subject: [PATCH 07/22] convert inAutoType from boolean block to QMutex --- src/autotype/AutoType.cpp | 35 ++++++++++++++++------------------- src/autotype/AutoType.h | 5 +++-- src/autotype/AutoTypeAction.h | 1 + 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 5bd10115a..73bb3ef4b 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -41,7 +41,6 @@ AutoType* AutoType::m_instance = nullptr; AutoType::AutoType(QObject* parent, bool test) : QObject(parent) - , m_inAutoType(false) , m_autoTypeDelay(0) , m_currentGlobalKey(static_cast(0)) , m_currentGlobalModifiers(0) @@ -143,9 +142,7 @@ QStringList AutoType::windowTitles() void AutoType::resetInAutoType() { - Q_ASSERT(m_inAutoType); - - m_inAutoType = false; + m_inAutoType.unlock(); } void AutoType::raiseWindow() @@ -200,11 +197,6 @@ int AutoType::callEventFilter(void* event) */ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window) { - Q_ASSERT(m_inAutoType); - if (!m_inAutoType) { - return; - } - // no edit to the sequence beyond this point if (!verifyAutoTypeSyntax(sequence)) { return; @@ -250,7 +242,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c */ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) { - if (m_inAutoType || !m_plugin) { + if (!m_plugin) { return; } @@ -259,11 +251,13 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) return; } - m_inAutoType = true; + if (!m_inAutoType.tryLock()) { + return; + } executeAutoTypeActions(entry, hideWindow, sequences.first()); - m_inAutoType = false; + m_inAutoType.unlock(); } /** @@ -272,7 +266,7 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow) */ void AutoType::performGlobalAutoType(const QList& dbList) { - if (m_inAutoType || !m_plugin) { + if (!m_plugin) { return; } @@ -282,7 +276,9 @@ void AutoType::performGlobalAutoType(const QList& dbList) return; } - m_inAutoType = true; + if (!m_inAutoType.tryLock()) { + return; + } QList matchList; @@ -290,7 +286,7 @@ void AutoType::performGlobalAutoType(const QList& dbList) const QList dbEntries = db->rootGroup()->entriesRecursive(); for (Entry* entry : dbEntries) { const QList sequences = autoTypeSequences(entry, windowTitle); - for (QString sequence : sequences) { + for (const QString& sequence : sequences) { if (!sequence.isEmpty()) { matchList << AutoTypeMatch(entry,sequence); } @@ -299,14 +295,14 @@ void AutoType::performGlobalAutoType(const QList& dbList) } if (matchList.isEmpty()) { - m_inAutoType = false; + m_inAutoType.unlock(); QString message = tr("Couldn't find an entry that matches the window title:"); message.append("\n\n"); message.append(windowTitle); MessageBox::information(nullptr, tr("Auto-Type - KeePassXC"), message); } else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence); - m_inAutoType = false; + m_inAutoType.unlock(); } else { m_windowFromGlobal = m_plugin->activeWindow(); AutoTypeSelectDialog* selectDialog = new AutoTypeSelectDialog(); @@ -326,13 +322,14 @@ void AutoType::performGlobalAutoType(const QList& dbList) void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match) { - Q_ASSERT(m_inAutoType); + // We don't care about the result here, the mutex should already be locked. Now it's locked for sure + m_inAutoType.tryLock(); m_plugin->raiseWindow(m_windowFromGlobal); executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal); - m_inAutoType = false; + m_inAutoType.unlock(); } /** diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index db60133ae..3b22106bd 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -1,4 +1,4 @@ -/* + /* * Copyright (C) 2012 Felix Geyer * Copyright (C) 2017 KeePassXC Team * @@ -22,6 +22,7 @@ #include #include #include +#include #include "core/AutoTypeMatch.h" @@ -84,7 +85,7 @@ private: bool windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl); bool windowMatches(const QString& windowTitle, const QString& windowPattern); - bool m_inAutoType; + QMutex m_inAutoType; int m_autoTypeDelay; Qt::Key m_currentGlobalKey; Qt::KeyboardModifiers m_currentGlobalModifiers; diff --git a/src/autotype/AutoTypeAction.h b/src/autotype/AutoTypeAction.h index 490f0d89f..7f0d829c0 100644 --- a/src/autotype/AutoTypeAction.h +++ b/src/autotype/AutoTypeAction.h @@ -20,6 +20,7 @@ #include #include +#include #include "core/Global.h" From 16fad1aba155573cd417caace672ffb286800c8c Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 31 Jan 2018 21:53:53 +0100 Subject: [PATCH 08/22] fix duplicate autotype sequences --- src/autotype/AutoType.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 73bb3ef4b..0dfdedaec 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -285,10 +285,10 @@ void AutoType::performGlobalAutoType(const QList& dbList) for (Database* db : dbList) { const QList dbEntries = db->rootGroup()->entriesRecursive(); for (Entry* entry : dbEntries) { - const QList sequences = autoTypeSequences(entry, windowTitle); + const QSet sequences = autoTypeSequences(entry, windowTitle).toSet(); for (const QString& sequence : sequences) { if (!sequence.isEmpty()) { - matchList << AutoTypeMatch(entry,sequence); + matchList << AutoTypeMatch(entry, sequence); } } } @@ -531,6 +531,7 @@ QList AutoType::createActionFromTemplate(const QString& tmpl, c /** * Retrive the autotype sequences matches for a given windowTitle + * This returns a list with priority ordering. If you don't want duplicates call .toSet() on it. */ QList AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle) { From aa54c7b6b372100a56eb1772be3f87b0e57a94fd Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sun, 4 Feb 2018 23:32:51 +0100 Subject: [PATCH 09/22] fix MatchView activation with Enter/Return on macOS --- src/gui/entry/AutoTypeMatchView.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/entry/AutoTypeMatchView.cpp b/src/gui/entry/AutoTypeMatchView.cpp index ad7d16ddc..67f38c79e 100644 --- a/src/gui/entry/AutoTypeMatchView.cpp +++ b/src/gui/entry/AutoTypeMatchView.cpp @@ -50,6 +50,10 @@ void AutoTypeMatchView::keyPressEvent(QKeyEvent* event) { if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && currentIndex().isValid()) { emitMatchActivated(currentIndex()); +#ifdef Q_OS_MAC + // Pressing return does not emit the QTreeView::activated signal on mac os + emit activated(currentIndex()); +#endif } QTreeView::keyPressEvent(event); From b33259b1f2383602509ae97dc05d98661000f741 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Mon, 5 Feb 2018 13:25:16 +0100 Subject: [PATCH 10/22] relock database after successful autotype --- src/autotype/AutoType.cpp | 12 +++++++++++- src/autotype/AutoType.h | 2 ++ src/autotype/AutoTypeSelectDialog.cpp | 12 ++++++++++++ src/autotype/AutoTypeSelectDialog.h | 2 ++ src/gui/DatabaseTabWidget.cpp | 26 +++++++++++++++++++++++++- src/gui/DatabaseTabWidget.h | 2 ++ src/gui/SettingsWidget.cpp | 2 ++ src/gui/SettingsWidgetSecurity.ui | 7 +++++++ 8 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 0dfdedaec..e1f14d5bc 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -143,6 +143,8 @@ QStringList AutoType::windowTitles() void AutoType::resetInAutoType() { m_inAutoType.unlock(); + + emit autotypeRejected(); } void AutoType::raiseWindow() @@ -199,6 +201,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c { // no edit to the sequence beyond this point if (!verifyAutoTypeSyntax(sequence)) { + emit autotypeRejected(); return; } @@ -206,6 +209,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c ListDeleter actionsDeleter(&actions); if (!parseActions(sequence, entry, actions)) { + emit autotypeRejected(); return; } @@ -228,12 +232,16 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c for (AutoTypeAction* action : asConst(actions)) { if (m_plugin->activeWindow() != window) { qWarning("Active window changed, interrupting auto-type."); - break; + emit autotypeRejected(); + return; } action->accept(m_executor); QCoreApplication::processEvents(QEventLoop::AllEvents, 10); } + + // emit signal only if autotype performed correctly + emit autotypePerformed(); } /** @@ -300,6 +308,8 @@ void AutoType::performGlobalAutoType(const QList& dbList) message.append("\n\n"); message.append(windowTitle); MessageBox::information(nullptr, tr("Auto-Type - KeePassXC"), message); + + emit autotypeRejected(); } else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) { executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence); m_inAutoType.unlock(); diff --git a/src/autotype/AutoType.h b/src/autotype/AutoType.h index 3b22106bd..98a7bd7fa 100644 --- a/src/autotype/AutoType.h +++ b/src/autotype/AutoType.h @@ -64,6 +64,8 @@ public slots: signals: void globalShortcutTriggered(); + void autotypePerformed(); + void autotypeRejected(); private slots: void performAutoTypeFromGlobal(AutoTypeMatch match); diff --git a/src/autotype/AutoTypeSelectDialog.cpp b/src/autotype/AutoTypeSelectDialog.cpp index 3ef086481..eae9e6ffb 100644 --- a/src/autotype/AutoTypeSelectDialog.cpp +++ b/src/autotype/AutoTypeSelectDialog.cpp @@ -35,6 +35,7 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent) : QDialog(parent) , m_view(new AutoTypeSelectView(this)) , m_matchActivatedEmitted(false) + , m_rejected(false) { setAttribute(Qt::WA_DeleteOnClose); // Places the window on the active (virtual) desktop instead of where the main window is. @@ -83,6 +84,13 @@ void AutoTypeSelectDialog::done(int r) QDialog::done(r); } +void AutoTypeSelectDialog::reject() +{ + m_rejected = true; + + QDialog::reject(); +} + void AutoTypeSelectDialog::emitMatchActivated(const QModelIndex& index) { // make sure we don't emit the signal twice when both activated() and clicked() are triggered @@ -98,6 +106,10 @@ void AutoTypeSelectDialog::emitMatchActivated(const QModelIndex& index) void AutoTypeSelectDialog::matchRemoved() { + if (m_rejected) { + return; + } + if (m_view->model()->rowCount() == 0) { reject(); } diff --git a/src/autotype/AutoTypeSelectDialog.h b/src/autotype/AutoTypeSelectDialog.h index 83abd2d80..cee3c4087 100644 --- a/src/autotype/AutoTypeSelectDialog.h +++ b/src/autotype/AutoTypeSelectDialog.h @@ -39,6 +39,7 @@ signals: public slots: void done(int r) override; + void reject() override; private slots: void emitMatchActivated(const QModelIndex& index); @@ -47,6 +48,7 @@ private slots: private: AutoTypeSelectView* const m_view; bool m_matchActivatedEmitted; + bool m_rejected; }; #endif // KEEPASSX_AUTOTYPESELECTDIALOG_H diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index f9b7fbd72..b3963f7b1 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -54,6 +54,7 @@ const int DatabaseTabWidget::LastDatabasesCount = 5; DatabaseTabWidget::DatabaseTabWidget(QWidget* parent) : QTabWidget(parent) , m_dbWidgetStateSync(new DatabaseWidgetStateSync(this)) + , m_dbPendingLock(nullptr) { DragTabBar* tabBar = new DragTabBar(this); setTabBar(tabBar); @@ -63,6 +64,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent) connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged())); connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), m_dbWidgetStateSync, SLOT(setActive(DatabaseWidget*))); connect(autoType(), SIGNAL(globalShortcutTriggered()), SLOT(performGlobalAutoType())); + connect(autoType(), SIGNAL(autotypePerformed()), SLOT(relockPendingDatabase())); } DatabaseTabWidget::~DatabaseTabWidget() @@ -737,6 +739,27 @@ void DatabaseTabWidget::lockDatabases() } } +/** + * This function relock the pending database when autotype has been performed successfully + * A database is marked as pending when it's unlocked after a global Auto-Type invocation + */ +void DatabaseTabWidget::relockPendingDatabase() +{ + if (!m_dbPendingLock || !config()->get("security/relockautotype").toBool()) { + return; + } + + if (m_dbPendingLock->currentMode() == DatabaseWidget::LockedMode || !m_dbPendingLock->dbHasKey()) { + m_dbPendingLock = nullptr; + return; + } + + m_dbPendingLock->lock(); + + emit databaseLocked(m_dbPendingLock); + m_dbPendingLock = nullptr; +} + void DatabaseTabWidget::modified() { Q_ASSERT(qobject_cast(sender())); @@ -827,6 +850,7 @@ void DatabaseTabWidget::performGlobalAutoType() if (unlockedDatabases.size() > 0) { autoType()->performGlobalAutoType(unlockedDatabases); } else if (m_dbList.size() > 0){ - indexDatabaseManagerStruct(0).dbWidget->showUnlockDialog(); + m_dbPendingLock = indexDatabaseManagerStruct(0).dbWidget; + m_dbPendingLock->showUnlockDialog(); } } diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index b839fb3ab..38f9b8474 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -79,6 +79,7 @@ public slots: bool isModified(int index = -1); void performGlobalAutoType(); void lockDatabases(); + void relockPendingDatabase(); QString databasePath(int index = -1); signals: @@ -117,6 +118,7 @@ private: QHash m_dbList; QPointer m_dbWidgetStateSync; + QPointer m_dbPendingLock; }; #endif // KEEPASSX_DATABASETABWIDGET_H diff --git a/src/gui/SettingsWidget.cpp b/src/gui/SettingsWidget.cpp index 919edf9fd..9e4152e25 100644 --- a/src/gui/SettingsWidget.cpp +++ b/src/gui/SettingsWidget.cpp @@ -161,6 +161,7 @@ void SettingsWidget::loadSettings() m_secUi->lockDatabaseIdleSpinBox->setValue(config()->get("security/lockdatabaseidlesec").toInt()); m_secUi->lockDatabaseMinimizeCheckBox->setChecked(config()->get("security/lockdatabaseminimize").toBool()); m_secUi->lockDatabaseOnScreenLockCheckBox->setChecked(config()->get("security/lockdatabasescreenlock").toBool()); + m_secUi->relockDatabaseAutoTypeCheckBox->setChecked(config()->get("security/relockautotype").toBool()); m_secUi->fallbackToGoogle->setChecked(config()->get("security/IconDownloadFallbackToGoogle").toBool()); m_secUi->passwordCleartextCheckBox->setChecked(config()->get("security/passwordscleartext").toBool()); @@ -233,6 +234,7 @@ void SettingsWidget::saveSettings() config()->set("security/lockdatabaseidlesec", m_secUi->lockDatabaseIdleSpinBox->value()); config()->set("security/lockdatabaseminimize", m_secUi->lockDatabaseMinimizeCheckBox->isChecked()); config()->set("security/lockdatabasescreenlock", m_secUi->lockDatabaseOnScreenLockCheckBox->isChecked()); + config()->set("security/relockautotype", m_secUi->relockDatabaseAutoTypeCheckBox->isChecked()); config()->set("security/IconDownloadFallbackToGoogle", m_secUi->fallbackToGoogle->isChecked()); config()->set("security/passwordscleartext", m_secUi->passwordCleartextCheckBox->isChecked()); diff --git a/src/gui/SettingsWidgetSecurity.ui b/src/gui/SettingsWidgetSecurity.ui index b1c41338d..da3def868 100644 --- a/src/gui/SettingsWidgetSecurity.ui +++ b/src/gui/SettingsWidgetSecurity.ui @@ -122,6 +122,13 @@ + + + + Re-lock previously locked database after performing Auto-Type + + + From 490e92167db2b23e79d802e7b59cfe18ae3f8759 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Wed, 7 Feb 2018 07:10:56 -0500 Subject: [PATCH 11/22] Replace qhttp client with curl for favicon downloading (#1460) Replace qhttp client with curl for favicon downloading --- .travis.yml | 47 ---------- Dockerfile | 5 +- ci/trusty/Dockerfile | 1 + snapcraft.yaml | 1 + src/CMakeLists.txt | 20 ++--- src/gui/EditWidgetIcons.cpp | 173 +++++++++++++----------------------- src/gui/EditWidgetIcons.h | 18 +--- src/gui/MainWindow.cpp | 10 +-- src/http/CMakeLists.txt | 6 +- 9 files changed, 87 insertions(+), 194 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7183e19f5..000000000 --- a/.travis.yml +++ /dev/null @@ -1,47 +0,0 @@ -language: cpp -sudo: required -dist: trusty -# FIXME : remove when (https://github.com/google/sanitizers/issues/837) is resolved. -group: deprecated-2017Q3 -services: [docker] - -os: - - linux -# - osx - -# Define clang compiler without any frills -compiler: - - clang - - gcc - -env: - - CONFIG=Release ASAN_OPTIONS=detect_odr_violation=1 - - CONFIG=Debug ASAN_OPTIONS=detect_odr_violation=1 - -git: - depth: 3 - -before_install: - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq update; fi - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install cmake3 libclang-common-3.5-dev libxi-dev qtbase5-dev libqt5x11extras5-dev qttools5-dev qttools5-dev-tools libgcrypt20-dev zlib1g-dev libxtst-dev xvfb libyubikey-dev libykpers-1-dev; fi - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; fi - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq cmake || brew install cmake; fi - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq qt5 || brew install qt5; fi - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq libgcrypt || brew install libgcrypt; fi - -before_script: - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then CMAKE_ARGS="-DCMAKE_PREFIX_PATH=/usr/local/opt/qt5"; fi - - mkdir build && pushd build - -script: - - cmake -DCMAKE_BUILD_TYPE=${CONFIG} -DWITH_GUI_TESTS=ON -DWITH_ASAN=ON -DWITH_XC_HTTP=ON -DWITH_XC_AUTOTYPE=ON -DWITH_XC_YUBIKEY=ON -DWITH_XC_SSHAGENT=ON $CMAKE_ARGS .. - - make -j2 - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui --output-on-failure"; fi - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then ASAN_OPTIONS=${ASAN_OPTIONS}:leak_check_at_exit=0 xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui --output-on-failure"; fi - - if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test ARGS+="--output-on-failure"; fi - -# Generate snapcraft build when merging into master/develop branches -#after_success: -# - popd -# - "[[ $DEPLOY = 1 ]] && [[ $CONFIG = Release ]] && [[ $TRAVIS_BRANCH =~ (master|develop) ]] && [[ $TRAVIS_PULL_REQUEST = false ]] \ -# && docker run -v $(pwd):/cwd snapcore/snapcraft sh -c 'cd /cwd && apt update && snapcraft'" diff --git a/Dockerfile b/Dockerfile index 91eb6f395..0a5c464bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ FROM ubuntu:14.04 -ENV REBUILD_COUNTER=4 +ENV REBUILD_COUNTER=5 ENV QT5_VERSION=59 ENV QT5_PPA_VERSION=${QT5_VERSION}2 @@ -51,7 +51,8 @@ RUN set -x \ libxtst-dev \ mesa-common-dev \ libyubikey-dev \ - libykpers-1-dev + libykpers-1-dev \ + libcurl4-openssl-dev ENV CMAKE_PREFIX_PATH="/opt/qt${QT5_VERSION}/lib/cmake" ENV CMAKE_INCLUDE_PATH="/opt/libgcrypt20-18/include:/opt/gpg-error-127/include" diff --git a/ci/trusty/Dockerfile b/ci/trusty/Dockerfile index cdaba3a07..5ef9cac23 100644 --- a/ci/trusty/Dockerfile +++ b/ci/trusty/Dockerfile @@ -43,6 +43,7 @@ RUN set -x \ libgcrypt20-18-dev \ libargon2-0-dev \ libsodium-dev \ + libcurl4-openssl-dev \ qt${QT5_VERSION}base \ qt${QT5_VERSION}tools \ qt${QT5_VERSION}x11extras \ diff --git a/snapcraft.yaml b/snapcraft.yaml index d614c354f..81d4ae3b3 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -42,6 +42,7 @@ parts: - libxtst-dev - libyubikey-dev - libykpers-1-dev + - libcurl4-openssl-dev - libsodium-dev stage-packages: - dbus diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4b7c07cd6..f01780baa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -199,13 +199,9 @@ add_feature_info(KeePassHTTP WITH_XC_HTTP "Browser integration compatible with C add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent") add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response") -if(WITH_XC_HTTP) - add_subdirectory(http) - set(keepasshttp_LIB keepasshttp) -endif() +add_subdirectory(http) if(WITH_XC_NETWORKING) - add_subdirectory(http/qhttp) - set(keepassxcnetwork_LIB qhttp Qt5::Network) + find_package(CURL REQUIRED) endif() set(BROWSER_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/browser) @@ -251,23 +247,21 @@ endif() add_library(autotype STATIC ${autotype_SOURCES}) target_link_libraries(autotype Qt5::Core Qt5::Widgets) -set(autotype_LIB autotype) - add_library(keepassx_core STATIC ${keepassx_SOURCES}) set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE) target_link_libraries(keepassx_core + autotype + ${keepassxchttp_LIB} ${keepassxcbrowser_LIB} - ${keepasshttp_LIB} - ${keepassxcnetwork_LIB} - ${autotype_LIB} ${sshagent_LIB} - ${YUBIKEY_LIBRARIES} - ${ZXCVBN_LIBRARIES} Qt5::Core Qt5::Network Qt5::Concurrent Qt5::Widgets + ${CURL_LIBRARIES} + ${YUBIKEY_LIBRARIES} + ${ZXCVBN_LIBRARIES} ${ARGON2_LIBRARIES} ${GCRYPT_LIBRARIES} ${GPGERROR_LIBRARIES} diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 6f5e7ee13..9a73c293c 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -31,10 +31,9 @@ #include "gui/MessageBox.h" #ifdef WITH_XC_NETWORKING -#include "http/qhttp/qhttpclient.hpp" -#include "http/qhttp/qhttpclientresponse.hpp" - -using namespace qhttp::client; +#include +#include "core/AsyncTask.h" +#undef MessageBox #endif IconStruct::IconStruct() @@ -49,10 +48,6 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) , m_database(nullptr) , m_defaultIconModel(new DefaultIconModel(this)) , m_customIconModel(new CustomIconModel(this)) -#ifdef WITH_XC_NETWORKING - , m_fallbackToGoogle(true) - , m_redirectCount(0) -#endif { m_ui->setupUi(this); @@ -88,17 +83,14 @@ IconStruct EditWidgetIcons::state() QModelIndex index = m_ui->defaultIconsView->currentIndex(); if (index.isValid()) { iconStruct.number = index.row(); - } - else { + } else { Q_ASSERT(false); } - } - else { + } else { QModelIndex index = m_ui->customIconsView->currentIndex(); if (index.isValid()) { iconStruct.uuid = m_customIconModel->uuidFromIndex(m_ui->customIconsView->currentIndex()); - } - else { + } else { iconStruct.number = -1; } } @@ -129,14 +121,12 @@ void EditWidgetIcons::load(const Uuid& currentUuid, Database* database, const Ic int iconNumber = iconStruct.number; m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(iconNumber, 0)); m_ui->defaultIconsRadio->setChecked(true); - } - else { + } else { QModelIndex index = m_customIconModel->indexFromUuid(iconUuid); if (index.isValid()) { m_ui->customIconsView->setCurrentIndex(index); m_ui->customIconsRadio->setChecked(true); - } - else { + } else { m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(0, 0)); m_ui->defaultIconsRadio->setChecked(true); } @@ -148,7 +138,6 @@ void EditWidgetIcons::setUrl(const QString& url) #ifdef WITH_XC_NETWORKING m_url = url; m_ui->faviconButton->setVisible(!url.isEmpty()); - resetFaviconDownload(); #else Q_UNUSED(url); m_ui->faviconButton->setVisible(false); @@ -158,107 +147,75 @@ void EditWidgetIcons::setUrl(const QString& url) void EditWidgetIcons::downloadFavicon() { #ifdef WITH_XC_NETWORKING + m_ui->faviconButton->setDisabled(true); + QUrl url = QUrl(m_url); url.setPath("/favicon.ico"); - fetchFavicon(url); + // Attempt to simply load the favicon.ico file + QImage image = fetchFavicon(url); + if (!image.isNull()) { + addCustomIcon(image); + } else if (config()->get("security/IconDownloadFallbackToGoogle", false).toBool()) { + QUrl faviconUrl = QUrl("https://www.google.com/s2/favicons"); + faviconUrl.setQuery("domain=" + QUrl::toPercentEncoding(url.host())); + // Attempt to load favicon from Google + image = fetchFavicon(faviconUrl); + if (!image.isNull()) { + addCustomIcon(image); + } else { + emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error); + } + } else { + emit messageEditEntry(tr("Unable to fetch favicon.") + "\n" + + tr("Hint: You can enable Google as a fallback under Tools>Settings>Security"), + MessageWidget::Error); + } + + m_ui->faviconButton->setDisabled(false); #endif } #ifdef WITH_XC_NETWORKING -void EditWidgetIcons::fetchFavicon(const QUrl& url) +namespace { +std::size_t writeCurlResponse(char* ptr, std::size_t size, std::size_t nmemb, void* data) { - if (nullptr == m_httpClient) { - m_httpClient = new QHttpClient(this); - } + QByteArray* response = static_cast(data); + std::size_t realsize = size * nmemb; + response->append(ptr, realsize); + return realsize; +} +} - bool requestMade = m_httpClient->request(qhttp::EHTTP_GET, url, [this, url](QHttpResponse* response) { - if (m_database == nullptr) { - return; - } +QImage EditWidgetIcons::fetchFavicon(const QUrl& url) +{ + QImage image; + CURL* curl = curl_easy_init(); + if (curl) { + QByteArray imagedata; + QByteArray baUrl = url.url().toLatin1(); - response->collectData(); - response->onEnd([this, response, &url]() { - int status = response->status(); - if (200 == status) { - QImage image; - image.loadFromData(response->collectedData()); + curl_easy_setopt(curl, CURLOPT_URL, baUrl.data()); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L); + curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &imagedata); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeCurlResponse); - if (!image.isNull()) { - addCustomIcon(image); - resetFaviconDownload(); - } else { - fetchFaviconFromGoogle(url.host()); - } - } else if (301 == status || 302 == status) { - // Check if server has sent a redirect - QUrl possibleRedirectUrl(response->headers().value("location", "")); - if (!possibleRedirectUrl.isEmpty() && possibleRedirectUrl != m_redirectUrl && m_redirectCount < 3) { - resetFaviconDownload(false); - m_redirectUrl = possibleRedirectUrl; - ++m_redirectCount; - fetchFavicon(m_redirectUrl); - } else { - // website is trying to redirect to itself or - // maximum number of redirects has been reached, fall back to Google - fetchFaviconFromGoogle(url.host()); - } - } else { - fetchFaviconFromGoogle(url.host()); - } + // Perform the request in another thread + CURLcode result = AsyncTask::runAndWaitForFuture([curl]() { + return curl_easy_perform(curl); }); - }); - if (!requestMade) { - resetFaviconDownload(); - return; - } - - m_httpClient->setConnectingTimeOut(5000, [this]() { - QUrl tempurl = QUrl(m_url); - if (tempurl.scheme() == "http") { - resetFaviconDownload(); - emit messageEditEntry(tr("Unable to fetch favicon.") + "\n" + - tr("Hint: You can enable Google as a fallback under Tools>Settings>Security"), - MessageWidget::Error); - } else { - tempurl.setScheme("http"); - m_url = tempurl.url(); - tempurl.setPath("/favicon.ico"); - fetchFavicon(tempurl); + if (result == CURLE_OK) { + image.loadFromData(imagedata); } - }); - m_ui->faviconButton->setDisabled(true); -} - -void EditWidgetIcons::fetchFaviconFromGoogle(const QString& domain) -{ - if (config()->get("security/IconDownloadFallbackToGoogle", false).toBool() && m_fallbackToGoogle) { - resetFaviconDownload(); - m_fallbackToGoogle = false; - QUrl faviconUrl = QUrl("https://www.google.com/s2/favicons"); - faviconUrl.setQuery("domain=" + QUrl::toPercentEncoding(domain)); - fetchFavicon(faviconUrl); - } else { - resetFaviconDownload(); - emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error); - } -} - -void EditWidgetIcons::resetFaviconDownload(bool clearRedirect) -{ - if (clearRedirect) { - m_redirectUrl.clear(); - m_redirectCount = 0; + curl_easy_cleanup(curl); } - if (nullptr != m_httpClient) { - m_httpClient->deleteLater(); - m_httpClient = nullptr; - } - - m_fallbackToGoogle = true; - m_ui->faviconButton->setDisabled(false); + return image; } #endif @@ -281,7 +238,7 @@ void EditWidgetIcons::addCustomIconFromFile() } } -void EditWidgetIcons::addCustomIcon(const QImage &icon) +void EditWidgetIcons::addCustomIcon(const QImage& icon) { if (m_database) { Uuid uuid = m_database->metadata()->findCustomIcon(icon); @@ -392,8 +349,7 @@ void EditWidgetIcons::updateWidgetsDefaultIcons(bool check) QModelIndex index = m_ui->defaultIconsView->currentIndex(); if (!index.isValid()) { m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(0, 0)); - } - else { + } else { m_ui->defaultIconsView->setCurrentIndex(index); } m_ui->customIconsView->selectionModel()->clearSelection(); @@ -408,8 +364,7 @@ void EditWidgetIcons::updateWidgetsCustomIcons(bool check) QModelIndex index = m_ui->customIconsView->currentIndex(); if (!index.isValid()) { m_ui->customIconsView->setCurrentIndex(m_customIconModel->index(0, 0)); - } - else { + } else { m_ui->customIconsView->setCurrentIndex(index); } m_ui->defaultIconsView->selectionModel()->clearSelection(); diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h index 796dd5939..7b5edf80c 100644 --- a/src/gui/EditWidgetIcons.h +++ b/src/gui/EditWidgetIcons.h @@ -32,14 +32,6 @@ class Database; class DefaultIconModel; class CustomIconModel; -#ifdef WITH_XC_NETWORKING -namespace qhttp { - namespace client { - class QHttpClient; - } -} -#endif - namespace Ui { class EditWidgetIcons; } @@ -74,9 +66,7 @@ signals: private slots: void downloadFavicon(); #ifdef WITH_XC_NETWORKING - void fetchFavicon(const QUrl& url); - void fetchFaviconFromGoogle(const QString& domain); - void resetFaviconDownload(bool clearRedirect = true); + QImage fetchFavicon(const QUrl& url); #endif void addCustomIconFromFile(); void addCustomIcon(const QImage& icon); @@ -93,12 +83,6 @@ private: QString m_url; DefaultIconModel* const m_defaultIconModel; CustomIconModel* const m_customIconModel; -#ifdef WITH_XC_NETWORKING - QUrl m_redirectUrl; - bool m_fallbackToGoogle; - unsigned short m_redirectCount; - qhttp::client::QHttpClient* m_httpClient = nullptr; -#endif Q_DISABLE_COPY(EditWidgetIcons) }; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index def6e6575..6fad65859 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -69,7 +69,7 @@ class HttpPlugin: public ISettingsPage { public: - HttpPlugin(DatabaseTabWidget * tabWidget) + HttpPlugin(DatabaseTabWidget* tabWidget) { m_service = new Service(tabWidget); } @@ -88,18 +88,18 @@ public: QWidget * createWidget() override { - OptionDialog * dlg = new OptionDialog(); + OptionDialog* dlg = new OptionDialog(); QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_service, SLOT(removeSharedEncryptionKeys())); QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_service, SLOT(removeStoredPermissions())); return dlg; } - void loadSettings(QWidget * widget) override + void loadSettings(QWidget* widget) override { qobject_cast(widget)->loadSettings(); } - void saveSettings(QWidget * widget) override + void saveSettings(QWidget* widget) override { qobject_cast(widget)->saveSettings(); if (HttpSettings::isEnabled()) @@ -108,7 +108,7 @@ public: m_service->stop(); } private: - Service *m_service; + Service* m_service; }; #endif diff --git a/src/http/CMakeLists.txt b/src/http/CMakeLists.txt index db9f5dfb3..962f78c36 100644 --- a/src/http/CMakeLists.txt +++ b/src/http/CMakeLists.txt @@ -1,4 +1,6 @@ if(WITH_XC_HTTP) + add_subdirectory(qhttp) + include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) set(keepasshttp_SOURCES @@ -13,5 +15,7 @@ if(WITH_XC_HTTP) ) add_library(keepasshttp STATIC ${keepasshttp_SOURCES}) - target_link_libraries(keepasshttp qhttp Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network) + target_link_libraries(keepasshttp PUBLIC qhttp Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network) + + set(keepassxchttp_LIB keepasshttp PARENT_SCOPE) endif() From 27f8aa095a26f3d6021525b3c27b3b25e5143320 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Fri, 19 Jan 2018 15:48:40 +0100 Subject: [PATCH 12/22] add diceware and passgen to the cli interface --- src/cli/CMakeLists.txt | 4 ++ src/cli/Command.cpp | 4 ++ src/cli/Diceware.cpp | 79 +++++++++++++++++++++++++++++ src/cli/Diceware.h | 31 ++++++++++++ src/cli/PassGen.cpp | 108 ++++++++++++++++++++++++++++++++++++++++ src/cli/PassGen.h | 31 ++++++++++++ src/cli/keepassxc-cli.1 | 35 ++++++++++++- 7 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 src/cli/Diceware.cpp create mode 100644 src/cli/Diceware.h create mode 100644 src/cli/PassGen.cpp create mode 100644 src/cli/PassGen.h diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 225ce47dc..92277811b 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -20,6 +20,8 @@ set(cli_SOURCES Clip.h Command.cpp Command.h + Diceware.cpp + Diceware.h Edit.cpp Edit.h Estimate.cpp @@ -32,6 +34,8 @@ set(cli_SOURCES Locate.h Merge.cpp Merge.h + PassGen.cpp + PassGen.h Remove.cpp Remove.h Show.cpp diff --git a/src/cli/Command.cpp b/src/cli/Command.cpp index 6ec07b7af..a9a53449e 100644 --- a/src/cli/Command.cpp +++ b/src/cli/Command.cpp @@ -24,12 +24,14 @@ #include "Add.h" #include "Clip.h" +#include "Diceware.h" #include "Edit.h" #include "Estimate.h" #include "Extract.h" #include "List.h" #include "Locate.h" #include "Merge.h" +#include "PassGen.h" #include "Remove.h" #include "Show.h" @@ -61,12 +63,14 @@ void populateCommands() if (commands.isEmpty()) { commands.insert(QString("add"), new Add()); commands.insert(QString("clip"), new Clip()); + commands.insert(QString("diceware"), new Diceware()); commands.insert(QString("edit"), new Edit()); commands.insert(QString("estimate"), new Estimate()); commands.insert(QString("extract"), new Extract()); commands.insert(QString("locate"), new Locate()); commands.insert(QString("ls"), new List()); commands.insert(QString("merge"), new Merge()); + commands.insert(QString("passgen"), new PassGen()); commands.insert(QString("rm"), new Remove()); commands.insert(QString("show"), new Show()); } diff --git a/src/cli/Diceware.cpp b/src/cli/Diceware.cpp new file mode 100644 index 000000000..ddc8f43a5 --- /dev/null +++ b/src/cli/Diceware.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 +#include + +#include "Diceware.h" + +#include +#include + +#include "core/PassphraseGenerator.h" + +Diceware::Diceware() +{ + this->name = QString("diceware"); + this->description = QObject::tr("Generate a new random password."); +} + +Diceware::~Diceware() +{ +} + +int Diceware::execute(QStringList arguments) +{ + QTextStream inputTextStream(stdin, QIODevice::ReadOnly); + QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + + QCommandLineParser parser; + parser.setApplicationDescription(this->description); + QCommandLineOption wordlistFile(QStringList() << "w" + << "wordlist", + QObject::tr("Wordlist fot the diceware generator.\n[Default: EFF English]"), + QObject::tr("path")); + parser.addOption(wordlistFile); + parser.addPositionalArgument("words", QObject::tr("Word count for the diceware generator.")); + parser.process(arguments); + + const QStringList args = parser.positionalArguments(); + if (args.size() != 1) { + outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); + return EXIT_FAILURE; + } + + PassphraseGenerator dicewareGenerator; + + int words = args.at(0).toInt(); + dicewareGenerator.setWordCount(words); + + if (!parser.value(wordlistFile).isEmpty()) { + dicewareGenerator.setWordList(parser.value(wordlistFile)); + } else { + dicewareGenerator.setDefaultWordList(); + } + + if (!dicewareGenerator.isValid()) { + outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli passgen"); + return EXIT_FAILURE; + } + + QString password = dicewareGenerator.generatePassphrase(); + outputTextStream << password << endl; + + return EXIT_SUCCESS; +} diff --git a/src/cli/Diceware.h b/src/cli/Diceware.h new file mode 100644 index 000000000..b6d71b6c6 --- /dev/null +++ b/src/cli/Diceware.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 KEEPASSXC_DICEWARE_H +#define KEEPASSXC_DICEWARE_H + +#include "Command.h" + +class Diceware : public Command +{ +public: + Diceware(); + ~Diceware(); + int execute(QStringList arguments); +}; + +#endif // KEEPASSXC_DICEWARE_H diff --git a/src/cli/PassGen.cpp b/src/cli/PassGen.cpp new file mode 100644 index 000000000..036f08db9 --- /dev/null +++ b/src/cli/PassGen.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * 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 +#include + +#include "PassGen.h" + +#include +#include + +#include "core/PasswordGenerator.h" + +PassGen::PassGen() +{ + this->name = QString("passgen"); + this->description = QObject::tr("Generate a new random password."); +} + +PassGen::~PassGen() +{ +} + +int PassGen::execute(QStringList arguments) +{ + QTextStream inputTextStream(stdin, QIODevice::ReadOnly); + QTextStream outputTextStream(stdout, QIODevice::WriteOnly); + + QCommandLineParser parser; + parser.setApplicationDescription(this->description); + QCommandLineOption lower(QStringList() << "l", + QObject::tr("Use lowercase in the generated password.")); + parser.addOption(lower); + QCommandLineOption upper(QStringList() << "u", + QObject::tr("Use uppercase in the generated password.")); + parser.addOption(upper); + QCommandLineOption numeric(QStringList() << "n", + QObject::tr("Use numbers in the generated password.")); + parser.addOption(numeric); + QCommandLineOption special(QStringList() << "s", + QObject::tr("Use special characters in the generated password.")); + parser.addOption(special); + QCommandLineOption extended(QStringList() << "e", + QObject::tr("Use extended ascii in the generated password.")); + parser.addOption(extended); + parser.addPositionalArgument("length", QObject::tr("Length of the generated password.")); + parser.process(arguments); + + const QStringList args = parser.positionalArguments(); + if (args.size() != 1) { + outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli passgen"); + return EXIT_FAILURE; + } + + PasswordGenerator passwordGenerator; + + int length = args.at(0).toInt(); + passwordGenerator.setLength(length); + + PasswordGenerator::CharClasses classes = 0x0; + + if (parser.isSet(lower)) { + classes |= PasswordGenerator::LowerLetters; + } + if (parser.isSet(upper)) { + classes |= PasswordGenerator::UpperLetters; + } + if (parser.isSet(numeric)) { + classes |= PasswordGenerator::Numbers; + } + if (parser.isSet(special)) { + classes |= PasswordGenerator::SpecialCharacters; + } + if (parser.isSet(extended)) { + classes |= PasswordGenerator::EASCII; + } + + if (classes == 0x0) { + passwordGenerator.setCharClasses(PasswordGenerator::LowerLetters | PasswordGenerator::UpperLetters | + PasswordGenerator::Numbers); + } else { + passwordGenerator.setCharClasses(classes); + } + + if (!passwordGenerator.isValid()) { + outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli passgen"); + return EXIT_FAILURE; + } + + QString password = passwordGenerator.generatePassword(); + outputTextStream << password << endl; + + return EXIT_SUCCESS; +} diff --git a/src/cli/PassGen.h b/src/cli/PassGen.h new file mode 100644 index 000000000..5c0b33aeb --- /dev/null +++ b/src/cli/PassGen.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * 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 KEEPASSXC_PASSGEN_H +#define KEEPASSXC_PASSGEN_H + +#include "Command.h" + +class PassGen : public Command +{ +public: + PassGen(); + ~PassGen(); + int execute(QStringList arguments); +}; + +#endif // KEEPASSXC_PASSGEN_H diff --git a/src/cli/keepassxc-cli.1 b/src/cli/keepassxc-cli.1 index a9145b27e..6a772e7cd 100644 --- a/src/cli/keepassxc-cli.1 +++ b/src/cli/keepassxc-cli.1 @@ -1,4 +1,4 @@ -.TH KEEPASSXC-CLI 1 "Aug 22, 2017" +.TH KEEPASSXC-CLI 1 "Jan 19, 2018" .SH NAME keepassxc-cli \- command line interface for the \fBKeePassXC\fP password manager. @@ -19,6 +19,9 @@ Adds a new entry to a database. A password can be generated (\fI-g\fP option), o .IP "clip [options] [timeout]" Copies the password of a database entry to the clipboard. If multiple entries with the same name exist in different groups, only the password for the first one is going to be copied. For copying the password of an entry in a specific group, the group path to the entry should be specified as well, instead of just the name. Optionally, a timeout in seconds can be specified to automatically clear the clipboard. +.IP "diceware [options] " +Generate a random diceware passphrase. + .IP "edit [options] " Edits a database entry. A password can be generated (\fI-g\fP option), or a prompt can be displayed to input the password (\fI-p\fP option). @@ -37,6 +40,9 @@ Lists the contents of a group in a database. If no group is specified, it will d .IP "merge [options] " Merges two databases together. The first database file is going to be replaced by the result of the merge, for that reason it is advisable to keep a backup of the two database files before attempting a merge. In the case that both databases make use of the same credentials, the \fI--same-credentials\fP or \fI-s\fP option can be used. +.IP "passgen [options] " +Generate a random password + .IP "rm [options] " Removes an entry from a database. If the database has a recycle bin, the entry will be moved there. If the entry is already in the recycle bin, it will be removed permanently. @@ -104,6 +110,33 @@ with each attribute shown one-per-line in the given order. If no attributes are specified, a summary of the default attributes is given. +.SS "Diceware options" + +.IP "-w, --wordlist " +Path of the wordlist for the diceware generator. The wordlist must have > 1000 words, +otherwise the program will fail. If the wordlist has < 4000 words a warning will +be printed to STDERR. + + +.SS "PassGen options" + +.IP "-l" +Use lowercase characters for the password generator + +.IP "-u" +Use uppercase characters for the password generator + +.IP "-n" +Use numbers characters for the password generator + +.IP "-s" +Use special characters for the password generator + +.IP "-e" +Use extended ascii characters for the password generator + + + .SH REPORTING BUGS Bugs and feature requests can be reported on GitHub at https://github.com/keepassxreboot/keepassxc/issues. From e9612ee9e6fcb14e43102c2636f5f8b4d2e1d968 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Sun, 21 Jan 2018 02:32:46 +0100 Subject: [PATCH 13/22] use default password lenght + minor fixes --- src/cli/Diceware.cpp | 4 ++-- src/cli/PassGen.cpp | 15 +++++++++++---- src/cli/keepassxc-cli.1 | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/cli/Diceware.cpp b/src/cli/Diceware.cpp index ddc8f43a5..361be1625 100644 --- a/src/cli/Diceware.cpp +++ b/src/cli/Diceware.cpp @@ -43,7 +43,7 @@ int Diceware::execute(QStringList arguments) QCommandLineParser parser; parser.setApplicationDescription(this->description); QCommandLineOption wordlistFile(QStringList() << "w" - << "wordlist", + << "word-list", QObject::tr("Wordlist fot the diceware generator.\n[Default: EFF English]"), QObject::tr("path")); parser.addOption(wordlistFile); @@ -68,7 +68,7 @@ int Diceware::execute(QStringList arguments) } if (!dicewareGenerator.isValid()) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli passgen"); + outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); return EXIT_FAILURE; } diff --git a/src/cli/PassGen.cpp b/src/cli/PassGen.cpp index 036f08db9..428554dc6 100644 --- a/src/cli/PassGen.cpp +++ b/src/cli/PassGen.cpp @@ -42,6 +42,10 @@ int PassGen::execute(QStringList arguments) QCommandLineParser parser; parser.setApplicationDescription(this->description); + QCommandLineOption len(QStringList() << "L" << "length", + QObject::tr("Length of the generated password."), + QObject::tr("length")); + parser.addOption(len); QCommandLineOption lower(QStringList() << "l", QObject::tr("Use lowercase in the generated password.")); parser.addOption(lower); @@ -57,19 +61,22 @@ int PassGen::execute(QStringList arguments) QCommandLineOption extended(QStringList() << "e", QObject::tr("Use extended ascii in the generated password.")); parser.addOption(extended); - parser.addPositionalArgument("length", QObject::tr("Length of the generated password.")); parser.process(arguments); const QStringList args = parser.positionalArguments(); - if (args.size() != 1) { + if (args.size() != 0) { outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli passgen"); return EXIT_FAILURE; } PasswordGenerator passwordGenerator; - int length = args.at(0).toInt(); - passwordGenerator.setLength(length); + if (parser.value(len).isEmpty()) { + passwordGenerator.setLength(PasswordGenerator::DefaultLength); + } else { + int length = parser.value(len).toInt(); + passwordGenerator.setLength(length); + } PasswordGenerator::CharClasses classes = 0x0; diff --git a/src/cli/keepassxc-cli.1 b/src/cli/keepassxc-cli.1 index 6a772e7cd..ec8d35c4c 100644 --- a/src/cli/keepassxc-cli.1 +++ b/src/cli/keepassxc-cli.1 @@ -112,7 +112,7 @@ specified, a summary of the default attributes is given. .SS "Diceware options" -.IP "-w, --wordlist " +.IP "-w, --word-list " Path of the wordlist for the diceware generator. The wordlist must have > 1000 words, otherwise the program will fail. If the wordlist has < 4000 words a warning will be printed to STDERR. From e57a2e0fa943c161841141467715cb9b8bd5cbce Mon Sep 17 00:00:00 2001 From: thez3ro Date: Mon, 22 Jan 2018 13:47:20 +0100 Subject: [PATCH 14/22] add default charset when not specified explicitly state the wordcount default value --- src/cli/Add.cpp | 3 +-- src/cli/Edit.cpp | 3 +-- src/cli/PassGen.cpp | 7 +------ src/core/PassphraseGenerator.cpp | 2 +- src/core/PassphraseGenerator.h | 2 ++ src/core/PasswordGenerator.cpp | 8 ++++++++ src/core/PasswordGenerator.h | 3 ++- 7 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/cli/Add.cpp b/src/cli/Add.cpp index 13023b5bb..6954532cd 100644 --- a/src/cli/Add.cpp +++ b/src/cli/Add.cpp @@ -133,8 +133,7 @@ int Add::execute(QStringList arguments) passwordGenerator.setLength(passwordLength.toInt()); } - passwordGenerator.setCharClasses(PasswordGenerator::LowerLetters | PasswordGenerator::UpperLetters | - PasswordGenerator::Numbers); + passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset); QString password = passwordGenerator.generatePassword(); entry->setPassword(password); } diff --git a/src/cli/Edit.cpp b/src/cli/Edit.cpp index 25d2fd456..675ec7def 100644 --- a/src/cli/Edit.cpp +++ b/src/cli/Edit.cpp @@ -149,8 +149,7 @@ int Edit::execute(QStringList arguments) passwordGenerator.setLength(passwordLength.toInt()); } - passwordGenerator.setCharClasses(PasswordGenerator::LowerLetters | PasswordGenerator::UpperLetters | - PasswordGenerator::Numbers); + passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset); QString password = passwordGenerator.generatePassword(); entry->setPassword(password); } diff --git a/src/cli/PassGen.cpp b/src/cli/PassGen.cpp index 428554dc6..900909559 100644 --- a/src/cli/PassGen.cpp +++ b/src/cli/PassGen.cpp @@ -96,12 +96,7 @@ int PassGen::execute(QStringList arguments) classes |= PasswordGenerator::EASCII; } - if (classes == 0x0) { - passwordGenerator.setCharClasses(PasswordGenerator::LowerLetters | PasswordGenerator::UpperLetters | - PasswordGenerator::Numbers); - } else { - passwordGenerator.setCharClasses(classes); - } + passwordGenerator.setCharClasses(classes); if (!passwordGenerator.isValid()) { outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli passgen"); diff --git a/src/core/PassphraseGenerator.cpp b/src/core/PassphraseGenerator.cpp index 115c70d6d..941ba5f38 100644 --- a/src/core/PassphraseGenerator.cpp +++ b/src/core/PassphraseGenerator.cpp @@ -48,7 +48,7 @@ void PassphraseGenerator::setWordCount(int wordCount) m_wordCount = wordCount; } else { // safe default if something goes wrong - m_wordCount = 7; + m_wordCount = DefaultWordCount; } } diff --git a/src/core/PassphraseGenerator.h b/src/core/PassphraseGenerator.h index 20845ff21..7df4b4a74 100644 --- a/src/core/PassphraseGenerator.h +++ b/src/core/PassphraseGenerator.h @@ -36,6 +36,8 @@ public: QString generatePassphrase() const; + static const int DefaultWordCount = 7; + private: int m_wordCount; QString m_separator; diff --git a/src/core/PasswordGenerator.cpp b/src/core/PasswordGenerator.cpp index 21aa590e0..740fb5467 100644 --- a/src/core/PasswordGenerator.cpp +++ b/src/core/PasswordGenerator.cpp @@ -35,11 +35,19 @@ double PasswordGenerator::calculateEntropy(QString password) void PasswordGenerator::setLength(int length) { + if (length <= 0) { + m_length = DefaultLength; + return; + } m_length = length; } void PasswordGenerator::setCharClasses(const CharClasses& classes) { + if (classes == 0) { + m_classes = DefaultCharset; + return; + } m_classes = classes; } diff --git a/src/core/PasswordGenerator.h b/src/core/PasswordGenerator.h index 98bb58b6a..0c13bac05 100644 --- a/src/core/PasswordGenerator.h +++ b/src/core/PasswordGenerator.h @@ -34,7 +34,8 @@ public: UpperLetters = 0x2, Numbers = 0x4, SpecialCharacters = 0x8, - EASCII = 0x10 + EASCII = 0x10, + DefaultCharset = LowerLetters | UpperLetters | Numbers }; Q_DECLARE_FLAGS(CharClasses, CharClass) From 4782b20d6144769e35d5bdec9e23b9677e844344 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 31 Jan 2018 00:50:02 +0100 Subject: [PATCH 15/22] renamed passgen to generate and use diceware default wordcount --- src/cli/CMakeLists.txt | 4 ++-- src/cli/Command.cpp | 4 ++-- src/cli/Diceware.cpp | 15 +++++++++++---- src/cli/{PassGen.cpp => Generate.cpp} | 14 +++++++------- src/cli/{PassGen.h => Generate.h} | 12 ++++++------ 5 files changed, 28 insertions(+), 21 deletions(-) rename src/cli/{PassGen.cpp => Generate.cpp} (94%) rename src/cli/{PassGen.h => Generate.h} (83%) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 92277811b..a5126f999 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -28,14 +28,14 @@ set(cli_SOURCES Estimate.h Extract.cpp Extract.h + Generate.cpp + Generate.h List.cpp List.h Locate.cpp Locate.h Merge.cpp Merge.h - PassGen.cpp - PassGen.h Remove.cpp Remove.h Show.cpp diff --git a/src/cli/Command.cpp b/src/cli/Command.cpp index a9a53449e..f0441fd7f 100644 --- a/src/cli/Command.cpp +++ b/src/cli/Command.cpp @@ -28,10 +28,10 @@ #include "Edit.h" #include "Estimate.h" #include "Extract.h" +#include "Generate.h" #include "List.h" #include "Locate.h" #include "Merge.h" -#include "PassGen.h" #include "Remove.h" #include "Show.h" @@ -67,10 +67,10 @@ void populateCommands() commands.insert(QString("edit"), new Edit()); commands.insert(QString("estimate"), new Estimate()); commands.insert(QString("extract"), new Extract()); + commands.insert(QString("generate"), new Generate()); commands.insert(QString("locate"), new Locate()); commands.insert(QString("ls"), new List()); commands.insert(QString("merge"), new Merge()); - commands.insert(QString("passgen"), new PassGen()); commands.insert(QString("rm"), new Remove()); commands.insert(QString("show"), new Show()); } diff --git a/src/cli/Diceware.cpp b/src/cli/Diceware.cpp index 361be1625..080a21c1f 100644 --- a/src/cli/Diceware.cpp +++ b/src/cli/Diceware.cpp @@ -42,24 +42,31 @@ int Diceware::execute(QStringList arguments) QCommandLineParser parser; parser.setApplicationDescription(this->description); + QCommandLineOption words(QStringList() << "W" << "words", + QObject::tr("Word count for the diceware passphrase."), + QObject::tr("count")); + parser.addOption(words); QCommandLineOption wordlistFile(QStringList() << "w" << "word-list", QObject::tr("Wordlist fot the diceware generator.\n[Default: EFF English]"), QObject::tr("path")); parser.addOption(wordlistFile); - parser.addPositionalArgument("words", QObject::tr("Word count for the diceware generator.")); parser.process(arguments); const QStringList args = parser.positionalArguments(); - if (args.size() != 1) { + if (args.size() != 0) { outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); return EXIT_FAILURE; } PassphraseGenerator dicewareGenerator; - int words = args.at(0).toInt(); - dicewareGenerator.setWordCount(words); + if (parser.value(words).isEmpty()) { + dicewareGenerator.setWordCount(PassphraseGenerator::DefaultWordCount); + } else { + int wordcount = parser.value(words).toInt(); + dicewareGenerator.setWordCount(wordcount); + } if (!parser.value(wordlistFile).isEmpty()) { dicewareGenerator.setWordList(parser.value(wordlistFile)); diff --git a/src/cli/PassGen.cpp b/src/cli/Generate.cpp similarity index 94% rename from src/cli/PassGen.cpp rename to src/cli/Generate.cpp index 900909559..3588cb421 100644 --- a/src/cli/PassGen.cpp +++ b/src/cli/Generate.cpp @@ -18,24 +18,24 @@ #include #include -#include "PassGen.h" +#include "Generate.h" #include #include #include "core/PasswordGenerator.h" -PassGen::PassGen() +Generate::Generate() { - this->name = QString("passgen"); + this->name = QString("generate"); this->description = QObject::tr("Generate a new random password."); } -PassGen::~PassGen() +Generate::~Generate() { } -int PassGen::execute(QStringList arguments) +int Generate::execute(QStringList arguments) { QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QTextStream outputTextStream(stdout, QIODevice::WriteOnly); @@ -65,7 +65,7 @@ int PassGen::execute(QStringList arguments) const QStringList args = parser.positionalArguments(); if (args.size() != 0) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli passgen"); + outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); return EXIT_FAILURE; } @@ -99,7 +99,7 @@ int PassGen::execute(QStringList arguments) passwordGenerator.setCharClasses(classes); if (!passwordGenerator.isValid()) { - outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli passgen"); + outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); return EXIT_FAILURE; } diff --git a/src/cli/PassGen.h b/src/cli/Generate.h similarity index 83% rename from src/cli/PassGen.h rename to src/cli/Generate.h index 5c0b33aeb..de6a8ea11 100644 --- a/src/cli/PassGen.h +++ b/src/cli/Generate.h @@ -15,17 +15,17 @@ * along with this program. If not, see . */ -#ifndef KEEPASSXC_PASSGEN_H -#define KEEPASSXC_PASSGEN_H +#ifndef KEEPASSXC_GENERATE_H +#define KEEPASSXC_GENERATE_H #include "Command.h" -class PassGen : public Command +class Generate : public Command { public: - PassGen(); - ~PassGen(); + Generate(); + ~Generate(); int execute(QStringList arguments); }; -#endif // KEEPASSXC_PASSGEN_H +#endif // KEEPASSXC_GENERATE_H From 33b95836b97664a1442aa015b4a440cf8177bd41 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Wed, 31 Jan 2018 11:15:23 +0100 Subject: [PATCH 16/22] update cli manpage --- src/cli/keepassxc-cli.1 | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/cli/keepassxc-cli.1 b/src/cli/keepassxc-cli.1 index ec8d35c4c..ebd3ea3da 100644 --- a/src/cli/keepassxc-cli.1 +++ b/src/cli/keepassxc-cli.1 @@ -19,7 +19,7 @@ Adds a new entry to a database. A password can be generated (\fI-g\fP option), o .IP "clip [options] [timeout]" Copies the password of a database entry to the clipboard. If multiple entries with the same name exist in different groups, only the password for the first one is going to be copied. For copying the password of an entry in a specific group, the group path to the entry should be specified as well, instead of just the name. Optionally, a timeout in seconds can be specified to automatically clear the clipboard. -.IP "diceware [options] " +.IP "diceware [options]" Generate a random diceware passphrase. .IP "edit [options] " @@ -31,6 +31,9 @@ Estimates the entropy of a password. The password to estimate can be provided as .IP "extract [options] " Extracts and prints the contents of a database to standard output in XML format. +.IP "generate [options]" +Generate a random password + .IP "locate [options] " Locates all the entries that match a specific search term in a database. @@ -40,9 +43,6 @@ Lists the contents of a group in a database. If no group is specified, it will d .IP "merge [options] " Merges two databases together. The first database file is going to be replaced by the result of the merge, for that reason it is advisable to keep a backup of the two database files before attempting a merge. In the case that both databases make use of the same credentials, the \fI--same-credentials\fP or \fI-s\fP option can be used. -.IP "passgen [options] " -Generate a random password - .IP "rm [options] " Removes an entry from a database. If the database has a recycle bin, the entry will be moved there. If the entry is already in the recycle bin, it will be removed permanently. @@ -112,28 +112,34 @@ specified, a summary of the default attributes is given. .SS "Diceware options" +.IP "-W, --words " +Desired number of words for the generated passphrase. [Default: 7] + .IP "-w, --word-list " Path of the wordlist for the diceware generator. The wordlist must have > 1000 words, otherwise the program will fail. If the wordlist has < 4000 words a warning will be printed to STDERR. -.SS "PassGen options" +.SS "Generate options" + +.IP "-L, --length " +Desired length for the generated password. [Default: 16] .IP "-l" -Use lowercase characters for the password generator +Use lowercase characters for the generated password. [Default: Enabled] .IP "-u" -Use uppercase characters for the password generator +Use uppercase characters for the generated password. [Default: Enabled] .IP "-n" -Use numbers characters for the password generator +Use numbers characters for the generated password. [Default: Enabled] .IP "-s" -Use special characters for the password generator +Use special characters for the generated password. [Default: Disabled] .IP "-e" -Use extended ascii characters for the password generator +Use extended ascii characters for the generated password. [Default: Disabled] From 6723f4215aafbae4c4fe69e975e21caaad66f636 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Mon, 5 Feb 2018 12:31:13 +0100 Subject: [PATCH 17/22] centralize every password generator default option. add extended ASCII to XC_HTTP generator --- src/browser/BrowserSettings.cpp | 20 ++++++------ src/core/FilePath.cpp | 5 +++ src/core/FilePath.h | 1 + src/core/PassphraseGenerator.cpp | 7 +++-- src/core/PassphraseGenerator.h | 2 ++ src/core/PasswordGenerator.h | 10 +++++- src/gui/PasswordGeneratorWidget.cpp | 26 ++++++++-------- src/http/HttpPasswordGeneratorWidget.cpp | 18 +++++++---- src/http/HttpPasswordGeneratorWidget.ui | 16 ++++++++++ src/http/HttpSettings.cpp | 39 +++++++++++++++++------- src/http/HttpSettings.h | 2 ++ 11 files changed, 103 insertions(+), 43 deletions(-) diff --git a/src/browser/BrowserSettings.cpp b/src/browser/BrowserSettings.cpp index 2df7fc1c5..ecaf8e23e 100755 --- a/src/browser/BrowserSettings.cpp +++ b/src/browser/BrowserSettings.cpp @@ -211,7 +211,7 @@ void BrowserSettings::setVivaldiSupport(bool enabled) { bool BrowserSettings::passwordUseNumbers() { - return config()->get("generator/Numbers", true).toBool(); + return config()->get("generator/Numbers", PasswordGenerator::DefaultNumbers).toBool(); } void BrowserSettings::setPasswordUseNumbers(bool useNumbers) @@ -221,7 +221,7 @@ void BrowserSettings::setPasswordUseNumbers(bool useNumbers) bool BrowserSettings::passwordUseLowercase() { - return config()->get("generator/LowerCase", true).toBool(); + return config()->get("generator/LowerCase", PasswordGenerator::DefaultLower).toBool(); } void BrowserSettings::setPasswordUseLowercase(bool useLowercase) @@ -231,7 +231,7 @@ void BrowserSettings::setPasswordUseLowercase(bool useLowercase) bool BrowserSettings::passwordUseUppercase() { - return config()->get("generator/UpperCase", true).toBool(); + return config()->get("generator/UpperCase", PasswordGenerator::DefaultUpper).toBool(); } void BrowserSettings::setPasswordUseUppercase(bool useUppercase) @@ -241,7 +241,7 @@ void BrowserSettings::setPasswordUseUppercase(bool useUppercase) bool BrowserSettings::passwordUseSpecial() { - return config()->get("generator/SpecialChars", false).toBool(); + return config()->get("generator/SpecialChars", PasswordGenerator::DefaultSpecial).toBool(); } void BrowserSettings::setPasswordUseSpecial(bool useSpecial) @@ -251,7 +251,7 @@ void BrowserSettings::setPasswordUseSpecial(bool useSpecial) bool BrowserSettings::passwordUseEASCII() { - return config()->get("generator/EASCII", false).toBool(); + return config()->get("generator/EASCII", PasswordGenerator::DefaultEASCII).toBool(); } void BrowserSettings::setPasswordUseEASCII(bool useEASCII) @@ -261,7 +261,7 @@ void BrowserSettings::setPasswordUseEASCII(bool useEASCII) int BrowserSettings::passPhraseWordCount() { - return config()->get("generator/WordCount", 6).toInt(); + return config()->get("generator/WordCount", PassphraseGenerator::DefaultWordCount).toInt(); } void BrowserSettings::setPassPhraseWordCount(int wordCount) @@ -271,7 +271,7 @@ void BrowserSettings::setPassPhraseWordCount(int wordCount) QString BrowserSettings::passPhraseWordSeparator() { - return config()->get("generator/WordSeparator", " ").toString(); + return config()->get("generator/WordSeparator", PassphraseGenerator::DefaultSeparator).toString(); } void BrowserSettings::setPassPhraseWordSeparator(QString separator) @@ -291,7 +291,7 @@ void BrowserSettings::setGeneratorType(int type) bool BrowserSettings::passwordEveryGroup() { - return config()->get("generator/EnsureEvery", true).toBool(); + return config()->get("generator/EnsureEvery", PasswordGenerator::DefaultFromEveryGroup).toBool(); } void BrowserSettings::setPasswordEveryGroup(bool everyGroup) @@ -301,7 +301,7 @@ void BrowserSettings::setPasswordEveryGroup(bool everyGroup) bool BrowserSettings::passwordExcludeAlike() { - return config()->get("generator/ExcludeAlike", true).toBool(); + return config()->get("generator/ExcludeAlike", PasswordGenerator::DefaultLookAlike).toBool(); } void BrowserSettings::setPasswordExcludeAlike(bool excludeAlike) @@ -311,7 +311,7 @@ void BrowserSettings::setPasswordExcludeAlike(bool excludeAlike) int BrowserSettings::passwordLength() { - return config()->get("generator/Length", 20).toInt(); + return config()->get("generator/Length", PasswordGenerator::DefaultLength).toInt(); } void BrowserSettings::setPasswordLength(int length) diff --git a/src/core/FilePath.cpp b/src/core/FilePath.cpp index f694f548b..6b555075a 100644 --- a/src/core/FilePath.cpp +++ b/src/core/FilePath.cpp @@ -91,6 +91,11 @@ QString FilePath::pluginPath(const QString& name) return QString(); } +QString FilePath::wordlistPath(const QString& name) +{ + return m_instance->dataPath("wordlists/" + name); +} + QIcon FilePath::applicationIcon() { bool darkIcon = useDarkIcon(); diff --git a/src/core/FilePath.h b/src/core/FilePath.h index 2db496937..f84f84e0e 100644 --- a/src/core/FilePath.h +++ b/src/core/FilePath.h @@ -27,6 +27,7 @@ class FilePath public: QString dataPath(const QString& name); QString pluginPath(const QString& name); + QString wordlistPath(const QString& name); QIcon applicationIcon(); QIcon trayIconLocked(); QIcon trayIconUnlocked(); diff --git a/src/core/PassphraseGenerator.cpp b/src/core/PassphraseGenerator.cpp index 941ba5f38..2bc2be56d 100644 --- a/src/core/PassphraseGenerator.cpp +++ b/src/core/PassphraseGenerator.cpp @@ -24,9 +24,12 @@ #include "crypto/Random.h" #include "core/FilePath.h" +const QString PassphraseGenerator::DefaultSeparator = " "; +const QString PassphraseGenerator::DefaultWordList = "eff_large.wordlist"; + PassphraseGenerator::PassphraseGenerator() : m_wordCount(0) - , m_separator(' ') + , m_separator(PassphraseGenerator::DefaultSeparator) { } @@ -76,7 +79,7 @@ void PassphraseGenerator::setWordList(QString path) void PassphraseGenerator::setDefaultWordList() { - const QString path = filePath()->dataPath("wordlists/eff_large.wordlist"); + const QString path = filePath()->wordlistPath(PassphraseGenerator::DefaultWordList); setWordList(path); } diff --git a/src/core/PassphraseGenerator.h b/src/core/PassphraseGenerator.h index 7df4b4a74..4a489827d 100644 --- a/src/core/PassphraseGenerator.h +++ b/src/core/PassphraseGenerator.h @@ -37,6 +37,8 @@ public: QString generatePassphrase() const; static const int DefaultWordCount = 7; + static const QString DefaultSeparator; + static const QString DefaultWordList; private: int m_wordCount; diff --git a/src/core/PasswordGenerator.h b/src/core/PasswordGenerator.h index 0c13bac05..d1b2a0b39 100644 --- a/src/core/PasswordGenerator.h +++ b/src/core/PasswordGenerator.h @@ -42,7 +42,8 @@ public: enum GeneratorFlag { ExcludeLookAlike = 0x1, - CharFromEveryGroup = 0x2 + CharFromEveryGroup = 0x2, + DefaultFlags = ExcludeLookAlike | CharFromEveryGroup }; Q_DECLARE_FLAGS(GeneratorFlags, GeneratorFlag) @@ -60,6 +61,13 @@ public: int getbits() const; static const int DefaultLength = 16; + static const bool DefaultLower = (DefaultCharset & LowerLetters) != 0; + static const bool DefaultUpper = (DefaultCharset & UpperLetters) != 0; + static const bool DefaultNumbers = (DefaultCharset & Numbers) != 0; + static const bool DefaultSpecial = (DefaultCharset & SpecialCharacters) != 0; + static const bool DefaultEASCII = (DefaultCharset & EASCII) != 0; + static const bool DefaultLookAlike = (DefaultFlags & ExcludeLookAlike) != 0; + static const bool DefaultFromEveryGroup = (DefaultFlags & CharFromEveryGroup) != 0; private: QVector passwordGroups() const; diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index c9c10aa56..e6cb0a92d 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -68,9 +68,9 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) } // set default separator to Space - m_ui->editWordSeparator->setText(" "); + m_ui->editWordSeparator->setText(PassphraseGenerator::DefaultSeparator); - QDir path(filePath()->dataPath("wordlists/")); + QDir path(filePath()->wordlistPath("")); QStringList files = path.entryList(QDir::Files); m_ui->comboBoxWordList->addItems(files); if (files.size() > 1) { @@ -93,19 +93,19 @@ PasswordGeneratorWidget::~PasswordGeneratorWidget() void PasswordGeneratorWidget::loadSettings() { // Password config - m_ui->checkBoxLower->setChecked(config()->get("generator/LowerCase", true).toBool()); - m_ui->checkBoxUpper->setChecked(config()->get("generator/UpperCase", true).toBool()); - m_ui->checkBoxNumbers->setChecked(config()->get("generator/Numbers", true).toBool()); - m_ui->checkBoxSpecialChars->setChecked(config()->get("generator/SpecialChars", false).toBool()); - m_ui->checkBoxExtASCII->setChecked(config()->get("generator/EASCII", false).toBool()); - m_ui->checkBoxExcludeAlike->setChecked(config()->get("generator/ExcludeAlike", true).toBool()); - m_ui->checkBoxEnsureEvery->setChecked(config()->get("generator/EnsureEvery", true).toBool()); + m_ui->checkBoxLower->setChecked(config()->get("generator/LowerCase", PasswordGenerator::DefaultLower).toBool()); + m_ui->checkBoxUpper->setChecked(config()->get("generator/UpperCase", PasswordGenerator::DefaultUpper).toBool()); + m_ui->checkBoxNumbers->setChecked(config()->get("generator/Numbers", PasswordGenerator::DefaultNumbers).toBool()); + m_ui->checkBoxSpecialChars->setChecked(config()->get("generator/SpecialChars", PasswordGenerator::DefaultSpecial).toBool()); + m_ui->checkBoxExtASCII->setChecked(config()->get("generator/EASCII", PasswordGenerator::DefaultEASCII).toBool()); + m_ui->checkBoxExcludeAlike->setChecked(config()->get("generator/ExcludeAlike", PasswordGenerator::DefaultLookAlike).toBool()); + m_ui->checkBoxEnsureEvery->setChecked(config()->get("generator/EnsureEvery", PasswordGenerator::DefaultFromEveryGroup).toBool()); m_ui->spinBoxLength->setValue(config()->get("generator/Length", PasswordGenerator::DefaultLength).toInt()); // Diceware config - m_ui->spinBoxWordCount->setValue(config()->get("generator/WordCount", 6).toInt()); - m_ui->editWordSeparator->setText(config()->get("generator/WordSeparator", " ").toString()); - m_ui->comboBoxWordList->setCurrentText(config()->get("generator/WordList", "eff_large.wordlist").toString()); + m_ui->spinBoxWordCount->setValue(config()->get("generator/WordCount", PassphraseGenerator::DefaultWordCount).toInt()); + m_ui->editWordSeparator->setText(config()->get("generator/WordSeparator", PassphraseGenerator::DefaultSeparator).toString()); + m_ui->comboBoxWordList->setCurrentText(config()->get("generator/WordList", PassphraseGenerator::DefaultWordList).toString()); // Password or diceware? m_ui->tabWidget->setCurrentIndex(config()->get("generator/Type", 0).toInt()); @@ -394,7 +394,7 @@ void PasswordGeneratorWidget::updateGenerator() m_dicewareGenerator->setWordCount(m_ui->spinBoxWordCount->value()); if (!m_ui->comboBoxWordList->currentText().isEmpty()) { - QString path = filePath()->dataPath("wordlists/" + m_ui->comboBoxWordList->currentText()); + QString path = filePath()->wordlistPath(m_ui->comboBoxWordList->currentText()); m_dicewareGenerator->setWordList(path); } m_dicewareGenerator->setWordSeparator(m_ui->editWordSeparator->text()); diff --git a/src/http/HttpPasswordGeneratorWidget.cpp b/src/http/HttpPasswordGeneratorWidget.cpp index b722a85f3..f08bc3d36 100644 --- a/src/http/HttpPasswordGeneratorWidget.cpp +++ b/src/http/HttpPasswordGeneratorWidget.cpp @@ -48,13 +48,14 @@ HttpPasswordGeneratorWidget::~HttpPasswordGeneratorWidget() void HttpPasswordGeneratorWidget::loadSettings() { - m_ui->checkBoxLower->setChecked(config()->get("Http/generator/LowerCase", true).toBool()); - m_ui->checkBoxUpper->setChecked(config()->get("Http/generator/UpperCase", true).toBool()); - m_ui->checkBoxNumbers->setChecked(config()->get("Http/generator/Numbers", true).toBool()); - m_ui->checkBoxSpecialChars->setChecked(config()->get("Http/generator/SpecialChars", false).toBool()); + m_ui->checkBoxLower->setChecked(config()->get("Http/generator/LowerCase", PasswordGenerator::DefaultLower).toBool()); + m_ui->checkBoxUpper->setChecked(config()->get("Http/generator/UpperCase", PasswordGenerator::DefaultUpper).toBool()); + m_ui->checkBoxNumbers->setChecked(config()->get("Http/generator/Numbers", PasswordGenerator::DefaultNumbers).toBool()); + m_ui->checkBoxSpecialChars->setChecked(config()->get("Http/generator/SpecialChars", PasswordGenerator::DefaultSpecial).toBool()); + m_ui->checkBoxSpecialChars->setChecked(config()->get("Http/generator/EASCII", PasswordGenerator::DefaultEASCII).toBool()); - m_ui->checkBoxExcludeAlike->setChecked(config()->get("Http/generator/ExcludeAlike", true).toBool()); - m_ui->checkBoxEnsureEvery->setChecked(config()->get("Http/generator/EnsureEvery", true).toBool()); + m_ui->checkBoxExcludeAlike->setChecked(config()->get("Http/generator/ExcludeAlike", PasswordGenerator::DefaultLookAlike).toBool()); + m_ui->checkBoxEnsureEvery->setChecked(config()->get("Http/generator/EnsureEvery", PasswordGenerator::DefaultFromEveryGroup).toBool()); m_ui->spinBoxLength->setValue(config()->get("Http/generator/Length", PasswordGenerator::DefaultLength).toInt()); } @@ -65,6 +66,7 @@ void HttpPasswordGeneratorWidget::saveSettings() config()->set("Http/generator/UpperCase", m_ui->checkBoxUpper->isChecked()); config()->set("Http/generator/Numbers", m_ui->checkBoxNumbers->isChecked()); config()->set("Http/generator/SpecialChars", m_ui->checkBoxSpecialChars->isChecked()); + config()->set("Http/generator/EASCII", m_ui->checkBoxExtASCII->isChecked()); config()->set("Http/generator/ExcludeAlike", m_ui->checkBoxExcludeAlike->isChecked()); config()->set("Http/generator/EnsureEvery", m_ui->checkBoxEnsureEvery->isChecked()); @@ -120,6 +122,10 @@ PasswordGenerator::CharClasses HttpPasswordGeneratorWidget::charClasses() classes |= PasswordGenerator::SpecialCharacters; } + if (m_ui->checkBoxExtASCII->isChecked()) { + classes |= PasswordGenerator::EASCII; + } + return classes; } diff --git a/src/http/HttpPasswordGeneratorWidget.ui b/src/http/HttpPasswordGeneratorWidget.ui index 066b9c512..71df88730 100644 --- a/src/http/HttpPasswordGeneratorWidget.ui +++ b/src/http/HttpPasswordGeneratorWidget.ui @@ -142,6 +142,22 @@ + + + + Extended ASCII + + + Extended ASCII + + + true + + + optionButtons + + + diff --git a/src/http/HttpSettings.cpp b/src/http/HttpSettings.cpp index 60a35940c..7ff0dbaa4 100644 --- a/src/http/HttpSettings.cpp +++ b/src/http/HttpSettings.cpp @@ -145,7 +145,7 @@ void HttpSettings::setHttpPort(int port) bool HttpSettings::passwordUseNumbers() { - return config()->get("Http/generator/Numbers", true).toBool(); + return config()->get("Http/generator/Numbers", PasswordGenerator::DefaultNumbers).toBool(); } void HttpSettings::setPasswordUseNumbers(bool useNumbers) @@ -155,7 +155,7 @@ void HttpSettings::setPasswordUseNumbers(bool useNumbers) bool HttpSettings::passwordUseLowercase() { - return config()->get("Http/generator/LowerCase", true).toBool(); + return config()->get("Http/generator/LowerCase", PasswordGenerator::DefaultLower).toBool(); } void HttpSettings::setPasswordUseLowercase(bool useLowercase) @@ -165,7 +165,7 @@ void HttpSettings::setPasswordUseLowercase(bool useLowercase) bool HttpSettings::passwordUseUppercase() { - return config()->get("Http/generator/UpperCase", true).toBool(); + return config()->get("Http/generator/UpperCase", PasswordGenerator::DefaultUpper).toBool(); } void HttpSettings::setPasswordUseUppercase(bool useUppercase) @@ -175,7 +175,7 @@ void HttpSettings::setPasswordUseUppercase(bool useUppercase) bool HttpSettings::passwordUseSpecial() { - return config()->get("Http/generator/SpecialChars", false).toBool(); + return config()->get("Http/generator/SpecialChars", PasswordGenerator::DefaultSpecial).toBool(); } void HttpSettings::setPasswordUseSpecial(bool useSpecial) @@ -183,9 +183,19 @@ void HttpSettings::setPasswordUseSpecial(bool useSpecial) config()->set("Http/generator/SpecialChars", useSpecial); } +bool HttpSettings::passwordUseEASCII() +{ + return config()->get("Http/generator/EASCII", PasswordGenerator::DefaultEASCII).toBool(); +} + +void HttpSettings::setPasswordUseEASCII(bool useExtended) +{ + config()->set("Http/generator/EASCII", useExtended); +} + bool HttpSettings::passwordEveryGroup() { - return config()->get("Http/generator/EnsureEvery", true).toBool(); + return config()->get("Http/generator/EnsureEvery", PasswordGenerator::DefaultFromEveryGroup).toBool(); } void HttpSettings::setPasswordEveryGroup(bool everyGroup) @@ -195,7 +205,7 @@ void HttpSettings::setPasswordEveryGroup(bool everyGroup) bool HttpSettings::passwordExcludeAlike() { - return config()->get("Http/generator/ExcludeAlike", true).toBool(); + return config()->get("Http/generator/ExcludeAlike", PasswordGenerator::DefaultLookAlike).toBool(); } void HttpSettings::setPasswordExcludeAlike(bool excludeAlike) @@ -205,7 +215,7 @@ void HttpSettings::setPasswordExcludeAlike(bool excludeAlike) int HttpSettings::passwordLength() { - return config()->get("Http/generator/Length", 20).toInt(); + return config()->get("Http/generator/Length", PasswordGenerator::DefaultLength).toInt(); } void HttpSettings::setPasswordLength(int length) @@ -217,14 +227,21 @@ void HttpSettings::setPasswordLength(int length) PasswordGenerator::CharClasses HttpSettings::passwordCharClasses() { PasswordGenerator::CharClasses classes; - if (passwordUseLowercase()) + if (passwordUseLowercase()) { classes |= PasswordGenerator::LowerLetters; - if (passwordUseUppercase()) + } + if (passwordUseUppercase()) { classes |= PasswordGenerator::UpperLetters; - if (passwordUseNumbers()) + } + if (passwordUseNumbers()) { classes |= PasswordGenerator::Numbers; - if (passwordUseSpecial()) + } + if (passwordUseSpecial()) { classes |= PasswordGenerator::SpecialCharacters; + } + if (passwordUseEASCII()) { + classes |= PasswordGenerator::EASCII; + } return classes; } diff --git a/src/http/HttpSettings.h b/src/http/HttpSettings.h index a4aee1a63..63c2963cf 100644 --- a/src/http/HttpSettings.h +++ b/src/http/HttpSettings.h @@ -58,6 +58,8 @@ public: static void setPasswordUseUppercase(bool useUppercase); static bool passwordUseSpecial(); static void setPasswordUseSpecial(bool useSpecial); + static bool passwordUseEASCII(); + static void setPasswordUseEASCII(bool useExtended); static bool passwordEveryGroup(); static void setPasswordEveryGroup(bool everyGroup); static bool passwordExcludeAlike(); From 1bfbb9242c8747bb192ce05d94e4c4a28219d9b0 Mon Sep 17 00:00:00 2001 From: thez3ro Date: Tue, 6 Feb 2018 01:17:36 +0100 Subject: [PATCH 18/22] fix cli commands, translations and codestyle --- src/cli/Add.cpp | 7 ++++--- src/cli/Add.h | 2 +- src/cli/Clip.cpp | 6 +++--- src/cli/Clip.h | 2 +- src/cli/Command.cpp | 2 +- src/cli/Command.h | 2 +- src/cli/Diceware.cpp | 10 +++++----- src/cli/Diceware.h | 2 +- src/cli/Edit.cpp | 7 ++++--- src/cli/Edit.h | 2 +- src/cli/Estimate.cpp | 6 +++--- src/cli/Estimate.h | 2 +- src/cli/Extract.cpp | 6 +++--- src/cli/Extract.h | 2 +- src/cli/Generate.cpp | 15 ++++++++------- src/cli/Generate.h | 2 +- src/cli/List.cpp | 6 +++--- src/cli/List.h | 2 +- src/cli/Locate.cpp | 6 +++--- src/cli/Locate.h | 2 +- src/cli/Merge.cpp | 6 +++--- src/cli/Merge.h | 2 +- src/cli/Remove.cpp | 6 +++--- src/cli/Remove.h | 2 +- src/cli/Show.cpp | 6 +++--- src/cli/Show.h | 2 +- src/cli/Utils.cpp | 2 +- src/cli/Utils.h | 2 +- src/cli/keepassxc-cli.1 | 4 ++-- src/core/PasswordGenerator.h | 14 +++++++------- 30 files changed, 70 insertions(+), 67 deletions(-) diff --git a/src/cli/Add.cpp b/src/cli/Add.cpp index 6954532cd..5c97299b8 100644 --- a/src/cli/Add.cpp +++ b/src/cli/Add.cpp @@ -31,15 +31,15 @@ Add::Add() { - this->name = QString("add"); - this->description = QObject::tr("Add a new entry to a database."); + name = QString("add"); + description = QObject::tr("Add a new entry to a database."); } Add::~Add() { } -int Add::execute(QStringList arguments) +int Add::execute(const QStringList& arguments) { QTextStream inputTextStream(stdin, QIODevice::ReadOnly); @@ -134,6 +134,7 @@ int Add::execute(QStringList arguments) } passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset); + passwordGenerator.setFlags(PasswordGenerator::DefaultFlags); QString password = passwordGenerator.generatePassword(); entry->setPassword(password); } diff --git a/src/cli/Add.h b/src/cli/Add.h index 14356c418..5769249c9 100644 --- a/src/cli/Add.h +++ b/src/cli/Add.h @@ -25,7 +25,7 @@ class Add : public Command public: Add(); ~Add(); - int execute(QStringList arguments); + int execute(const QStringList& arguments); }; #endif // KEEPASSXC_ADD_H diff --git a/src/cli/Clip.cpp b/src/cli/Clip.cpp index 6b458a673..886f8ecc7 100644 --- a/src/cli/Clip.cpp +++ b/src/cli/Clip.cpp @@ -32,15 +32,15 @@ Clip::Clip() { - this->name = QString("clip"); - this->description = QObject::tr("Copy an entry's password to the clipboard."); + name = QString("clip"); + description = QObject::tr("Copy an entry's password to the clipboard."); } Clip::~Clip() { } -int Clip::execute(QStringList arguments) +int Clip::execute(const QStringList& arguments) { QTextStream out(stdout); diff --git a/src/cli/Clip.h b/src/cli/Clip.h index a9e24faee..e94231236 100644 --- a/src/cli/Clip.h +++ b/src/cli/Clip.h @@ -25,7 +25,7 @@ class Clip : public Command public: Clip(); ~Clip(); - int execute(QStringList arguments); + int execute(const QStringList& arguments); int clipEntry(Database* database, QString entryPath, QString timeout); }; diff --git a/src/cli/Command.cpp b/src/cli/Command.cpp index f0441fd7f..ef6948888 100644 --- a/src/cli/Command.cpp +++ b/src/cli/Command.cpp @@ -41,7 +41,7 @@ Command::~Command() { } -int Command::execute(QStringList) +int Command::execute(const QStringList&) { return EXIT_FAILURE; } diff --git a/src/cli/Command.h b/src/cli/Command.h index b751c4a8e..2ebdd77b9 100644 --- a/src/cli/Command.h +++ b/src/cli/Command.h @@ -29,7 +29,7 @@ class Command { public: virtual ~Command(); - virtual int execute(QStringList arguments); + virtual int execute(const QStringList& arguments); QString name; QString description; QString getDescriptionLine(); diff --git a/src/cli/Diceware.cpp b/src/cli/Diceware.cpp index 080a21c1f..c71b57d7e 100644 --- a/src/cli/Diceware.cpp +++ b/src/cli/Diceware.cpp @@ -27,15 +27,15 @@ Diceware::Diceware() { - this->name = QString("diceware"); - this->description = QObject::tr("Generate a new random password."); + name = QString("diceware"); + description = QObject::tr("Generate a new random diceware passphrase."); } Diceware::~Diceware() { } -int Diceware::execute(QStringList arguments) +int Diceware::execute(const QStringList& arguments) { QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QTextStream outputTextStream(stdout, QIODevice::WriteOnly); @@ -48,13 +48,13 @@ int Diceware::execute(QStringList arguments) parser.addOption(words); QCommandLineOption wordlistFile(QStringList() << "w" << "word-list", - QObject::tr("Wordlist fot the diceware generator.\n[Default: EFF English]"), + QObject::tr("Wordlist for the diceware generator.\n[Default: EFF English]"), QObject::tr("path")); parser.addOption(wordlistFile); parser.process(arguments); const QStringList args = parser.positionalArguments(); - if (args.size() != 0) { + if (!args.isEmpty()) { outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware"); return EXIT_FAILURE; } diff --git a/src/cli/Diceware.h b/src/cli/Diceware.h index b6d71b6c6..61fe724ca 100644 --- a/src/cli/Diceware.h +++ b/src/cli/Diceware.h @@ -25,7 +25,7 @@ class Diceware : public Command public: Diceware(); ~Diceware(); - int execute(QStringList arguments); + int execute(const QStringList& arguments); }; #endif // KEEPASSXC_DICEWARE_H diff --git a/src/cli/Edit.cpp b/src/cli/Edit.cpp index 675ec7def..967ddd8ed 100644 --- a/src/cli/Edit.cpp +++ b/src/cli/Edit.cpp @@ -31,15 +31,15 @@ Edit::Edit() { - this->name = QString("edit"); - this->description = QObject::tr("Edit an entry."); + name = QString("edit"); + description = QObject::tr("Edit an entry."); } Edit::~Edit() { } -int Edit::execute(QStringList arguments) +int Edit::execute(const QStringList& arguments) { QTextStream inputTextStream(stdin, QIODevice::ReadOnly); @@ -150,6 +150,7 @@ int Edit::execute(QStringList arguments) } passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset); + passwordGenerator.setFlags(PasswordGenerator::DefaultFlags); QString password = passwordGenerator.generatePassword(); entry->setPassword(password); } diff --git a/src/cli/Edit.h b/src/cli/Edit.h index e52069ff0..2c413bea0 100644 --- a/src/cli/Edit.h +++ b/src/cli/Edit.h @@ -25,7 +25,7 @@ class Edit : public Command public: Edit(); ~Edit(); - int execute(QStringList arguments); + int execute(const QStringList& arguments); }; #endif // KEEPASSXC_EDIT_H diff --git a/src/cli/Estimate.cpp b/src/cli/Estimate.cpp index 80226a27e..9a2ab0b0f 100644 --- a/src/cli/Estimate.cpp +++ b/src/cli/Estimate.cpp @@ -34,8 +34,8 @@ Estimate::Estimate() { - this->name = QString("estimate"); - this->description = QObject::tr("Estimate the entropy of a password."); + name = QString("estimate"); + description = QObject::tr("Estimate the entropy of a password."); } Estimate::~Estimate() @@ -138,7 +138,7 @@ static void estimate(const char* pwd, bool advanced) } } -int Estimate::execute(QStringList arguments) +int Estimate::execute(const QStringList& arguments) { QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QTextStream outputTextStream(stdout, QIODevice::WriteOnly); diff --git a/src/cli/Estimate.h b/src/cli/Estimate.h index 2cbe49104..15f922752 100644 --- a/src/cli/Estimate.h +++ b/src/cli/Estimate.h @@ -25,7 +25,7 @@ class Estimate : public Command public: Estimate(); ~Estimate(); - int execute(QStringList arguments); + int execute(const QStringList& arguments); }; #endif // KEEPASSXC_ESTIMATE_H diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 477f2b0e2..b48d5a6aa 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -33,15 +33,15 @@ Extract::Extract() { - this->name = QString("extract"); - this->description = QObject::tr("Extract and print the content of a database."); + name = QString("extract"); + description = QObject::tr("Extract and print the content of a database."); } Extract::~Extract() { } -int Extract::execute(QStringList arguments) +int Extract::execute(const QStringList& arguments) { QTextStream out(stdout); QTextStream errorTextStream(stderr); diff --git a/src/cli/Extract.h b/src/cli/Extract.h index bef6a7821..2939afddb 100644 --- a/src/cli/Extract.h +++ b/src/cli/Extract.h @@ -25,7 +25,7 @@ class Extract : public Command public: Extract(); ~Extract(); - int execute(QStringList arguments); + int execute(const QStringList& arguments); }; #endif // KEEPASSXC_EXTRACT_H diff --git a/src/cli/Generate.cpp b/src/cli/Generate.cpp index 3588cb421..eb8fea5e8 100644 --- a/src/cli/Generate.cpp +++ b/src/cli/Generate.cpp @@ -27,15 +27,15 @@ Generate::Generate() { - this->name = QString("generate"); - this->description = QObject::tr("Generate a new random password."); + name = QString("generate"); + description = QObject::tr("Generate a new random password."); } Generate::~Generate() { } -int Generate::execute(QStringList arguments) +int Generate::execute(const QStringList& arguments) { QTextStream inputTextStream(stdin, QIODevice::ReadOnly); QTextStream outputTextStream(stdout, QIODevice::WriteOnly); @@ -47,10 +47,10 @@ int Generate::execute(QStringList arguments) QObject::tr("length")); parser.addOption(len); QCommandLineOption lower(QStringList() << "l", - QObject::tr("Use lowercase in the generated password.")); + QObject::tr("Use lowercase characters in the generated password.")); parser.addOption(lower); QCommandLineOption upper(QStringList() << "u", - QObject::tr("Use uppercase in the generated password.")); + QObject::tr("Use uppercase characters in the generated password.")); parser.addOption(upper); QCommandLineOption numeric(QStringList() << "n", QObject::tr("Use numbers in the generated password.")); @@ -59,12 +59,12 @@ int Generate::execute(QStringList arguments) QObject::tr("Use special characters in the generated password.")); parser.addOption(special); QCommandLineOption extended(QStringList() << "e", - QObject::tr("Use extended ascii in the generated password.")); + QObject::tr("Use extended ASCII in the generated password.")); parser.addOption(extended); parser.process(arguments); const QStringList args = parser.positionalArguments(); - if (args.size() != 0) { + if (!args.isEmpty()) { outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); return EXIT_FAILURE; } @@ -97,6 +97,7 @@ int Generate::execute(QStringList arguments) } passwordGenerator.setCharClasses(classes); + passwordGenerator.setFlags(PasswordGenerator::DefaultFlags); if (!passwordGenerator.isValid()) { outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); diff --git a/src/cli/Generate.h b/src/cli/Generate.h index de6a8ea11..607fc105c 100644 --- a/src/cli/Generate.h +++ b/src/cli/Generate.h @@ -25,7 +25,7 @@ class Generate : public Command public: Generate(); ~Generate(); - int execute(QStringList arguments); + int execute(const QStringList& arguments); }; #endif // KEEPASSXC_GENERATE_H diff --git a/src/cli/List.cpp b/src/cli/List.cpp index 73830cab8..bdedaf210 100644 --- a/src/cli/List.cpp +++ b/src/cli/List.cpp @@ -29,15 +29,15 @@ List::List() { - this->name = QString("ls"); - this->description = QObject::tr("List database entries."); + name = QString("ls"); + description = QObject::tr("List database entries."); } List::~List() { } -int List::execute(QStringList arguments) +int List::execute(const QStringList& arguments) { QTextStream out(stdout); diff --git a/src/cli/List.h b/src/cli/List.h index d12105f3c..98b8b5a45 100644 --- a/src/cli/List.h +++ b/src/cli/List.h @@ -25,7 +25,7 @@ class List : public Command public: List(); ~List(); - int execute(QStringList arguments); + int execute(const QStringList& arguments); int listGroup(Database* database, QString groupPath = QString("")); }; diff --git a/src/cli/Locate.cpp b/src/cli/Locate.cpp index 83a3c5ce0..f80372885 100644 --- a/src/cli/Locate.cpp +++ b/src/cli/Locate.cpp @@ -31,15 +31,15 @@ Locate::Locate() { - this->name = QString("locate"); - this->description = QObject::tr("Find entries quickly."); + name = QString("locate"); + description = QObject::tr("Find entries quickly."); } Locate::~Locate() { } -int Locate::execute(QStringList arguments) +int Locate::execute(const QStringList& arguments) { QTextStream out(stdout); diff --git a/src/cli/Locate.h b/src/cli/Locate.h index c919b0cb3..3677a034d 100644 --- a/src/cli/Locate.h +++ b/src/cli/Locate.h @@ -25,7 +25,7 @@ class Locate : public Command public: Locate(); ~Locate(); - int execute(QStringList arguments); + int execute(const QStringList& arguments); int locateEntry(Database* database, QString searchTerm); }; diff --git a/src/cli/Merge.cpp b/src/cli/Merge.cpp index 5df6b0188..6b114bff3 100644 --- a/src/cli/Merge.cpp +++ b/src/cli/Merge.cpp @@ -26,15 +26,15 @@ Merge::Merge() { - this->name = QString("merge"); - this->description = QObject::tr("Merge two databases."); + name = QString("merge"); + description = QObject::tr("Merge two databases."); } Merge::~Merge() { } -int Merge::execute(QStringList arguments) +int Merge::execute(const QStringList& arguments) { QTextStream out(stdout); diff --git a/src/cli/Merge.h b/src/cli/Merge.h index 4f0b42836..496c66b86 100644 --- a/src/cli/Merge.h +++ b/src/cli/Merge.h @@ -25,7 +25,7 @@ class Merge : public Command public: Merge(); ~Merge(); - int execute(QStringList arguments); + int execute(const QStringList& arguments); }; #endif // KEEPASSXC_MERGE_H diff --git a/src/cli/Remove.cpp b/src/cli/Remove.cpp index 6abb68f1c..64a5976e9 100644 --- a/src/cli/Remove.cpp +++ b/src/cli/Remove.cpp @@ -34,15 +34,15 @@ Remove::Remove() { - this->name = QString("rm"); - this->description = QString("Remove an entry from the database."); + name = QString("rm"); + description = QString("Remove an entry from the database."); } Remove::~Remove() { } -int Remove::execute(QStringList arguments) +int Remove::execute(const QStringList& arguments) { QTextStream outputTextStream(stdout, QIODevice::WriteOnly); diff --git a/src/cli/Remove.h b/src/cli/Remove.h index 2440c201c..5465530ed 100644 --- a/src/cli/Remove.h +++ b/src/cli/Remove.h @@ -27,7 +27,7 @@ class Remove : public Command public: Remove(); ~Remove(); - int execute(QStringList arguments); + int execute(const QStringList& arguments); int removeEntry(Database* database, QString databasePath, QString entryPath); }; diff --git a/src/cli/Show.cpp b/src/cli/Show.cpp index 66225c56a..54561b1f7 100644 --- a/src/cli/Show.cpp +++ b/src/cli/Show.cpp @@ -29,15 +29,15 @@ Show::Show() { - this->name = QString("show"); - this->description = QObject::tr("Show an entry's information."); + name = QString("show"); + description = QObject::tr("Show an entry's information."); } Show::~Show() { } -int Show::execute(QStringList arguments) +int Show::execute(const QStringList& arguments) { QTextStream out(stdout); diff --git a/src/cli/Show.h b/src/cli/Show.h index f2caefdbf..18b6d7049 100644 --- a/src/cli/Show.h +++ b/src/cli/Show.h @@ -25,7 +25,7 @@ class Show : public Command public: Show(); ~Show(); - int execute(QStringList arguments); + int execute(const QStringList& arguments); int showEntry(Database* database, QStringList attributes, QString entryPath); }; diff --git a/src/cli/Utils.cpp b/src/cli/Utils.cpp index f42095cbb..35e7cce38 100644 --- a/src/cli/Utils.cpp +++ b/src/cli/Utils.cpp @@ -76,7 +76,7 @@ QString Utils::getPassword() * A valid and running event loop is needed to use the global QClipboard, * so we need to use this from the CLI. */ -int Utils::clipText(QString text) +int Utils::clipText(const QString& text) { QString programName = ""; diff --git a/src/cli/Utils.h b/src/cli/Utils.h index 0c6b749a3..1f8051183 100644 --- a/src/cli/Utils.h +++ b/src/cli/Utils.h @@ -25,7 +25,7 @@ class Utils public: static void setStdinEcho(bool enable); static QString getPassword(); - static int clipText(QString text); + static int clipText(const QString& text); }; #endif // KEEPASSXC_UTILS_H diff --git a/src/cli/keepassxc-cli.1 b/src/cli/keepassxc-cli.1 index ebd3ea3da..cc1e7b8d7 100644 --- a/src/cli/keepassxc-cli.1 +++ b/src/cli/keepassxc-cli.1 @@ -32,7 +32,7 @@ Estimates the entropy of a password. The password to estimate can be provided as Extracts and prints the contents of a database to standard output in XML format. .IP "generate [options]" -Generate a random password +Generate a random password. .IP "locate [options] " Locates all the entries that match a specific search term in a database. @@ -139,7 +139,7 @@ Use numbers characters for the generated password. [Default: Enabled] Use special characters for the generated password. [Default: Disabled] .IP "-e" -Use extended ascii characters for the generated password. [Default: Disabled] +Use extended ASCII characters for the generated password. [Default: Disabled] diff --git a/src/core/PasswordGenerator.h b/src/core/PasswordGenerator.h index d1b2a0b39..15a0dcefe 100644 --- a/src/core/PasswordGenerator.h +++ b/src/core/PasswordGenerator.h @@ -61,13 +61,13 @@ public: int getbits() const; static const int DefaultLength = 16; - static const bool DefaultLower = (DefaultCharset & LowerLetters) != 0; - static const bool DefaultUpper = (DefaultCharset & UpperLetters) != 0; - static const bool DefaultNumbers = (DefaultCharset & Numbers) != 0; - static const bool DefaultSpecial = (DefaultCharset & SpecialCharacters) != 0; - static const bool DefaultEASCII = (DefaultCharset & EASCII) != 0; - static const bool DefaultLookAlike = (DefaultFlags & ExcludeLookAlike) != 0; - static const bool DefaultFromEveryGroup = (DefaultFlags & CharFromEveryGroup) != 0; + static constexpr bool DefaultLower = (DefaultCharset & LowerLetters) != 0; + static constexpr bool DefaultUpper = (DefaultCharset & UpperLetters) != 0; + static constexpr bool DefaultNumbers = (DefaultCharset & Numbers) != 0; + static constexpr bool DefaultSpecial = (DefaultCharset & SpecialCharacters) != 0; + static constexpr bool DefaultEASCII = (DefaultCharset & EASCII) != 0; + static constexpr bool DefaultLookAlike = (DefaultFlags & ExcludeLookAlike) != 0; + static constexpr bool DefaultFromEveryGroup = (DefaultFlags & CharFromEveryGroup) != 0; private: QVector passwordGroups() const; From ab3775d4c5bda89db948b0efaafd264a2ce9466c Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 11 Feb 2018 15:08:07 +0100 Subject: [PATCH 19/22] Fix regression introduced in 6723f42 Use of QString for static DefaultSeparator lead to crashes on Windows --- src/core/FilePath.cpp | 2 +- src/core/PassphraseGenerator.cpp | 29 ++++++++++++----------------- src/core/PassphraseGenerator.h | 15 +++++++-------- 3 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/core/FilePath.cpp b/src/core/FilePath.cpp index 6b555075a..330542eb9 100644 --- a/src/core/FilePath.cpp +++ b/src/core/FilePath.cpp @@ -93,7 +93,7 @@ QString FilePath::pluginPath(const QString& name) QString FilePath::wordlistPath(const QString& name) { - return m_instance->dataPath("wordlists/" + name); + return dataPath("wordlists/" + name); } QIcon FilePath::applicationIcon() diff --git a/src/core/PassphraseGenerator.cpp b/src/core/PassphraseGenerator.cpp index 2bc2be56d..88871eb8c 100644 --- a/src/core/PassphraseGenerator.cpp +++ b/src/core/PassphraseGenerator.cpp @@ -17,32 +17,31 @@ #include "PassphraseGenerator.h" -#include +#include #include #include #include "crypto/Random.h" #include "core/FilePath.h" -const QString PassphraseGenerator::DefaultSeparator = " "; -const QString PassphraseGenerator::DefaultWordList = "eff_large.wordlist"; +const char* PassphraseGenerator::DefaultSeparator = " "; +const char* PassphraseGenerator::DefaultWordList = "eff_large.wordlist"; PassphraseGenerator::PassphraseGenerator() : m_wordCount(0) , m_separator(PassphraseGenerator::DefaultSeparator) { - } -double PassphraseGenerator::calculateEntropy(QString passphrase) +double PassphraseGenerator::calculateEntropy(const QString& passphrase) { Q_UNUSED(passphrase); - if (m_wordlist.size() == 0) { - return 0; + if (m_wordlist.isEmpty()) { + return 0.0; } - return log(m_wordlist.size()) / log(2.0) * m_wordCount; + return std::log2(m_wordlist.size()) * m_wordCount; } void PassphraseGenerator::setWordCount(int wordCount) @@ -56,7 +55,7 @@ void PassphraseGenerator::setWordCount(int wordCount) } -void PassphraseGenerator::setWordList(QString path) +void PassphraseGenerator::setWordList(const QString& path) { m_wordlist.clear(); @@ -83,7 +82,7 @@ void PassphraseGenerator::setDefaultWordList() setWordList(path); } -void PassphraseGenerator::setWordSeparator(QString separator) { +void PassphraseGenerator::setWordSeparator(const QString& separator) { m_separator = separator; } @@ -97,8 +96,8 @@ QString PassphraseGenerator::generatePassphrase() const } QStringList words; - for (int i = 0; i < m_wordCount; i++) { - int wordIndex = randomGen()->randomUInt(m_wordlist.length()); + for (int i = 0; i < m_wordCount; ++i) { + int wordIndex = randomGen()->randomUInt(static_cast(m_wordlist.length())); words.append(m_wordlist.at(wordIndex)); } @@ -111,9 +110,5 @@ bool PassphraseGenerator::isValid() const return false; } - if (m_wordlist.size() < 1000) { - return false; - } - - return true; + return m_wordlist.size() >= 1000; } diff --git a/src/core/PassphraseGenerator.h b/src/core/PassphraseGenerator.h index 4a489827d..a4e0e426b 100644 --- a/src/core/PassphraseGenerator.h +++ b/src/core/PassphraseGenerator.h @@ -26,26 +26,25 @@ class PassphraseGenerator { public: PassphraseGenerator(); + Q_DISABLE_COPY(PassphraseGenerator) - double calculateEntropy(QString passphrase); + double calculateEntropy(const QString& passphrase); void setWordCount(int wordCount); - void setWordList(QString path); + void setWordList(const QString& path); void setDefaultWordList(); - void setWordSeparator(QString separator); + void setWordSeparator(const QString& separator); bool isValid() const; QString generatePassphrase() const; - static const int DefaultWordCount = 7; - static const QString DefaultSeparator; - static const QString DefaultWordList; + static constexpr int DefaultWordCount = 7; + static const char* DefaultSeparator; + static const char* DefaultWordList; private: int m_wordCount; QString m_separator; QVector m_wordlist; - - Q_DISABLE_COPY(PassphraseGenerator) }; #endif // KEEPASSX_PASSPHRASEGENERATOR_H From 80d85965e922f3741a5483b10d8b3d212e3eee24 Mon Sep 17 00:00:00 2001 From: Toni Spets Date: Tue, 6 Feb 2018 19:22:21 +0200 Subject: [PATCH 20/22] SSH Agent: Fix translation arguments --- src/sshagent/OpenSSHKey.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sshagent/OpenSSHKey.cpp b/src/sshagent/OpenSSHKey.cpp index 808408ab6..08110291a 100644 --- a/src/sshagent/OpenSSHKey.cpp +++ b/src/sshagent/OpenSSHKey.cpp @@ -278,7 +278,7 @@ bool OpenSSHKey::parse(const QByteArray& in) return false; } } else { - m_error = tr("Unsupported key type: %s").arg(m_privateType); + m_error = tr("Unsupported key type: %1").arg(m_privateType); return false; } @@ -313,7 +313,7 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase) } else if (m_cipherName == "aes256-ctr") { cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt)); } else if (m_cipherName != "none") { - m_error = tr("Unknown cipher: %s").arg(m_cipherName); + m_error = tr("Unknown cipher: %1").arg(m_cipherName); return false; } @@ -356,7 +356,7 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase) return false; } } else if (m_kdfName != "none") { - m_error = tr("Unknown KDF: %s").arg(m_kdfName); + m_error = tr("Unknown KDF: %1").arg(m_kdfName); return false; } @@ -402,7 +402,7 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase) return readPrivate(keyStream); } - m_error = tr("Unsupported key type: %s").arg(m_privateType); + m_error = tr("Unsupported key type: %1").arg(m_privateType); return false; } @@ -425,7 +425,7 @@ bool OpenSSHKey::readPublic(BinaryStream& stream) } else if (m_type == "ssh-ed25519") { keyParts = 1; } else { - m_error = tr("Unknown key type: %s").arg(m_type); + m_error = tr("Unknown key type: %1").arg(m_type); return false; } @@ -462,7 +462,7 @@ bool OpenSSHKey::readPrivate(BinaryStream& stream) } else if (m_type == "ssh-ed25519") { keyParts = 2; } else { - m_error = tr("Unknown key type: %s").arg(m_type); + m_error = tr("Unknown key type: %1").arg(m_type); return false; } From d2359df2b06676530fb08923884b2f15f302aea2 Mon Sep 17 00:00:00 2001 From: Toni Spets Date: Tue, 6 Feb 2018 19:20:10 +0200 Subject: [PATCH 21/22] SymmetricCipher: Add support for AES-128-CBC --- src/crypto/SymmetricCipher.cpp | 1 + src/crypto/SymmetricCipher.h | 1 + src/crypto/SymmetricCipherGcrypt.cpp | 3 + tests/TestSymmetricCipher.cpp | 84 ++++++++++++++++++++++++++++ tests/TestSymmetricCipher.h | 2 + 5 files changed, 91 insertions(+) diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp index 1ec8a2cf6..1ba42a537 100644 --- a/src/crypto/SymmetricCipher.cpp +++ b/src/crypto/SymmetricCipher.cpp @@ -57,6 +57,7 @@ bool SymmetricCipher::isInitalized() const SymmetricCipherBackend* SymmetricCipher::createBackend(Algorithm algo, Mode mode, Direction direction) { switch (algo) { + case Aes128: case Aes256: case Twofish: case Salsa20: diff --git a/src/crypto/SymmetricCipher.h b/src/crypto/SymmetricCipher.h index eab834956..0c683d224 100644 --- a/src/crypto/SymmetricCipher.h +++ b/src/crypto/SymmetricCipher.h @@ -31,6 +31,7 @@ class SymmetricCipher public: enum Algorithm { + Aes128, Aes256, Twofish, Salsa20, diff --git a/src/crypto/SymmetricCipherGcrypt.cpp b/src/crypto/SymmetricCipherGcrypt.cpp index b1abd5250..97d53cd83 100644 --- a/src/crypto/SymmetricCipherGcrypt.cpp +++ b/src/crypto/SymmetricCipherGcrypt.cpp @@ -37,6 +37,9 @@ SymmetricCipherGcrypt::~SymmetricCipherGcrypt() int SymmetricCipherGcrypt::gcryptAlgo(SymmetricCipher::Algorithm algo) { switch (algo) { + case SymmetricCipher::Aes128: + return GCRY_CIPHER_AES128; + case SymmetricCipher::Aes256: return GCRY_CIPHER_AES256; diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index f1e7c0e06..a5159e0d6 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -32,6 +32,90 @@ void TestSymmetricCipher::initTestCase() QVERIFY(Crypto::init()); } +void TestSymmetricCipher::testAes128CbcEncryption() +{ + // http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf + + QByteArray key = QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c"); + QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); + QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); + plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); + QByteArray cipherText = QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d"); + cipherText.append(QByteArray::fromHex("5086cb9b507219ee95db113a917678b2")); + bool ok; + + SymmetricCipher cipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); + QVERIFY(cipher.init(key, iv)); + QCOMPARE(cipher.blockSize(), 16); + QCOMPARE(cipher.process(plainText, &ok), cipherText); + QVERIFY(ok); + + QBuffer buffer; + SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, + SymmetricCipher::Encrypt); + QVERIFY(stream.init(key, iv)); + buffer.open(QIODevice::WriteOnly); + QVERIFY(stream.open(QIODevice::WriteOnly)); + QVERIFY(stream.reset()); + + buffer.reset(); + buffer.buffer().clear(); + QCOMPARE(stream.write(plainText.left(16)), qint64(16)); + QCOMPARE(buffer.data(), cipherText.left(16)); + QVERIFY(stream.reset()); + // make sure padding is written + QCOMPARE(buffer.data().size(), 32); + + buffer.reset(); + buffer.buffer().clear(); + QCOMPARE(stream.write(plainText.left(10)), qint64(10)); + QVERIFY(buffer.data().isEmpty()); + + QVERIFY(stream.reset()); + buffer.reset(); + buffer.buffer().clear(); + QCOMPARE(stream.write(plainText.left(10)), qint64(10)); + stream.close(); + QCOMPARE(buffer.data().size(), 16); +} + +void TestSymmetricCipher::testAes128CbcDecryption() +{ + QByteArray key = QByteArray::fromHex("2b7e151628aed2a6abf7158809cf4f3c"); + QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); + QByteArray cipherText = QByteArray::fromHex("7649abac8119b246cee98e9b12e9197d"); + cipherText.append(QByteArray::fromHex("5086cb9b507219ee95db113a917678b2")); + QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); + plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); + bool ok; + + SymmetricCipher cipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); + QVERIFY(cipher.init(key, iv)); + QCOMPARE(cipher.blockSize(), 16); + QCOMPARE(cipher.process(cipherText, &ok), plainText); + QVERIFY(ok); + + // padded with 16 0x10 bytes + QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("55e21d7100b988ffec32feeafaf23538"); + QBuffer buffer(&cipherTextPadded); + SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes128, SymmetricCipher::Cbc, + SymmetricCipher::Decrypt); + QVERIFY(stream.init(key, iv)); + buffer.open(QIODevice::ReadOnly); + QVERIFY(stream.open(QIODevice::ReadOnly)); + + QCOMPARE(stream.read(10), plainText.left(10)); + buffer.reset(); + QVERIFY(stream.reset()); + QCOMPARE(stream.read(20), plainText.left(20)); + buffer.reset(); + QVERIFY(stream.reset()); + QCOMPARE(stream.read(16), plainText.left(16)); + buffer.reset(); + QVERIFY(stream.reset()); + QCOMPARE(stream.read(100), plainText); +} + void TestSymmetricCipher::testAes256CbcEncryption() { // http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf diff --git a/tests/TestSymmetricCipher.h b/tests/TestSymmetricCipher.h index 40e3b49cf..9b82fd88a 100644 --- a/tests/TestSymmetricCipher.h +++ b/tests/TestSymmetricCipher.h @@ -27,6 +27,8 @@ class TestSymmetricCipher : public QObject private slots: void initTestCase(); + void testAes128CbcEncryption(); + void testAes128CbcDecryption(); void testAes256CbcEncryption(); void testAes256CbcDecryption(); void testAes256CtrEncryption(); From d58e3ca34dd97707facc2e8b40da24b9cda97c15 Mon Sep 17 00:00:00 2001 From: Toni Spets Date: Tue, 6 Feb 2018 19:23:03 +0200 Subject: [PATCH 22/22] SSH Agent: Support old AES-128-CBC encrypted keys --- src/sshagent/OpenSSHKey.cpp | 30 ++++++++++++++++++----- src/sshagent/OpenSSHKey.h | 1 + tests/TestOpenSSHKey.cpp | 48 +++++++++++++++++++++++++++++++++++++ tests/TestOpenSSHKey.h | 1 + 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/sshagent/OpenSSHKey.cpp b/src/sshagent/OpenSSHKey.cpp index 08110291a..ce867a95f 100644 --- a/src/sshagent/OpenSSHKey.cpp +++ b/src/sshagent/OpenSSHKey.cpp @@ -204,9 +204,10 @@ bool OpenSSHKey::parsePEM(const QByteArray& in, QByteArray& out) rows.removeFirst(); } while (!rows.isEmpty()); - if (pemOptions.contains("Proc-Type")) { - m_error = tr("Encrypted keys are not yet supported"); - return false; + if (pemOptions.value("Proc-Type").compare("4,encrypted", Qt::CaseInsensitive) == 0) { + m_kdfName = "md5"; + m_cipherName = pemOptions.value("DEK-Info").section(",", 0, 0); + m_cipherIV = QByteArray::fromHex(pemOptions.value("DEK-Info").section(",", 1, 1).toLatin1()); } out = QByteArray::fromBase64(rows.join("").toLatin1()); @@ -308,7 +309,9 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase) return false; } - if (m_cipherName == "aes256-cbc") { + if (m_cipherName.compare("aes-128-cbc", Qt::CaseInsensitive) == 0) { + cipher.reset(new SymmetricCipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt)); + } else if (m_cipherName == "aes256-cbc") { cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt)); } else if (m_cipherName == "aes256-ctr") { cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt)); @@ -355,6 +358,21 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase) m_error = cipher->errorString(); return false; } + } else if (m_kdfName == "md5") { + if (m_cipherIV.length() < 8) { + m_error = tr("Cipher IV is too short for MD5 kdf"); + return false; + } + + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(passphrase.toUtf8()); + hash.addData(m_cipherIV.data(), 8); + QByteArray keyData = hash.result(); + + if (!cipher->init(keyData, m_cipherIV)) { + m_error = cipher->errorString(); + return false; + } } else if (m_kdfName != "none") { m_error = tr("Unknown KDF: %1").arg(m_kdfName); return false; @@ -373,14 +391,14 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase) if (m_privateType == TYPE_DSA) { if (!ASN1Key::parseDSA(rawPrivateData, *this)) { - m_error = tr("Reading DSA private key failed, only unencrypted keys are supported at this time"); + m_error = tr("Decryption failed, wrong passphrase?"); return false; } return true; } else if (m_privateType == TYPE_RSA) { if (!ASN1Key::parseRSA(rawPrivateData, *this)) { - m_error = tr("Reading RSA private key failed, only unencrypted keys are supported at this time"); + m_error = tr("Decryption failed, wrong passphrase?"); return false; } diff --git a/src/sshagent/OpenSSHKey.h b/src/sshagent/OpenSSHKey.h index 539d01892..e06af2201 100644 --- a/src/sshagent/OpenSSHKey.h +++ b/src/sshagent/OpenSSHKey.h @@ -63,6 +63,7 @@ private: QString m_type; QString m_cipherName; + QByteArray m_cipherIV; QString m_kdfName; QByteArray m_kdfOptions; QByteArray m_rawPrivateData; diff --git a/tests/TestOpenSSHKey.cpp b/tests/TestOpenSSHKey.cpp index 7f94365e7..8ac129866 100644 --- a/tests/TestOpenSSHKey.cpp +++ b/tests/TestOpenSSHKey.cpp @@ -90,6 +90,54 @@ void TestOpenSSHKey::testParseDSA() QCOMPARE(key.fingerprint(), QString("SHA256:tbbNuLN1hja8JNASDTlLOZQsbTlJDzJlz/oAGK3sX18")); } +void TestOpenSSHKey::testDecryptAES128CBC() +{ + const QString keyString = QString( + "-----BEGIN RSA PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,804E4D214D1263FF94E3743FE799DBB4\n" + "\n" + "lM9TDfOTbiRhaGGDh7Hn+rqw8CCWcYBZYu7smyYLdnWKXKPmbne8CQFZBAS1FJwZ\n" + "6Mj6n075yFGyzN9/OfeqKiUA4adlbwLbGwB+yyKsC2FlsvRIEr4hup02WWM47vHj\n" + "DS4TRmNkE7MKFLhpNCyt5OGGM45s+/lwVTw51K0Hm99TBd72IrX4jfY9ZxAVbL3l\n" + "aTohL8x6oOTe7q318QgJoFi+DjJhDWLGLLJ7fBqD2imz2fmrY4j8Jpw2sDe1rj82\n" + "gMqqNG3FrfN0S4uYlWYH5pAh+BUcB1UdmTU/rV5wJMK1oUytmZv/J2+X/0k3Y93F\n" + "aw6JWOy28OizW+TQXvv8gREWsp5PEclqUZhhGQbVbCQCiDOxg+xiXNySdRH1IqjR\n" + "zQiKgD4SPzkxQekExPaIQT/KutWZdMNYybEqooCx8YyeDoN31z7Wa2rv6OulOn/j\n" + "wJFvyd2PT/6brHKI4ky8RYroDf4FbVYKfyEW5CSAg2OyL/tY/kSPgy/k0WT7fDwq\n" + "dPSuYM9yeWNL6kAhDqDOv8+s3xvOVEljktBvQvItQwVLmHszC3E2AcnaxzdblKPu\n" + "e3+mBT80NXHjERK2ht+/9JYseK1ujNbNAaG8SbKfU3FF0VlyJ0QW6TuIEdpNnymT\n" + "0fm0cDfKNaoeJIFnBRZhgIOJAic9DM0cTe/vSG69DaUYsaQPp36al7Fbux3GpFHS\n" + "OtJEySYGro/6zvJ9dDIEfIGZjA3RaMt6+DuyJZXQdT2RNXa9j60xW7dXh0En4n82\n" + "JUKTxYhDPLS5c8BzpJqoopxpKwElmrJ7Y3xpd6z2vIlD8ftuZrkk6siTMNQ2s7MI\n" + "Xl332O+0H4k7uSfczHPOOw36TFhNjGQAP0b7O+0/RVG0ttOIoAn7ZkX3nfdbtG5B\n" + "DWKvDaopvrcC2/scQ5uLUnqnBiGw1XiYpdg5ang7knHNzHZAIekVaYYZigpCAKp+\n" + "OtoaDeUEzqFhYVmF8ad1fgvC9ZUsuxS4XUHCKl0H6CJcvW9MJPVbveqYoK+j9qKd\n" + "iMIkQBP1kE2rzGZVGUkZTpM9LVD9nP0nsbr6E8BatFcNgRirsg2BTJglNpXlCmY6\n" + "ldzJ/ELBbzoXIn+0wTGai0o4eBPx55baef69JfPuZqEB9pLNE+mHstrqIwcfqYu4\n" + "M+Vzun1QshRMj9a1PVkIHfs1fLeebI4QCHO0vJlc9K4iYPM4rsDNO3YaAgGRuARS\n" + "f3McGiGFxkv5zxe8i05ZBnn+exE77jpRKxd223jAMe2wu4WiFB7ZVo4Db6b5Oo2T\n" + "TPh3VuY7TNMEKkcUi+mGLKjroocQ5j8WQYlfnyOaTalUVQDzOTNb67QIIoiszR0U\n" + "+AXGyxHj0QtotZFoPME+AbS9Zqy3SgSOuIzPBPU5zS4uoKNdD5NPE5YAuafCjsDy\n" + "MT4DVy+cPOQYUK022S7T2nsA1btmvUvD5LL2Mc8VuKsWOn/7FKZua6OCfipt6oX0\n" + "1tzYrw0/ALK+CIdVdYIiPPfxGZkr+JSLOOg7u50tpmen9GzxgNTv63miygwUAIDF\n" + "u0GbQwOueoA453/N75FcXOgrbqTdivyadUbRP+l7YJk/SfIytyJMOigejp+Z1lzF\n" + "-----END RSA PRIVATE KEY-----\n" + ); + + const QByteArray keyData = keyString.toLatin1(); + + OpenSSHKey key; + QVERIFY(key.parse(keyData)); + QVERIFY(key.encrypted()); + QCOMPARE(key.cipherName(), QString("AES-128-CBC")); + QVERIFY(!key.openPrivateKey("incorrectpassphrase")); + QVERIFY(key.openPrivateKey("correctpassphrase")); + QCOMPARE(key.type(), QString("ssh-rsa")); + QCOMPARE(key.comment(), QString("")); + QCOMPARE(key.fingerprint(), QString("SHA256:1Hsebt2WWnmc72FERsUOgvaajIGHkrMONxXylcmk87U")); +} + void TestOpenSSHKey::testParseRSA() { const QString keyString = QString( diff --git a/tests/TestOpenSSHKey.h b/tests/TestOpenSSHKey.h index 6c2041074..5d2724410 100644 --- a/tests/TestOpenSSHKey.h +++ b/tests/TestOpenSSHKey.h @@ -31,6 +31,7 @@ private slots: void testParse(); void testParseDSA(); void testParseRSA(); + void testDecryptAES128CBC(); void testDecryptAES256CBC(); void testDecryptAES256CTR(); };