mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Merge branch 'release/2.3.0' into develop
This commit is contained in:
commit
b80ddef274
47
.travis.yml
47
.travis.yml
@ -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'"
|
@ -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"
|
||||
|
@ -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 \
|
||||
|
@ -42,6 +42,7 @@ parts:
|
||||
- libxtst-dev
|
||||
- libyubikey-dev
|
||||
- libykpers-1-dev
|
||||
- libcurl4-openssl-dev
|
||||
- libsodium-dev
|
||||
stage-packages:
|
||||
- dbus
|
||||
|
@ -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
|
||||
@ -196,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)
|
||||
@ -248,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}
|
||||
|
@ -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"
|
||||
@ -40,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<Qt::Key>(0))
|
||||
, m_currentGlobalModifiers(0)
|
||||
@ -102,6 +102,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) {
|
||||
@ -127,32 +140,76 @@ QStringList AutoType::windowTitles()
|
||||
return m_plugin->windowTitles();
|
||||
}
|
||||
|
||||
void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& customSequence, WId window)
|
||||
void AutoType::resetInAutoType()
|
||||
{
|
||||
if (m_inAutoType || !m_plugin) {
|
||||
m_inAutoType.unlock();
|
||||
|
||||
emit autotypeRejected();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
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
|
||||
*/
|
||||
void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window)
|
||||
{
|
||||
// no edit to the sequence beyond this point
|
||||
if (!verifyAutoTypeSyntax(sequence)) {
|
||||
emit autotypeRejected();
|
||||
return;
|
||||
}
|
||||
m_inAutoType = true;
|
||||
|
||||
QString sequence;
|
||||
if (customSequence.isEmpty()) {
|
||||
sequence = autoTypeSequence(entry);
|
||||
} else {
|
||||
sequence = customSequence;
|
||||
}
|
||||
|
||||
if (!checkSyntax(sequence)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sequence.replace("{{}", "{LEFTBRACE}");
|
||||
sequence.replace("{}}", "{RIGHTBRACE}");
|
||||
|
||||
QList<AutoTypeAction*> actions;
|
||||
ListDeleter<AutoTypeAction*> actionsDeleter(&actions);
|
||||
|
||||
if (!parseActions(sequence, entry, actions)) {
|
||||
m_inAutoType = false; // TODO: make this automatic
|
||||
emit autotypeRejected();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -175,19 +232,49 @@ 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);
|
||||
}
|
||||
|
||||
m_inAutoType = false;
|
||||
// emit signal only if autotype performed correctly
|
||||
emit autotypePerformed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Single Autotype entry-point function
|
||||
* Perfom autotype sequence in the active window
|
||||
*/
|
||||
void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow)
|
||||
{
|
||||
if (!m_plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<QString> sequences = autoTypeSequences(entry);
|
||||
if (sequences.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_inAutoType.tryLock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
executeAutoTypeActions(entry, hideWindow, sequences.first());
|
||||
|
||||
m_inAutoType.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Global Autotype entry-point funcion
|
||||
* Perform global autotype on the active window
|
||||
*/
|
||||
void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
|
||||
{
|
||||
if (m_inAutoType || !m_plugin) {
|
||||
if (!m_plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -197,38 +284,42 @@ void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
|
||||
return;
|
||||
}
|
||||
|
||||
m_inAutoType = true;
|
||||
if (!m_inAutoType.tryLock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Entry*> entryList;
|
||||
QHash<Entry*, QString> sequenceHash;
|
||||
QList<AutoTypeMatch> matchList;
|
||||
|
||||
for (Database* db : dbList) {
|
||||
const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive();
|
||||
for (Entry* entry : dbEntries) {
|
||||
QString sequence = autoTypeSequence(entry, windowTitle);
|
||||
const QSet<QString> sequences = autoTypeSequences(entry, windowTitle).toSet();
|
||||
for (const QString& sequence : sequences) {
|
||||
if (!sequence.isEmpty()) {
|
||||
entryList << entry;
|
||||
sequenceHash.insert(entry, sequence);
|
||||
matchList << AutoTypeMatch(entry, sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entryList.isEmpty()) {
|
||||
m_inAutoType = false;
|
||||
if (matchList.isEmpty()) {
|
||||
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 ((entryList.size() == 1) && !config()->get("security/autotypeask").toBool()) {
|
||||
m_inAutoType = false;
|
||||
performAutoType(entryList.first(), nullptr, sequenceHash[entryList.first()]);
|
||||
|
||||
emit autotypeRejected();
|
||||
} else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) {
|
||||
executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence);
|
||||
m_inAutoType.unlock();
|
||||
} 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,92 +330,31 @@ void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
|
||||
}
|
||||
}
|
||||
|
||||
void AutoType::performAutoTypeFromGlobal(Entry* entry, const QString& sequence)
|
||||
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);
|
||||
|
||||
m_inAutoType = false;
|
||||
executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal);
|
||||
|
||||
performAutoType(entry, nullptr, sequence, m_windowFromGlobal);
|
||||
m_inAutoType.unlock();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool AutoType::parseActions(const QString& sequence, const Entry* entry, QList<AutoTypeAction*>& actions)
|
||||
/**
|
||||
* Parse an autotype sequence and resolve its Template/command inside as AutoTypeActions
|
||||
*/
|
||||
bool AutoType::parseActions(const QString& actionSequence, const Entry* entry, QList<AutoTypeAction*>& 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 == '{') {
|
||||
@ -363,6 +393,9 @@ bool AutoType::parseActions(const QString& sequence, const Entry* entry, QList<A
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an autotype Template/command to its AutoTypeAction that will be executed by the plugin executor
|
||||
*/
|
||||
QList<AutoTypeAction*> AutoType::createActionFromTemplate(const QString& tmpl, const Entry* entry)
|
||||
{
|
||||
QString tmplName = tmpl;
|
||||
@ -506,80 +539,65 @@ QList<AutoTypeAction*> AutoType::createActionFromTemplate(const QString& tmpl, c
|
||||
return list;
|
||||
}
|
||||
|
||||
QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitle)
|
||||
/**
|
||||
* 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<QString> AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle)
|
||||
{
|
||||
QList<QString> sequenceList;
|
||||
|
||||
if (!entry->autoTypeEnabled()) {
|
||||
return QString();
|
||||
return sequenceList;
|
||||
}
|
||||
|
||||
bool enableSet = false;
|
||||
QString sequence;
|
||||
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()) {
|
||||
bool match = false;
|
||||
const QList<AutoTypeAssociations::Association> 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 (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}";
|
||||
}
|
||||
}
|
||||
|
||||
return sequence;
|
||||
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) {
|
||||
@ -590,11 +608,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)) {
|
||||
@ -609,6 +635,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+)?";
|
||||
@ -634,6 +663,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
|
||||
@ -642,6 +674,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
|
||||
@ -650,6 +685,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
|
||||
@ -658,6 +696,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)) {
|
||||
@ -691,12 +732,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);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
@ -22,6 +22,9 @@
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QWidget>
|
||||
#include <QMutex>
|
||||
|
||||
#include "core/AutoTypeMatch.h"
|
||||
|
||||
class AutoTypeAction;
|
||||
class AutoTypeExecutor;
|
||||
@ -36,10 +39,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);
|
||||
@ -49,9 +48,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()
|
||||
{
|
||||
@ -67,9 +64,11 @@ public slots:
|
||||
|
||||
signals:
|
||||
void globalShortcutTriggered();
|
||||
void autotypePerformed();
|
||||
void autotypeRejected();
|
||||
|
||||
private slots:
|
||||
void performAutoTypeFromGlobal(Entry* entry, const QString& sequence);
|
||||
void performAutoTypeFromGlobal(AutoTypeMatch match);
|
||||
void resetInAutoType();
|
||||
void unloadPlugin();
|
||||
|
||||
@ -77,14 +76,18 @@ 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<AutoTypeAction*>& actions);
|
||||
QList<AutoTypeAction*> createActionFromTemplate(const QString& tmpl, const Entry* entry);
|
||||
QString autoTypeSequence(const Entry* entry, const QString& windowTitle = QString());
|
||||
QList<QString> 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);
|
||||
|
||||
bool m_inAutoType;
|
||||
QMutex m_inAutoType;
|
||||
int m_autoTypeDelay;
|
||||
Qt::Key m_currentGlobalKey;
|
||||
Qt::KeyboardModifiers m_currentGlobalModifiers;
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <QChar>
|
||||
#include <Qt>
|
||||
#include <QObject>
|
||||
|
||||
#include "core/Global.h"
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -25,14 +26,16 @@
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#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)
|
||||
, m_rejected(false)
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
// Places the window on the active (virtual) desktop instead of where the main window is.
|
||||
@ -42,7 +45,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 +59,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 +70,9 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
|
||||
layout->addWidget(buttonBox);
|
||||
}
|
||||
|
||||
void AutoTypeSelectDialog::setEntries(const QList<Entry*>& entries, const QHash<Entry*, QString>& sequences)
|
||||
void AutoTypeSelectDialog::setMatchList(const QList<AutoTypeMatch>& matchList)
|
||||
{
|
||||
m_sequences = sequences;
|
||||
m_view->setEntryList(entries);
|
||||
m_view->setMatchList(matchList);
|
||||
|
||||
m_view->header()->resizeSections(QHeaderView::ResizeToContents);
|
||||
}
|
||||
@ -82,21 +84,32 @@ void AutoTypeSelectDialog::done(int r)
|
||||
QDialog::done(r);
|
||||
}
|
||||
|
||||
void AutoTypeSelectDialog::emitEntryActivated(const QModelIndex& index)
|
||||
void AutoTypeSelectDialog::reject()
|
||||
{
|
||||
// make sure we don't emit the signal twice when both activated() and clicked() are triggered
|
||||
if (m_entryActivatedEmitted) {
|
||||
return;
|
||||
}
|
||||
m_entryActivatedEmitted = true;
|
||||
m_rejected = true;
|
||||
|
||||
Entry* entry = m_view->entryFromIndex(index);
|
||||
accept();
|
||||
emit entryActivated(entry, m_sequences[entry]);
|
||||
QDialog::reject();
|
||||
}
|
||||
|
||||
void AutoTypeSelectDialog::entryRemoved()
|
||||
void AutoTypeSelectDialog::emitMatchActivated(const QModelIndex& index)
|
||||
{
|
||||
// make sure we don't emit the signal twice when both activated() and clicked() are triggered
|
||||
if (m_matchActivatedEmitted) {
|
||||
return;
|
||||
}
|
||||
m_matchActivatedEmitted = true;
|
||||
|
||||
AutoTypeMatch match = m_view->matchFromIndex(index);
|
||||
accept();
|
||||
emit matchActivated(match);
|
||||
}
|
||||
|
||||
void AutoTypeSelectDialog::matchRemoved()
|
||||
{
|
||||
if (m_rejected) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_view->model()->rowCount() == 0) {
|
||||
reject();
|
||||
}
|
||||
|
@ -22,8 +22,9 @@
|
||||
#include <QDialog>
|
||||
#include <QHash>
|
||||
|
||||
#include "core/AutoTypeMatch.h"
|
||||
|
||||
class AutoTypeSelectView;
|
||||
class Entry;
|
||||
|
||||
class AutoTypeSelectDialog : public QDialog
|
||||
{
|
||||
@ -31,22 +32,23 @@ class AutoTypeSelectDialog : public QDialog
|
||||
|
||||
public:
|
||||
explicit AutoTypeSelectDialog(QWidget* parent = nullptr);
|
||||
void setEntries(const QList<Entry*>& entries, const QHash<Entry*, QString>& sequences);
|
||||
void setMatchList(const QList<AutoTypeMatch>& matchList);
|
||||
|
||||
signals:
|
||||
void entryActivated(Entry* entry, const QString& sequence);
|
||||
void matchActivated(AutoTypeMatch match);
|
||||
|
||||
public slots:
|
||||
void done(int r) override;
|
||||
void reject() override;
|
||||
|
||||
private slots:
|
||||
void emitEntryActivated(const QModelIndex& index);
|
||||
void entryRemoved();
|
||||
void emitMatchActivated(const QModelIndex& index);
|
||||
void matchRemoved();
|
||||
|
||||
private:
|
||||
AutoTypeSelectView* const m_view;
|
||||
QHash<Entry*, QString> m_sequences;
|
||||
bool m_entryActivatedEmitted;
|
||||
bool m_matchActivatedEmitted;
|
||||
bool m_rejected;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_AUTOTYPESELECTDIALOG_H
|
||||
|
@ -21,15 +21,12 @@
|
||||
#include <QMouseEvent>
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
@ -133,8 +133,8 @@ int Add::execute(QStringList arguments)
|
||||
passwordGenerator.setLength(passwordLength.toInt());
|
||||
}
|
||||
|
||||
passwordGenerator.setCharClasses(PasswordGenerator::LowerLetters | PasswordGenerator::UpperLetters |
|
||||
PasswordGenerator::Numbers);
|
||||
passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset);
|
||||
passwordGenerator.setFlags(PasswordGenerator::DefaultFlags);
|
||||
QString password = passwordGenerator.generatePassword();
|
||||
entry->setPassword(password);
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class Add : public Command
|
||||
public:
|
||||
Add();
|
||||
~Add();
|
||||
int execute(QStringList arguments);
|
||||
int execute(const QStringList& arguments);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_ADD_H
|
||||
|
@ -20,12 +20,16 @@ set(cli_SOURCES
|
||||
Clip.h
|
||||
Command.cpp
|
||||
Command.h
|
||||
Diceware.cpp
|
||||
Diceware.h
|
||||
Edit.cpp
|
||||
Edit.h
|
||||
Estimate.cpp
|
||||
Estimate.h
|
||||
Extract.cpp
|
||||
Extract.h
|
||||
Generate.cpp
|
||||
Generate.h
|
||||
List.cpp
|
||||
List.h
|
||||
Locate.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);
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -24,9 +24,11 @@
|
||||
|
||||
#include "Add.h"
|
||||
#include "Clip.h"
|
||||
#include "Diceware.h"
|
||||
#include "Edit.h"
|
||||
#include "Estimate.h"
|
||||
#include "Extract.h"
|
||||
#include "Generate.h"
|
||||
#include "List.h"
|
||||
#include "Locate.h"
|
||||
#include "Merge.h"
|
||||
@ -39,7 +41,7 @@ Command::~Command()
|
||||
{
|
||||
}
|
||||
|
||||
int Command::execute(QStringList)
|
||||
int Command::execute(const QStringList&)
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
@ -61,9 +63,11 @@ 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("generate"), new Generate());
|
||||
commands.insert(QString("locate"), new Locate());
|
||||
commands.insert(QString("ls"), new List());
|
||||
commands.insert(QString("merge"), new Merge());
|
||||
|
@ -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();
|
||||
|
86
src/cli/Diceware.cpp
Normal file
86
src/cli/Diceware.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Diceware.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "core/PassphraseGenerator.h"
|
||||
|
||||
Diceware::Diceware()
|
||||
{
|
||||
name = QString("diceware");
|
||||
description = QObject::tr("Generate a new random diceware passphrase.");
|
||||
}
|
||||
|
||||
Diceware::~Diceware()
|
||||
{
|
||||
}
|
||||
|
||||
int Diceware::execute(const QStringList& arguments)
|
||||
{
|
||||
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
|
||||
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 for the diceware generator.\n[Default: EFF English]"),
|
||||
QObject::tr("path"));
|
||||
parser.addOption(wordlistFile);
|
||||
parser.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (!args.isEmpty()) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
PassphraseGenerator dicewareGenerator;
|
||||
|
||||
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));
|
||||
} else {
|
||||
dicewareGenerator.setDefaultWordList();
|
||||
}
|
||||
|
||||
if (!dicewareGenerator.isValid()) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli diceware");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QString password = dicewareGenerator.generatePassphrase();
|
||||
outputTextStream << password << endl;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
31
src/cli/Diceware.h
Normal file
31
src/cli/Diceware.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_DICEWARE_H
|
||||
#define KEEPASSXC_DICEWARE_H
|
||||
|
||||
#include "Command.h"
|
||||
|
||||
class Diceware : public Command
|
||||
{
|
||||
public:
|
||||
Diceware();
|
||||
~Diceware();
|
||||
int execute(const QStringList& arguments);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_DICEWARE_H
|
@ -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);
|
||||
@ -149,8 +149,8 @@ int Edit::execute(QStringList arguments)
|
||||
passwordGenerator.setLength(passwordLength.toInt());
|
||||
}
|
||||
|
||||
passwordGenerator.setCharClasses(PasswordGenerator::LowerLetters | PasswordGenerator::UpperLetters |
|
||||
PasswordGenerator::Numbers);
|
||||
passwordGenerator.setCharClasses(PasswordGenerator::DefaultCharset);
|
||||
passwordGenerator.setFlags(PasswordGenerator::DefaultFlags);
|
||||
QString password = passwordGenerator.generatePassword();
|
||||
entry->setPassword(password);
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class Edit : public Command
|
||||
public:
|
||||
Edit();
|
||||
~Edit();
|
||||
int execute(QStringList arguments);
|
||||
int execute(const QStringList& arguments);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_EDIT_H
|
||||
|
@ -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);
|
||||
|
@ -25,7 +25,7 @@ class Estimate : public Command
|
||||
public:
|
||||
Estimate();
|
||||
~Estimate();
|
||||
int execute(QStringList arguments);
|
||||
int execute(const QStringList& arguments);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_ESTIMATE_H
|
||||
|
@ -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);
|
||||
|
@ -25,7 +25,7 @@ class Extract : public Command
|
||||
public:
|
||||
Extract();
|
||||
~Extract();
|
||||
int execute(QStringList arguments);
|
||||
int execute(const QStringList& arguments);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_EXTRACT_H
|
||||
|
111
src/cli/Generate.cpp
Normal file
111
src/cli/Generate.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "Generate.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "core/PasswordGenerator.h"
|
||||
|
||||
Generate::Generate()
|
||||
{
|
||||
name = QString("generate");
|
||||
description = QObject::tr("Generate a new random password.");
|
||||
}
|
||||
|
||||
Generate::~Generate()
|
||||
{
|
||||
}
|
||||
|
||||
int Generate::execute(const QStringList& arguments)
|
||||
{
|
||||
QTextStream inputTextStream(stdin, QIODevice::ReadOnly);
|
||||
QTextStream outputTextStream(stdout, QIODevice::WriteOnly);
|
||||
|
||||
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 characters in the generated password."));
|
||||
parser.addOption(lower);
|
||||
QCommandLineOption upper(QStringList() << "u",
|
||||
QObject::tr("Use uppercase characters 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.process(arguments);
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if (!args.isEmpty()) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
PasswordGenerator passwordGenerator;
|
||||
|
||||
if (parser.value(len).isEmpty()) {
|
||||
passwordGenerator.setLength(PasswordGenerator::DefaultLength);
|
||||
} else {
|
||||
int length = parser.value(len).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;
|
||||
}
|
||||
|
||||
passwordGenerator.setCharClasses(classes);
|
||||
passwordGenerator.setFlags(PasswordGenerator::DefaultFlags);
|
||||
|
||||
if (!passwordGenerator.isValid()) {
|
||||
outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QString password = passwordGenerator.generatePassword();
|
||||
outputTextStream << password << endl;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
31
src/cli/Generate.h
Normal file
31
src/cli/Generate.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_GENERATE_H
|
||||
#define KEEPASSXC_GENERATE_H
|
||||
|
||||
#include "Command.h"
|
||||
|
||||
class Generate : public Command
|
||||
{
|
||||
public:
|
||||
Generate();
|
||||
~Generate();
|
||||
int execute(const QStringList& arguments);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_GENERATE_H
|
@ -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);
|
||||
|
||||
|
@ -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(""));
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -25,7 +25,7 @@ class Merge : public Command
|
||||
public:
|
||||
Merge();
|
||||
~Merge();
|
||||
int execute(QStringList arguments);
|
||||
int execute(const QStringList& arguments);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_MERGE_H
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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 = "";
|
||||
|
@ -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
|
||||
|
@ -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] <database> <entry> [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] <database> <entry>"
|
||||
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).
|
||||
|
||||
@ -28,6 +31,9 @@ Estimates the entropy of a password. The password to estimate can be provided as
|
||||
.IP "extract [options] <database>"
|
||||
Extracts and prints the contents of a database to standard output in XML format.
|
||||
|
||||
.IP "generate [options]"
|
||||
Generate a random password.
|
||||
|
||||
.IP "locate [options] <database> <term>"
|
||||
Locates all the entries that match a specific search term in a database.
|
||||
|
||||
@ -104,6 +110,39 @@ 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, --words <count>"
|
||||
Desired number of words for the generated passphrase. [Default: 7]
|
||||
|
||||
.IP "-w, --word-list <path>"
|
||||
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 "Generate options"
|
||||
|
||||
.IP "-L, --length <length>"
|
||||
Desired length for the generated password. [Default: 16]
|
||||
|
||||
.IP "-l"
|
||||
Use lowercase characters for the generated password. [Default: Enabled]
|
||||
|
||||
.IP "-u"
|
||||
Use uppercase characters for the generated password. [Default: Enabled]
|
||||
|
||||
.IP "-n"
|
||||
Use numbers characters for the generated password. [Default: Enabled]
|
||||
|
||||
.IP "-s"
|
||||
Use special characters for the generated password. [Default: Disabled]
|
||||
|
||||
.IP "-e"
|
||||
Use extended ASCII characters for the generated password. [Default: Disabled]
|
||||
|
||||
|
||||
|
||||
.SH REPORTING BUGS
|
||||
Bugs and feature requests can be reported on GitHub at https://github.com/keepassxreboot/keepassxc/issues.
|
||||
|
||||
|
39
src/core/AutoTypeMatch.cpp
Normal file
39
src/core/AutoTypeMatch.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "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;
|
||||
}
|
41
src/core/AutoTypeMatch.h
Normal file
41
src/core/AutoTypeMatch.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_AUTOTYPEMATCH_H
|
||||
#define KEEPASSX_AUTOTYPEMATCH_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
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
|
@ -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()
|
||||
@ -218,30 +220,37 @@ 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()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const Group* parent = group();
|
||||
if (!parent) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QString sequence = parent->effectiveAutoTypeSequence();
|
||||
if (sequence.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
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 (sequence == Group::RootAutoTypeSequence && (!username().isEmpty() || !password().isEmpty())) {
|
||||
if (username().isEmpty()) {
|
||||
sequence = "{PASSWORD}{ENTER}";
|
||||
}
|
||||
else if (password().isEmpty()) {
|
||||
sequence = "{USERNAME}{ENTER}";
|
||||
}
|
||||
else {
|
||||
sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}";
|
||||
return AutoTypeSequencePassword;
|
||||
} else if (password().isEmpty()) {
|
||||
return AutoTypeSequenceUsername;
|
||||
}
|
||||
return Group::RootAutoTypeSequence;
|
||||
}
|
||||
|
||||
return sequence;
|
||||
|
@ -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);
|
||||
|
@ -91,6 +91,11 @@ QString FilePath::pluginPath(const QString& name)
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString FilePath::wordlistPath(const QString& name)
|
||||
{
|
||||
return dataPath("wordlists/" + name);
|
||||
}
|
||||
|
||||
QIcon FilePath::applicationIcon()
|
||||
{
|
||||
bool darkIcon = useDarkIcon();
|
||||
|
@ -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();
|
||||
|
@ -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::CloneFlags>(
|
||||
Group::CloneNewUuid | Group::CloneResetTimeInfo | Group::CloneIncludeEntries);
|
||||
@ -192,6 +193,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;
|
||||
@ -207,7 +212,7 @@ QString Group::effectiveAutoTypeSequence() const
|
||||
} while (group && sequence.isEmpty());
|
||||
|
||||
if (sequence.isEmpty()) {
|
||||
sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}";
|
||||
sequence = RootAutoTypeSequence;
|
||||
}
|
||||
|
||||
return sequence;
|
||||
|
@ -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);
|
||||
|
@ -17,29 +17,31 @@
|
||||
|
||||
#include "PassphraseGenerator.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <cmath>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "crypto/Random.h"
|
||||
#include "core/FilePath.h"
|
||||
|
||||
const char* PassphraseGenerator::DefaultSeparator = " ";
|
||||
const char* PassphraseGenerator::DefaultWordList = "eff_large.wordlist";
|
||||
|
||||
PassphraseGenerator::PassphraseGenerator()
|
||||
: m_wordCount(0)
|
||||
, m_separator(' ')
|
||||
, 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)
|
||||
@ -48,12 +50,12 @@ void PassphraseGenerator::setWordCount(int wordCount)
|
||||
m_wordCount = wordCount;
|
||||
} else {
|
||||
// safe default if something goes wrong
|
||||
m_wordCount = 7;
|
||||
m_wordCount = DefaultWordCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PassphraseGenerator::setWordList(QString path)
|
||||
void PassphraseGenerator::setWordList(const QString& path)
|
||||
{
|
||||
m_wordlist.clear();
|
||||
|
||||
@ -76,11 +78,11 @@ 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);
|
||||
}
|
||||
|
||||
void PassphraseGenerator::setWordSeparator(QString separator) {
|
||||
void PassphraseGenerator::setWordSeparator(const QString& separator) {
|
||||
m_separator = separator;
|
||||
}
|
||||
|
||||
@ -94,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<quint32>(m_wordlist.length()));
|
||||
words.append(m_wordlist.at(wordIndex));
|
||||
}
|
||||
|
||||
@ -108,9 +110,5 @@ bool PassphraseGenerator::isValid() const
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_wordlist.size() < 1000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return m_wordlist.size() >= 1000;
|
||||
}
|
||||
|
@ -26,22 +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 constexpr int DefaultWordCount = 7;
|
||||
static const char* DefaultSeparator;
|
||||
static const char* DefaultWordList;
|
||||
|
||||
private:
|
||||
int m_wordCount;
|
||||
QString m_separator;
|
||||
QVector<QString> m_wordlist;
|
||||
|
||||
Q_DISABLE_COPY(PassphraseGenerator)
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_PASSPHRASEGENERATOR_H
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -34,14 +34,16 @@ public:
|
||||
UpperLetters = 0x2,
|
||||
Numbers = 0x4,
|
||||
SpecialCharacters = 0x8,
|
||||
EASCII = 0x10
|
||||
EASCII = 0x10,
|
||||
DefaultCharset = LowerLetters | UpperLetters | Numbers
|
||||
};
|
||||
Q_DECLARE_FLAGS(CharClasses, CharClass)
|
||||
|
||||
enum GeneratorFlag
|
||||
{
|
||||
ExcludeLookAlike = 0x1,
|
||||
CharFromEveryGroup = 0x2
|
||||
CharFromEveryGroup = 0x2,
|
||||
DefaultFlags = ExcludeLookAlike | CharFromEveryGroup
|
||||
};
|
||||
Q_DECLARE_FLAGS(GeneratorFlags, GeneratorFlag)
|
||||
|
||||
@ -59,6 +61,13 @@ public:
|
||||
int getbits() const;
|
||||
|
||||
static const int DefaultLength = 16;
|
||||
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<PasswordGroup> passwordGroups() const;
|
||||
|
@ -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:
|
||||
|
@ -31,6 +31,7 @@ class SymmetricCipher
|
||||
public:
|
||||
enum Algorithm
|
||||
{
|
||||
Aes128,
|
||||
Aes256,
|
||||
Twofish,
|
||||
Salsa20,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<Database*>(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();
|
||||
}
|
||||
}
|
||||
|
@ -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<Database*, DatabaseManagerStruct> m_dbList;
|
||||
QPointer<DatabaseWidgetStateSync> m_dbWidgetStateSync;
|
||||
QPointer<DatabaseWidget> m_dbPendingLock;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_DATABASETABWIDGET_H
|
||||
|
@ -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 <curl/curl.h>
|
||||
#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<QByteArray*>(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;
|
||||
}
|
||||
|
||||
response->collectData();
|
||||
response->onEnd([this, response, &url]() {
|
||||
int status = response->status();
|
||||
if (200 == status) {
|
||||
QImage EditWidgetIcons::fetchFavicon(const QUrl& url)
|
||||
{
|
||||
QImage image;
|
||||
image.loadFromData(response->collectedData());
|
||||
CURL* curl = curl_easy_init();
|
||||
if (curl) {
|
||||
QByteArray imagedata;
|
||||
QByteArray baUrl = url.url().toLatin1();
|
||||
|
||||
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());
|
||||
}
|
||||
});
|
||||
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);
|
||||
|
||||
// Perform the request in another thread
|
||||
CURLcode result = AsyncTask::runAndWaitForFuture([curl]() {
|
||||
return curl_easy_perform(curl);
|
||||
});
|
||||
|
||||
if (!requestMade) {
|
||||
resetFaviconDownload();
|
||||
return;
|
||||
if (result == CURLE_OK) {
|
||||
image.loadFromData(imagedata);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
|
@ -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)
|
||||
};
|
||||
|
@ -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<OptionDialog*>(widget)->loadSettings();
|
||||
}
|
||||
|
||||
void saveSettings(QWidget * widget) override
|
||||
void saveSettings(QWidget* widget) override
|
||||
{
|
||||
qobject_cast<OptionDialog*>(widget)->saveSettings();
|
||||
if (HttpSettings::isEnabled())
|
||||
@ -108,7 +108,7 @@ public:
|
||||
m_service->stop();
|
||||
}
|
||||
private:
|
||||
Service *m_service;
|
||||
Service* m_service;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -122,6 +122,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="relockDatabaseAutoTypeCheckBox">
|
||||
<property name="text">
|
||||
<string>Re-lock previously locked database after performing Auto-Type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="passwordRepeatCheckBox">
|
||||
<property name="text">
|
||||
|
201
src/gui/entry/AutoTypeMatchModel.cpp
Normal file
201
src/gui/entry/AutoTypeMatchModel.cpp
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AutoTypeMatchModel.h"
|
||||
|
||||
#include <QFont>
|
||||
|
||||
#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<AutoTypeMatch>& matches)
|
||||
{
|
||||
beginResetModel();
|
||||
|
||||
severConnections();
|
||||
|
||||
m_allGroups.clear();
|
||||
m_matches = matches;
|
||||
|
||||
QSet<Database*> databases;
|
||||
|
||||
for (AutoTypeMatch& match : 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;
|
||||
}
|
||||
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 {};
|
||||
}
|
||||
|
||||
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 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 {};
|
||||
}
|
||||
|
||||
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*)));
|
||||
}
|
66
src/gui/entry/AutoTypeMatchModel.h
Normal file
66
src/gui/entry/AutoTypeMatchModel.h
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_AUTOTYPEMATCHMODEL_H
|
||||
#define KEEPASSX_AUTOTYPEMATCHMODEL_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
#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<AutoTypeMatch>& matches);
|
||||
|
||||
private slots:
|
||||
void entryAboutToRemove(Entry* entry);
|
||||
void entryRemoved();
|
||||
void entryDataChanged(Entry* entry);
|
||||
|
||||
private:
|
||||
void severConnections();
|
||||
void makeConnections(const Group* group);
|
||||
|
||||
QList<AutoTypeMatch> m_matches;
|
||||
QList<const Group*> m_allGroups;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_AUTOTYPEMATCHMODEL_H
|
112
src/gui/entry/AutoTypeMatchView.cpp
Normal file
112
src/gui/entry/AutoTypeMatchView.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AutoTypeMatchView.h"
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QKeyEvent>
|
||||
|
||||
#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());
|
||||
#ifdef Q_OS_MAC
|
||||
// Pressing return does not emit the QTreeView::activated signal on mac os
|
||||
emit activated(currentIndex());
|
||||
#endif
|
||||
}
|
||||
|
||||
QTreeView::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void AutoTypeMatchView::setMatchList(const QList<AutoTypeMatch>& 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);
|
||||
}
|
||||
|
||||
AutoTypeMatch AutoTypeMatchView::currentMatch()
|
||||
{
|
||||
QModelIndexList list = selectionModel()->selectedRows();
|
||||
if (list.size() == 1) {
|
||||
return m_model->matchFromIndex(m_sortModel->mapToSource(list.first()));
|
||||
}
|
||||
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));
|
||||
}
|
||||
return AutoTypeMatch();
|
||||
}
|
57
src/gui/entry/AutoTypeMatchView.h
Normal file
57
src/gui/entry/AutoTypeMatchView.h
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2015 David Wu <lightvector@gmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_AUTOTYPEMATCHVIEW_H
|
||||
#define KEEPASSX_AUTOTYPEMATCHVIEW_H
|
||||
|
||||
#include <QTreeView>
|
||||
|
||||
#include "core/AutoTypeMatch.h"
|
||||
|
||||
#include "gui/entry/AutoTypeMatchModel.h"
|
||||
|
||||
class SortFilterHideProxyModel;
|
||||
|
||||
class AutoTypeMatchView : public QTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AutoTypeMatchView(QWidget* parent = nullptr);
|
||||
AutoTypeMatch currentMatch();
|
||||
void setCurrentMatch(AutoTypeMatch match);
|
||||
AutoTypeMatch matchFromIndex(const QModelIndex& index);
|
||||
void setMatchList(const QList<AutoTypeMatch>& matches);
|
||||
void setFirstMatchActive();
|
||||
|
||||
signals:
|
||||
void matchActivated(AutoTypeMatch match);
|
||||
void matchSelectionChanged();
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
private slots:
|
||||
void emitMatchActivated(const QModelIndex& index);
|
||||
|
||||
private:
|
||||
AutoTypeMatchModel* const m_model;
|
||||
SortFilterHideProxyModel* const m_sortModel;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_AUTOTYPEMATCHVIEW_H
|
@ -170,8 +170,6 @@ 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_autoTypeAssocModel->setAutoTypeAssociations(m_autoTypeAssoc);
|
||||
m_autoTypeUi->assocView->setModel(m_autoTypeAssocModel);
|
||||
m_autoTypeUi->assocView->setColumnHidden(1, true);
|
||||
@ -190,8 +188,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 +640,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 +994,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 +1024,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);
|
||||
}
|
||||
|
||||
updateAutoTypeEnabled();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
clearCurrentAssoc();
|
||||
}
|
||||
}
|
||||
@ -1047,7 +1041,7 @@ void EditEntryWidget::clearCurrentAssoc()
|
||||
{
|
||||
m_autoTypeUi->windowTitleCombo->setEditText("");
|
||||
|
||||
m_autoTypeUi->defaultWindowSequenceButton->setChecked(true);
|
||||
m_autoTypeUi->customWindowSequenceButton->setChecked(false);
|
||||
m_autoTypeUi->windowSequenceEdit->setText("");
|
||||
|
||||
updateAutoTypeEnabled();
|
||||
|
@ -203,16 +203,9 @@
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="defaultWindowSequenceButton">
|
||||
<widget class="QCheckBox" name="customWindowSequenceButton">
|
||||
<property name="text">
|
||||
<string>Use default se&quence</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="customWindowSequenceButton">
|
||||
<property name="text">
|
||||
<string>Set custo&m sequence:</string>
|
||||
<string>Use a specific sequence for this association:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -277,7 +270,6 @@
|
||||
<tabstop>sequenceEdit</tabstop>
|
||||
<tabstop>assocView</tabstop>
|
||||
<tabstop>windowTitleCombo</tabstop>
|
||||
<tabstop>defaultWindowSequenceButton</tabstop>
|
||||
<tabstop>customWindowSequenceButton</tabstop>
|
||||
<tabstop>windowSequenceEdit</tabstop>
|
||||
<tabstop>assocAddButton</tabstop>
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -142,6 +142,22 @@
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="checkBoxExtASCII">
|
||||
<property name="toolTip">
|
||||
<string>Extended ASCII</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Extended ASCII</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">optionButtons</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
@ -278,7 +279,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;
|
||||
}
|
||||
|
||||
@ -308,12 +309,14 @@ 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));
|
||||
} 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;
|
||||
}
|
||||
|
||||
@ -355,8 +358,23 @@ 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: %s").arg(m_kdfName);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -402,7 +420,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 +443,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 +480,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;
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,7 @@ private:
|
||||
|
||||
QString m_type;
|
||||
QString m_cipherName;
|
||||
QByteArray m_cipherIV;
|
||||
QString m_kdfName;
|
||||
QByteArray m_kdfOptions;
|
||||
QByteArray m_rawPrivateData;
|
||||
|
@ -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");
|
||||
@ -311,3 +300,81 @@ void TestAutoType::testAutoTypeSyntaxChecks()
|
||||
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}");
|
||||
|
||||
QScopedPointer<Database> db(new Database());
|
||||
QPointer<Group> rootGroup = db->rootGroup();
|
||||
|
||||
// Group with autotype enabled and custom default sequence
|
||||
QPointer<Group> group1 = new Group();
|
||||
group1->setParent(rootGroup);
|
||||
group1->setDefaultAutoTypeSequence(sequenceG1);
|
||||
|
||||
// Child group with inherit
|
||||
QPointer<Group> group2 = new Group();
|
||||
group2->setParent(group1);
|
||||
|
||||
// Group with autotype disabled and custom default sequence
|
||||
QPointer<Group> group3 = new Group();
|
||||
group3->setParent(group1);
|
||||
group3->setAutoTypeEnabled(Group::Disable);
|
||||
group3->setDefaultAutoTypeSequence(sequenceG3);
|
||||
|
||||
QCOMPARE(rootGroup->defaultAutoTypeSequence(), QString());
|
||||
QCOMPARE(rootGroup->effectiveAutoTypeSequence(), defaultSequence);
|
||||
QCOMPARE(group1->defaultAutoTypeSequence(), sequenceG1);
|
||||
QCOMPARE(group1->effectiveAutoTypeSequence(), sequenceG1);
|
||||
QCOMPARE(group2->defaultAutoTypeSequence(), QString());
|
||||
QCOMPARE(group2->effectiveAutoTypeSequence(), sequenceG1);
|
||||
QCOMPARE(group3->defaultAutoTypeSequence(), sequenceG3);
|
||||
QCOMPARE(group3->effectiveAutoTypeSequence(), QString());
|
||||
|
||||
// Entry from root group
|
||||
QPointer<Entry> entry1 = new Entry();
|
||||
entry1->setGroup(rootGroup);
|
||||
|
||||
// Entry with custom default sequence
|
||||
QPointer<Entry> entry2 = new Entry();
|
||||
entry2->setDefaultAutoTypeSequence(sequenceE2);
|
||||
entry2->setGroup(rootGroup);
|
||||
|
||||
// Entry from enabled child group
|
||||
QPointer<Entry> entry3 = new Entry();
|
||||
entry3->setGroup(group2);
|
||||
|
||||
// Entry from disabled group
|
||||
QPointer<Entry> entry4 = new Entry();
|
||||
entry4->setDefaultAutoTypeSequence(sequenceDisabled);
|
||||
entry4->setGroup(group3);
|
||||
|
||||
// Entry from enabled group with disabled autotype
|
||||
QPointer<Entry> entry5 = new Entry();
|
||||
entry5->setGroup(group2);
|
||||
entry5->setDefaultAutoTypeSequence(sequenceDisabled);
|
||||
entry5->setAutoTypeEnabled(false);
|
||||
|
||||
// Entry with no parent
|
||||
QScopedPointer<Entry> entry6(new Entry());
|
||||
entry6->setDefaultAutoTypeSequence(sequenceOrphan);
|
||||
|
||||
QCOMPARE(entry1->defaultAutoTypeSequence(), QString());
|
||||
QCOMPARE(entry1->effectiveAutoTypeSequence(), defaultSequence);
|
||||
QCOMPARE(entry2->defaultAutoTypeSequence(), sequenceE2);
|
||||
QCOMPARE(entry2->effectiveAutoTypeSequence(), sequenceE2);
|
||||
QCOMPARE(entry3->defaultAutoTypeSequence(), QString());
|
||||
QCOMPARE(entry3->effectiveAutoTypeSequence(), sequenceG1);
|
||||
QCOMPARE(entry4->defaultAutoTypeSequence(), sequenceDisabled);
|
||||
QCOMPARE(entry4->effectiveAutoTypeSequence(), QString());
|
||||
QCOMPARE(entry5->defaultAutoTypeSequence(), sequenceDisabled);
|
||||
QCOMPARE(entry5->effectiveAutoTypeSequence(), QString());
|
||||
QCOMPARE(entry6->defaultAutoTypeSequence(), sequenceOrphan);
|
||||
QCOMPARE(entry6->effectiveAutoTypeSequence(), QString());
|
||||
}
|
@ -38,8 +38,7 @@ private slots:
|
||||
void cleanup();
|
||||
|
||||
void testInternal();
|
||||
void testAutoTypeWithoutSequence();
|
||||
void testAutoTypeWithSequence();
|
||||
void testSingleAutoType();
|
||||
void testGlobalAutoTypeWithNoMatch();
|
||||
void testGlobalAutoTypeWithOneMatch();
|
||||
void testGlobalAutoTypeTitleMatch();
|
||||
@ -48,6 +47,7 @@ private slots:
|
||||
void testGlobalAutoTypeTitleMatchDisabled();
|
||||
void testGlobalAutoTypeRegExp();
|
||||
void testAutoTypeSyntaxChecks();
|
||||
void testAutoTypeEffectiveSequences();
|
||||
|
||||
private:
|
||||
AutoTypePlatformInterface* m_platform;
|
||||
|
@ -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(
|
||||
|
@ -31,6 +31,7 @@ private slots:
|
||||
void testParse();
|
||||
void testParseDSA();
|
||||
void testParseRSA();
|
||||
void testDecryptAES128CBC();
|
||||
void testDecryptAES256CBC();
|
||||
void testDecryptAES256CTR();
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -27,6 +27,8 @@ class TestSymmetricCipher : public QObject
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void testAes128CbcEncryption();
|
||||
void testAes128CbcDecryption();
|
||||
void testAes256CbcEncryption();
|
||||
void testAes256CbcDecryption();
|
||||
void testAes256CtrEncryption();
|
||||
|
Loading…
Reference in New Issue
Block a user