diff --git a/.gitignore b/.gitignore index c96688e9f..903910a37 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ release*/ *.swp .DS_Store -.version \ No newline at end of file +.version +\.scannerwork/ diff --git a/CMakeLists.txt b/CMakeLists.txt index a4cb4d1fc..dfba16788 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -339,6 +339,8 @@ if(ZLIB_VERSION_STRING VERSION_LESS "1.2.0") message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format") endif() +include_directories(SYSTEM ${ARGON2_INCLUDE_DIR}) + # Optional if(WITH_XC_YUBIKEY) find_package(YubiKey REQUIRED) diff --git a/COPYING b/COPYING index 8095ef067..5108458d8 100644 --- a/COPYING +++ b/COPYING @@ -235,3 +235,7 @@ License: LGPL-2.1 Files: share/macosx/dmg-background.tiff Copyright: 2008-2014, Andrey Tarantsov License: MIT + +Files: cmake/DeployQt.cmake +Copyright: 2018, Nathan Osman +License: MIT diff --git a/README.md b/README.md index b09a5d208..fc63beda2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # KeePassXC -[![TeamCity Build Status](https://ci.keepassxc.org/app/rest/builds/buildType:\(id:KeepassXC_TeamCityCi\)/statusIcon?guest=1)](https://ci.keepassxc.org/viewType.html?buildTypeId=KeepassXC_TeamCityCi&guest=1) [![Coverage Status](https://coveralls.io/repos/github/keepassxreboot/keepassxc/badge.svg)](https://coveralls.io/github/keepassxreboot/keepassxc) +[![TeamCity Build Status](https://ci.keepassxc.org/app/rest/builds/buildType:\(id:KeepassXC_TeamCityCi\)/statusIcon?guest=1)](https://ci.keepassxc.org/viewType.html?buildTypeId=KeepassXC_TeamCityCi&guest=1) [![Build status](https://ci.appveyor.com/api/projects/status/qmcar8rnqjh4oxof?svg=true)](https://ci.appveyor.com/project/droidmonkey/keepassxc) [![codecov](https://codecov.io/gh/keepassxreboot/keepassxc/branch/develop/graph/badge.svg)](https://codecov.io/gh/keepassxreboot/keepassxc) + ## About KeePassXC [KeePassXC](https://keepassxc.org) is a cross-platform community fork of @@ -32,7 +33,7 @@ so please check out your distribution's package list to see if KeePassXC is avai - Browser integration with KeePassHTTP-Connector for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/) and [Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb), and -[passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari. [[See note about KeePassHTTP]](#Note_about_KeePassHTTP) +[passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari. [[See note about KeePassHTTP]](#note-about-keepasshttp) - Browser integration with KeePassXC-Browser using [native messaging](https://developer.chrome.com/extensions/nativeMessaging) for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/) and [Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk) - Many bug fixes diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..bcb9b9e05 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,31 @@ +image: + - Visual Studio 2017 + +configuration: + - Debug + +environment: + matrix: + - CC: gcc + CXX: g++ + +clone_depth: 1 + +platform: + - x64 + +matrix: + fast_finish: true + +install: + - C:\msys64\usr\bin\env MSYSTEM=MINGW64 /bin/bash -l -c "pacman --noconfirm -Syu" + - C:\msys64\usr\bin\env MSYSTEM=MINGW64 /bin/bash -l -c "/c/projects/keepassxc/ci/appveyor/install.sh" + +before_build: + - C:\msys64\usr\bin\env MSYSTEM=MINGW64 /bin/bash -l -c "/c/projects/keepassxc/ci/appveyor/before-build.sh" + +build_script: + - C:\msys64\usr\bin\env MSYSTEM=MINGW64 /bin/bash -l -c "/c/projects/keepassxc/ci/appveyor/build.sh" + +test_script: + - C:\msys64\usr\bin\env MSYSTEM=MINGW64 /bin/bash -l -c "/c/projects/keepassxc/ci/appveyor/test.sh" diff --git a/ci/appveyor/before-build.sh b/ci/appveyor/before-build.sh new file mode 100644 index 000000000..ff8305985 --- /dev/null +++ b/ci/appveyor/before-build.sh @@ -0,0 +1,21 @@ +#!/usr/bin/bash + +cd /c/projects/keepassxc +mkdir build && cd build + +if [ "${configuration}" = "Debug" ]; then + cmake -G "MSYS Makefiles" \ + -DCMAKE_BUILD_TYPE=${configuration} \ + -DWITH_XC_ALL=ON \ + -DWITH_TESTS=ON \ + -DWITH_GUI_TESTS=ON \ + -DWITH_ASAN=ON \ + .. +else + cmake -G "MSYS Makefiles" \ + -DCMAKE_BUILD_TYPE=${configuration} \ + -DWITH_XC_ALL=ON \ + -DWITH_TESTS=ON \ + -DWITH_GUI_TESTS=ON \ + .. +fi diff --git a/ci/appveyor/build.sh b/ci/appveyor/build.sh new file mode 100644 index 000000000..b89ebd3ad --- /dev/null +++ b/ci/appveyor/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/bash + +cd /c/projects/keepassxc/build + +make -j2 diff --git a/ci/appveyor/install.sh b/ci/appveyor/install.sh new file mode 100644 index 000000000..c5c34c327 --- /dev/null +++ b/ci/appveyor/install.sh @@ -0,0 +1,24 @@ +#!/usr/bin/bash + +# Pacman derived dependencies +pacman --needed --noconfirm -S \ + mingw-w64-$(uname -m)-cmake \ + mingw-w64-$(uname -m)-libgcrypt \ + mingw-w64-$(uname -m)-zlib \ + mingw-w64-$(uname -m)-libsodium \ + mingw-w64-$(uname -m)-argon2 \ + mingw-w64-$(uname -m)-qt5 + +# Yubikey library +curl -O -J -L https://developers.yubico.com/yubikey-personalization/Releases/ykpers-1.18.1-win64.zip +7z x ykpers-1.18.1-win64.zip -o"/mingw64/" -aoa + +# qrencode library +curl -O -J -L https://fukuchi.org/works/qrencode/qrencode-4.0.0.tar.gz +tar -xf qrencode-4.0.0.tar.gz + +cd qrencode-4.0.0 +mkdir build && cd build +cmake -G "MSYS Makefiles" -DBUILD_SHARED_LIBS=YES -DWITH_TOOLS=NO -DCMAKE_BUILD_TYPE=${configuration} .. +make -j2 +make install PREFIX="/mingw64" \ No newline at end of file diff --git a/ci/appveyor/test.sh b/ci/appveyor/test.sh new file mode 100644 index 000000000..b7a8a2121 --- /dev/null +++ b/ci/appveyor/test.sh @@ -0,0 +1,6 @@ +#!/usr/bin/bash + +cd /c/projects/keepassxc/build + +make test ARGS+="-E testgui --output-on-failure --verbose" +make test ARGS+="-R testgui --output-on-failure --verbose" diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake index a0b0ef526..d10f79723 100644 --- a/cmake/CodeCoverage.cmake +++ b/cmake/CodeCoverage.cmake @@ -131,6 +131,10 @@ FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) ENDIF() # NOT GENHTML_PATH SET(coverage_info "${CMAKE_BINARY_DIR}/${_outputname}.info") + IF(MINGW) + # Replace C:/ with /C for MINGW + STRING(REGEX REPLACE "^([a-zA-Z]):" "/\\1" coverage_info ${coverage_info}) + ENDIF() SET(coverage_cleaned "${coverage_info}.cleaned") SEPARATE_ARGUMENTS(test_command UNIX_COMMAND "${_testrunner}") diff --git a/cmake/DeployQt.cmake b/cmake/DeployQt.cmake new file mode 100644 index 000000000..a9d239be8 --- /dev/null +++ b/cmake/DeployQt.cmake @@ -0,0 +1,88 @@ +# The MIT License (MIT) +# +# Copyright (c) 2018 Nathan Osman +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +find_package(Qt5Core REQUIRED) + +# Retrieve the absolute path to qmake and then use that path to find +# the windeployqt and macdeployqt binaries +get_target_property(_qmake_executable Qt5::qmake IMPORTED_LOCATION) +get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY) + +find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}") +if(WIN32 AND NOT WINDEPLOYQT_EXECUTABLE) + message(FATAL_ERROR "windeployqt not found") +endif() + +find_program(MACDEPLOYQT_EXECUTABLE macdeployqt HINTS "${_qt_bin_dir}") +if(APPLE AND NOT MACDEPLOYQT_EXECUTABLE) + message(FATAL_ERROR "macdeployqt not found") +endif() + +# Add commands that copy the required Qt files to the same directory as the +# target after being built as well as including them in final installation +function(windeployqt target) + + # Run windeployqt immediately after build + add_custom_command(TARGET ${target} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E + env PATH="${_qt_bin_dir}" "${WINDEPLOYQT_EXECUTABLE}" + --verbose 0 + --no-compiler-runtime + --no-angle + --no-opengl-sw + \"$\" + COMMENT "Deploying Qt..." + ) + + # windeployqt doesn't work correctly with the system runtime libraries, + # so we fall back to one of CMake's own modules for copying them over + + # Doing this with MSVC 2015 requires CMake 3.6+ + if((MSVC_VERSION VERSION_EQUAL 1900 OR MSVC_VERSION VERSION_GREATER 1900) + AND CMAKE_VERSION VERSION_LESS "3.6") + message(WARNING "Deploying with MSVC 2015+ requires CMake 3.6+") + endif() + + set(CMAKE_INSTALL_UCRT_LIBRARIES TRUE) + include(InstallRequiredSystemLibraries) + foreach(lib ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}) + get_filename_component(filename "${lib}" NAME) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E + copy_if_different "${lib}" \"$\" + COMMENT "Copying ${filename}..." + ) + endforeach() +endfunction() + +# Add commands that copy the required Qt files to the application bundle +# represented by the target. +function(macdeployqt target) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND "${MACDEPLOYQT_EXECUTABLE}" + \"$/../..\" + -always-overwrite + COMMENT "Deploying Qt..." + ) +endfunction() + +mark_as_advanced(WINDEPLOYQT_EXECUTABLE MACDEPLOYQT_EXECUTABLE) diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 000000000..0ba2bee8f --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,18 @@ +# https://about.sonarcloud.io/get-started/ +# Run the SonarCloud tools with the follow parameters: +# Run in the cmake build directory after cmake: build-wrapper-[platform]-x86-64 --out-dir bw-output make clean all +# Run in the project root directory: sonar-scanner.bat -Dsonar.cfamily.build-wrapper-output=build/bw-output -Dsonar.cfamily.gcov.reportsPath=build -Dsonar.login=[AUTH_TOKEN] + +# required metadata +sonar.projectKey=keepassxc +sonar.organization=droidmonkey-github +sonar.projectName=keepassxc +sonar.host.url=https://sonarcloud.io + +# path to source directories (required) +sonar.sources=src +sonar.tests=tests + +sonar.cfamily.threads=2 + +sonar.exclusions=**/zxcvbn/* diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 432d10eed..012fc29db 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -313,11 +313,8 @@ if(APPLE AND WITH_APP_BUNDLE) set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION}") include(CPack) - add_custom_command(TARGET ${PROGNAME} - POST_BUILD - COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src - COMMENT "Deploying app bundle") + include(DeployQt) + macdeployqt(${PROGNAME}) endif() install(TARGETS ${PROGNAME} @@ -378,8 +375,8 @@ if(MINGW) set(gp_tool \"objdump\") " COMPONENT Runtime) - include(DeployQt4) - install_qt4_executable(${PROGNAME}.exe) + include(DeployQt) + windeployqt(${PROGNAME}) # install Qt5 plugins set(PLUGINS_DIR ${Qt5_PREFIX}/share/qt5/plugins) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index 89c24e55e..aaa742a09 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -756,9 +756,10 @@ bool AutoType::verifyAutoTypeSyntax(const QString& sequence) } } else if (AutoType::checkHighRepetition(sequence)) { QMessageBox::StandardButton reply; - reply = - QMessageBox::question(nullptr, tr("Auto-Type"), tr("This Auto-Type command contains arguments which are " - "repeated very often. Do you really want to proceed?")); + reply = QMessageBox::question(nullptr, + tr("Auto-Type"), + tr("This Auto-Type command contains arguments which are " + "repeated very often. Do you really want to proceed?")); if (reply == QMessageBox::No) { return false; diff --git a/src/autotype/mac/CMakeLists.txt b/src/autotype/mac/CMakeLists.txt index 08c532784..9ec8b71ac 100644 --- a/src/autotype/mac/CMakeLists.txt +++ b/src/autotype/mac/CMakeLists.txt @@ -11,10 +11,11 @@ set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets) if(WITH_APP_BUNDLE) + include(DeployQt) add_custom_command(TARGET keepassx-autotype-cocoa POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR} - COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins + COMMAND ${MACDEPLOYQT_EXECUTABLE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src COMMENT "Deploying autotype plugin") else() diff --git a/src/browser/BrowserSettings.cpp b/src/browser/BrowserSettings.cpp index 4a75797fd..76163b9d9 100644 --- a/src/browser/BrowserSettings.cpp +++ b/src/browser/BrowserSettings.cpp @@ -261,6 +261,66 @@ void BrowserSettings::setPasswordUseSpecial(bool useSpecial) config()->set("generator/SpecialChars", useSpecial); } +bool BrowserSettings::passwordUseBraces() +{ + return config()->get("generator/Braces", PasswordGenerator::DefaultBraces).toBool(); +} + +void BrowserSettings::setPasswordUseBraces(bool useBraces) +{ + config()->set("generator/Braces", useBraces); +} + +bool BrowserSettings::passwordUsePunctuation() +{ + return config()->get("generator/Punctuation", PasswordGenerator::DefaultQuotes).toBool(); +} + +void BrowserSettings::setPasswordUsePunctuation(bool usePunctuation) +{ + config()->set("generator/Punctuation", usePunctuation); +} + +bool BrowserSettings::passwordUseQuotes() +{ + return config()->get("generator/Quotes", PasswordGenerator::DefaultQuotes).toBool(); +} + +void BrowserSettings::setPasswordUseQuotes(bool useQuotes) +{ + config()->set("generator/Quotes", useQuotes); +} + +bool BrowserSettings::passwordUseDashes() +{ + return config()->get("generator/Dashes", PasswordGenerator::DefaultDashes).toBool(); +} + +void BrowserSettings::setPasswordUseDashes(bool useDashes) +{ + config()->set("generator/Dashes", useDashes); +} + +bool BrowserSettings::passwordUseMath() +{ + return config()->get("generator/Math", PasswordGenerator::DefaultMath).toBool(); +} + +void BrowserSettings::setPasswordUseMath(bool useMath) +{ + config()->set("generator/Math", useMath); +} + +bool BrowserSettings::passwordUseLogograms() +{ + return config()->get("generator/Logograms", PasswordGenerator::DefaultLogograms).toBool(); +} + +void BrowserSettings::setPasswordUseLogograms(bool useLogograms) +{ + config()->set("generator/Logograms", useLogograms); +} + bool BrowserSettings::passwordUseEASCII() { return config()->get("generator/EASCII", PasswordGenerator::DefaultEASCII).toBool(); @@ -271,6 +331,26 @@ void BrowserSettings::setPasswordUseEASCII(bool useEASCII) config()->set("generator/EASCII", useEASCII); } +bool BrowserSettings::advancedMode() +{ + return config()->get("generator/AdvancedMode", PasswordGenerator::DefaultAdvancedMode).toBool(); +} + +void BrowserSettings::setAdvancedMode(bool advancedMode) +{ + config()->set("generator/AdvancedMode", advancedMode); +} + +QString BrowserSettings::passwordExcludedChars() +{ + return config()->get("generator/ExcludedChars", PasswordGenerator::DefaultExcludedChars).toString(); +} + +void BrowserSettings::setPasswordExcludedChars(QString chars) +{ + config()->set("generator/ExcludedChars", chars); +} + int BrowserSettings::passPhraseWordCount() { return config()->get("generator/WordCount", PassphraseGenerator::DefaultWordCount).toInt(); @@ -347,6 +427,24 @@ PasswordGenerator::CharClasses BrowserSettings::passwordCharClasses() if (passwordUseSpecial()) { classes |= PasswordGenerator::SpecialCharacters; } + if (passwordUseBraces()) { + classes |= PasswordGenerator::Braces; + } + if (passwordUsePunctuation()) { + classes |= PasswordGenerator::Punctuation; + } + if (passwordUseQuotes()) { + classes |= PasswordGenerator::Quotes; + } + if (passwordUseDashes()) { + classes |= PasswordGenerator::Dashes; + } + if (passwordUseMath()) { + classes |= PasswordGenerator::Math; + } + if (passwordUseLogograms()) { + classes |= PasswordGenerator::Logograms; + } if (passwordUseEASCII()) { classes |= PasswordGenerator::EASCII; } diff --git a/src/browser/BrowserSettings.h b/src/browser/BrowserSettings.h index b02fd4759..3c79dbed2 100644 --- a/src/browser/BrowserSettings.h +++ b/src/browser/BrowserSettings.h @@ -76,8 +76,24 @@ public: static void setPasswordUseUppercase(bool useUppercase); static bool passwordUseSpecial(); static void setPasswordUseSpecial(bool useSpecial); + static bool passwordUseBraces(); + static void setPasswordUseBraces(bool useBraces); + static bool passwordUsePunctuation(); + static void setPasswordUsePunctuation(bool usePunctuation); + static bool passwordUseQuotes(); + static void setPasswordUseQuotes(bool useQuotes); + static bool passwordUseDashes(); + static void setPasswordUseDashes(bool useDashes); + static bool passwordUseMath(); + static void setPasswordUseMath(bool useMath); + static bool passwordUseLogograms(); + static void setPasswordUseLogograms(bool useLogograms); static bool passwordUseEASCII(); static void setPasswordUseEASCII(bool useEASCII); + static bool advancedMode(); + static void setAdvancedMode(bool advancedMode); + static QString passwordExcludedChars(); + static void setPasswordExcludedChars(QString chars); static int passPhraseWordCount(); static void setPassPhraseWordCount(int wordCount); static QString passPhraseWordSeparator(); diff --git a/src/cli/Generate.cpp b/src/cli/Generate.cpp index 45b67259c..6a5be3f07 100644 --- a/src/cli/Generate.cpp +++ b/src/cli/Generate.cpp @@ -44,19 +44,41 @@ int Generate::execute(const QStringList& arguments) parser.setApplicationDescription(this->description); QCommandLineOption len(QStringList() << "L" << "length", - QObject::tr("Length of the generated password."), + 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.")); + QCommandLineOption lower(QStringList() << "l" + << "lower", + QObject::tr("Use lowercase characters")); parser.addOption(lower); - QCommandLineOption upper(QStringList() << "u", QObject::tr("Use uppercase characters in the generated password.")); + QCommandLineOption upper(QStringList() << "u" + << "upper", + QObject::tr("Use uppercase characters")); parser.addOption(upper); - QCommandLineOption numeric(QStringList() << "n", QObject::tr("Use numbers in the generated password.")); + QCommandLineOption numeric(QStringList() << "n" + << "numeric", + QObject::tr("Use numbers.")); parser.addOption(numeric); - QCommandLineOption special(QStringList() << "s", QObject::tr("Use special characters in the generated password.")); + QCommandLineOption special(QStringList() << "s" + << "special", + QObject::tr("Use special characters")); parser.addOption(special); - QCommandLineOption extended(QStringList() << "e", QObject::tr("Use extended ASCII in the generated password.")); + QCommandLineOption extended(QStringList() << "e" + << "extended", + QObject::tr("Use extended ASCII")); parser.addOption(extended); + QCommandLineOption exclude(QStringList() << "x" + << "exclude", + QObject::tr("Exclude character set"), + QObject::tr("chars")); + parser.addOption(exclude); + QCommandLineOption exclude_similar(QStringList() << "exclude-similar", + QObject::tr("Exclude similar looking characters")); + parser.addOption(exclude_similar); + QCommandLineOption every_group(QStringList() << "every-group", + QObject::tr("Include characters from every selected group")); + parser.addOption(every_group); + parser.process(arguments); const QStringList args = parser.positionalArguments(); @@ -92,8 +114,18 @@ int Generate::execute(const QStringList& arguments) classes |= PasswordGenerator::EASCII; } + PasswordGenerator::GeneratorFlags flags = 0x0; + + if (parser.isSet(exclude_similar)) { + flags |= PasswordGenerator::ExcludeLookAlike; + } + if (parser.isSet(every_group)) { + flags |= PasswordGenerator::CharFromEveryGroup; + } + passwordGenerator.setCharClasses(classes); - passwordGenerator.setFlags(PasswordGenerator::DefaultFlags); + passwordGenerator.setFlags(flags); + passwordGenerator.setExcludedChars(parser.value(exclude)); if (!passwordGenerator.isValid()) { outputTextStream << parser.helpText().replace("keepassxc-cli", "keepassxc-cli generate"); diff --git a/src/core/PasswordGenerator.cpp b/src/core/PasswordGenerator.cpp index 00984d313..01b715072 100644 --- a/src/core/PasswordGenerator.cpp +++ b/src/core/PasswordGenerator.cpp @@ -21,10 +21,13 @@ #include "crypto/Random.h" #include +const char* PasswordGenerator::DefaultExcludedChars = ""; + PasswordGenerator::PasswordGenerator() : m_length(0) , m_classes(0) , m_flags(0) + , m_excluded(PasswordGenerator::DefaultExcludedChars) { } @@ -56,6 +59,11 @@ void PasswordGenerator::setFlags(const GeneratorFlags& flags) m_flags = flags; } +void PasswordGenerator::setExcludedChars(const QString& chars) +{ + m_excluded = chars; +} + QString PasswordGenerator::generatePassword() const { Q_ASSERT(isValid()); @@ -130,6 +138,10 @@ bool PasswordGenerator::isValid() const return false; } + if (passwordGroups().size() == 0) { + return false; + } + return true; } @@ -140,7 +152,8 @@ QVector PasswordGenerator::passwordGroups() const if (m_classes & LowerLetters) { PasswordGroup group; - for (int i = 97; i < (97 + 26); i++) { + for (int i = 97; i <= (97 + 25); i++) { + if ((m_flags & ExcludeLookAlike) && (i == 108)) { // "l" continue; } @@ -153,7 +166,8 @@ QVector PasswordGenerator::passwordGroups() const if (m_classes & UpperLetters) { PasswordGroup group; - for (int i = 65; i < (65 + 26); i++) { + for (int i = 65; i <= (65 + 25); i++) { + if ((m_flags & ExcludeLookAlike) && (i == 73 || i == 79)) { // "I" and "O" continue; } @@ -176,28 +190,79 @@ QVector PasswordGenerator::passwordGroups() const passwordGroups.append(group); } - if (m_classes & SpecialCharacters) { + if (m_classes & Braces) { PasswordGroup group; - for (int i = 33; i <= 47; i++) { - group.append(i); + // ()[]{} + group.append(40); + group.append(41); + group.append(91); + group.append(93); + group.append(123); + group.append(125); + + passwordGroups.append(group); + } + if (m_classes & Punctuation) { + PasswordGroup group; + + // .,:; + group.append(44); + group.append(46); + group.append(58); + group.append(59); + + passwordGroups.append(group); + } + if (m_classes & Quotes) { + PasswordGroup group; + + // "' + group.append(34); + group.append(39); + + passwordGroups.append(group); + } + if (m_classes & Dashes) { + PasswordGroup group; + + // -/\_| + group.append(45); + group.append(47); + group.append(92); + group.append(95); + if (!(m_flags & ExcludeLookAlike)) { + group.append(124); // "|" } - for (int i = 58; i <= 64; i++) { - group.append(i); - } - - for (int i = 91; i <= 96; i++) { - group.append(i); - } - - for (int i = 123; i <= 126; i++) { - if ((m_flags & ExcludeLookAlike) && (i == 124)) { // "|" - continue; - } - + passwordGroups.append(group); + } + if (m_classes & Math) { + PasswordGroup group; + + // !*+-<=>? + group.append(33); + group.append(42); + group.append(43); + group.append(60); + group.append(61); + group.append(62); + group.append(63); + + passwordGroups.append(group); + } + if (m_classes & Logograms) { + PasswordGroup group; + + // #$%& + for (int i = 35; i <= 38; i++) { group.append(i); } + // @^`~ + group.append(64); + group.append(94); + group.append(96); + group.append(126); passwordGroups.append(group); } @@ -220,6 +285,27 @@ QVector PasswordGenerator::passwordGroups() const passwordGroups.append(group); } + // Loop over character groups and remove excluded characters from them; + // remove empty groups + int i = 0; + while (i != passwordGroups.size()) { + PasswordGroup group = passwordGroups[i]; + + for (QChar ch : m_excluded) { + int j = group.indexOf(ch); + while (j != -1) { + group.remove(j); + j = group.indexOf(ch); + } + } + if (group.size() > 0) { + passwordGroups.replace(i, group); + i++; + } else { + passwordGroups.remove(i); + } + } + return passwordGroups; } @@ -236,7 +322,22 @@ int PasswordGenerator::numCharClasses() const if (m_classes & Numbers) { numClasses++; } - if (m_classes & SpecialCharacters) { + if (m_classes & Braces) { + numClasses++; + } + if (m_classes & Punctuation) { + numClasses++; + } + if (m_classes & Quotes) { + numClasses++; + } + if (m_classes & Dashes) { + numClasses++; + } + if (m_classes & Math) { + numClasses++; + } + if (m_classes & Logograms) { numClasses++; } if (m_classes & EASCII) { diff --git a/src/core/PasswordGenerator.h b/src/core/PasswordGenerator.h index d6866b5ef..1d6ac73f6 100644 --- a/src/core/PasswordGenerator.h +++ b/src/core/PasswordGenerator.h @@ -30,19 +30,26 @@ class PasswordGenerator public: enum CharClass { - LowerLetters = 0x1, - UpperLetters = 0x2, - Numbers = 0x4, - SpecialCharacters = 0x8, - EASCII = 0x10, + LowerLetters = (1 << 0), + UpperLetters = (1 << 1), + Numbers = (1 << 2), + Braces = (1 << 3), + Punctuation = (1 << 4), + Quotes = (1 << 5), + Dashes = (1 << 6), + Math = (1 << 7), + Logograms = (1 << 8), + SpecialCharacters = Braces | Punctuation | Quotes | Dashes | Math | Logograms, + EASCII = (1 << 9), DefaultCharset = LowerLetters | UpperLetters | Numbers }; Q_DECLARE_FLAGS(CharClasses, CharClass) enum GeneratorFlag { - ExcludeLookAlike = 0x1, - CharFromEveryGroup = 0x2, + ExcludeLookAlike = (1 << 0), + CharFromEveryGroup = (1 << 1), + AdvancedMode = (1 << 2), DefaultFlags = ExcludeLookAlike | CharFromEveryGroup }; Q_DECLARE_FLAGS(GeneratorFlags, GeneratorFlag) @@ -54,6 +61,7 @@ public: void setLength(int length); void setCharClasses(const CharClasses& classes); void setFlags(const GeneratorFlags& flags); + void setExcludedChars(const QString& chars); bool isValid() const; @@ -61,10 +69,18 @@ public: int getbits() const; static const int DefaultLength = 16; + static const char* DefaultExcludedChars; 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 DefaultAdvancedMode = (DefaultFlags & AdvancedMode) != 0; + static constexpr bool DefaultBraces = (DefaultCharset & Braces) != 0; + static constexpr bool DefaultPunctuation = (DefaultCharset & Punctuation) != 0; + static constexpr bool DefaultQuotes = (DefaultCharset & Quotes) != 0; + static constexpr bool DefaultDashes = (DefaultCharset & Dashes) != 0; + static constexpr bool DefaultMath = (DefaultCharset & Math) != 0; + static constexpr bool DefaultLogograms = (DefaultCharset & Logograms) != 0; static constexpr bool DefaultEASCII = (DefaultCharset & EASCII) != 0; static constexpr bool DefaultLookAlike = (DefaultFlags & ExcludeLookAlike) != 0; static constexpr bool DefaultFromEveryGroup = (DefaultFlags & CharFromEveryGroup) != 0; @@ -76,6 +92,7 @@ private: int m_length; CharClasses m_classes; GeneratorFlags m_flags; + QString m_excluded; Q_DISABLE_COPY(PasswordGenerator) }; diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 5f51fa47d..458d42988 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -56,8 +57,9 @@ namespace Tools { - QString humanReadableFileSize(qint64 bytes) + QString humanReadableFileSize(qint64 bytes, quint32 precision) { + constexpr auto kibibyte = 1024; double size = bytes; QStringList units = QStringList() << "B" @@ -67,12 +69,12 @@ namespace Tools int i = 0; int maxI = units.size() - 1; - while ((size >= 1024) && (i < maxI)) { - size /= 1024; + while ((size >= kibibyte) && (i < maxI)) { + size /= kibibyte; i++; } - return QString("%1 %2").arg(QLocale().toString(size, 'f', 2), units.at(i)); + return QString("%1 %2").arg(QLocale().toString(size, 'f', precision), units.at(i)); } bool hasChild(const QObject* parent, const QObject* child) @@ -147,8 +149,8 @@ namespace Tools bool isHex(const QByteArray& ba) { - for (char c : ba) { - if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { + for (const unsigned char c : ba) { + if (!std::isxdigit(c)) { return false; } } @@ -158,8 +160,8 @@ namespace Tools bool isBase64(const QByteArray& ba) { - QRegExp regexp( - "^(?:[a-z0-9+/]{4})*(?:[a-z0-9+/]{3}=|[a-z0-9+/]{2}==)?$", Qt::CaseInsensitive, QRegExp::RegExp2); + constexpr auto pattern = R"(^(?:[a-z0-9+]{4})*(?:[a-z0-9+]{3}=|[a-z0-9+]{2}==)?$)"; + QRegExp regexp(pattern, Qt::CaseInsensitive, QRegExp::RegExp2); QString base64 = QString::fromLatin1(ba.constData(), ba.size()); @@ -318,14 +320,15 @@ namespace Tools } // Set discretionary access control list - bSuccess = ERROR_SUCCESS == SetSecurityInfo(GetCurrentProcess(), // object handle - SE_KERNEL_OBJECT, // type of object - DACL_SECURITY_INFORMATION, // change only the objects DACL - nullptr, - nullptr, // do not change owner or group - pACL, // DACL specified - nullptr // do not change SACL - ); + bSuccess = ERROR_SUCCESS + == SetSecurityInfo(GetCurrentProcess(), // object handle + SE_KERNEL_OBJECT, // type of object + DACL_SECURITY_INFORMATION, // change only the objects DACL + nullptr, + nullptr, // do not change owner or group + pACL, // DACL specified + nullptr // do not change SACL + ); Cleanup: diff --git a/src/core/Tools.h b/src/core/Tools.h index b11760079..9fd497995 100644 --- a/src/core/Tools.h +++ b/src/core/Tools.h @@ -32,7 +32,7 @@ class QIODevice; namespace Tools { - QString humanReadableFileSize(qint64 bytes); + QString humanReadableFileSize(qint64 bytes, quint32 precision = 2); bool hasChild(const QObject* parent, const QObject* child); bool readFromDevice(QIODevice* device, QByteArray& data, int size = 16384); bool readAllFromDevice(QIODevice* device, QByteArray& data); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 7ee317ad0..f1eb1793b 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include diff --git a/src/gui/EditWidget.cpp b/src/gui/EditWidget.cpp index ce80c03b6..a58cc6d82 100644 --- a/src/gui/EditWidget.cpp +++ b/src/gui/EditWidget.cpp @@ -125,8 +125,14 @@ void EditWidget::enableApplyButton(bool enabled) void EditWidget::showMessage(const QString& text, MessageWidget::MessageType type) { - m_ui->messageWidget->setCloseButtonVisible(false); - m_ui->messageWidget->showMessage(text, type, 2000); + // Show error messages for a longer time to make sure the user can read them + if (type == MessageWidget::Error) { + m_ui->messageWidget->setCloseButtonVisible(true); + m_ui->messageWidget->showMessage(text, type, 15000); + } else { + m_ui->messageWidget->setCloseButtonVisible(false); + m_ui->messageWidget->showMessage(text, type, 2000); + } } void EditWidget::hideMessage() diff --git a/src/gui/EditWidgetIcons.cpp b/src/gui/EditWidgetIcons.cpp index 4292234e1..d5d97dde1 100644 --- a/src/gui/EditWidgetIcons.cpp +++ b/src/gui/EditWidgetIcons.cpp @@ -19,7 +19,6 @@ #include "EditWidgetIcons.h" #include "ui_EditWidgetIcons.h" -#include #include #include @@ -93,6 +92,7 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent) SIGNAL(widgetUpdated())); m_ui->faviconButton->setVisible(false); + m_ui->addButton->setEnabled(true); } EditWidgetIcons::~EditWidgetIcons() @@ -269,7 +269,9 @@ void EditWidgetIcons::fetchFinished() } if (!image.isNull()) { - addCustomIcon(image); + if (!addCustomIcon(image)) { + emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information); + } } else if (!m_urlsToTry.empty()) { m_redirects = 0; startFetchFavicon(m_urlsToTry.takeFirst()); @@ -325,35 +327,66 @@ void EditWidgetIcons::addCustomIconFromFile() if (m_database) { QString filter = QString("%1 (%2);;%3 (*)").arg(tr("Images"), Tools::imageReaderFilter(), tr("All files")); - QString filename = QFileDialog::getOpenFileName(this, tr("Select Image"), "", filter); - if (!filename.isEmpty()) { - auto icon = QImage(filename); - if (!icon.isNull()) { - addCustomIcon(QImage(filename)); + auto filenames = QFileDialog::getOpenFileNames(this, tr("Select Image(s)"), "", filter); + if (!filenames.empty()) { + QStringList errornames; + int numexisting = 0; + for (const auto& filename : filenames) { + if (!filename.isEmpty()) { + auto icon = QImage(filename); + if (icon.isNull()) { + errornames << filename; + } else if (!addCustomIcon(icon)) { + // Icon already exists in database + ++numexisting; + } + } + } + + int numloaded = filenames.size() - errornames.size() - numexisting; + QString msg; + + if (numloaded > 0) { + msg = tr("Successfully loaded %1 of %n icon(s)", "", filenames.size()).arg(numloaded); } else { - emit messageEditEntry(tr("Can't read icon"), MessageWidget::Error); + msg = tr("No icons were loaded"); + } + + if (numexisting > 0) { + msg += "\n" + tr("%n icon(s) already exist in the database", "", numexisting); + } + + if (!errornames.empty()) { + // Show the first 8 icons that failed to load + errornames = errornames.mid(0, 8); + emit messageEditEntry(msg + "\n" + tr("The following icon(s) failed:", "", errornames.size()) + + "\n" + errornames.join("\n"), MessageWidget::Error); + } else if (numloaded > 0) { + emit messageEditEntry(msg, MessageWidget::Positive); + } else { + emit messageEditEntry(msg, MessageWidget::Information); } } } } -void EditWidgetIcons::addCustomIcon(const QImage& icon) +bool EditWidgetIcons::addCustomIcon(const QImage& icon) { + bool added = false; if (m_database) { - Uuid uuid = m_database->metadata()->findCustomIcon(icon); + // Don't add an icon larger than 128x128, but retain original size if smaller + auto scaledicon = icon; + if (icon.width() > 128 || icon.height() > 128) { + scaledicon = icon.scaled(128, 128); + } + + Uuid uuid = m_database->metadata()->findCustomIcon(scaledicon); if (uuid.isNull()) { uuid = Uuid::random(); - // Don't add an icon larger than 128x128, but retain original size if smaller - if (icon.width() > 128 || icon.height() > 128) { - m_database->metadata()->addCustomIcon(uuid, icon.scaled(128, 128)); - } else { - m_database->metadata()->addCustomIcon(uuid, icon); - } - + m_database->metadata()->addCustomIcon(uuid, scaledicon); m_customIconModel->setIcons(m_database->metadata()->customIconsScaledPixmaps(), m_database->metadata()->customIconsOrder()); - } else { - emit messageEditEntry(tr("Custom icon already exists"), MessageWidget::Information); + added = true; } // Select the new or existing icon @@ -363,6 +396,8 @@ void EditWidgetIcons::addCustomIcon(const QImage& icon) emit widgetUpdated(); } + + return added; } void EditWidgetIcons::removeCustomIcon() @@ -459,7 +494,6 @@ void EditWidgetIcons::updateWidgetsDefaultIcons(bool check) m_ui->defaultIconsView->setCurrentIndex(index); } m_ui->customIconsView->selectionModel()->clearSelection(); - m_ui->addButton->setEnabled(false); m_ui->deleteButton->setEnabled(false); } } @@ -474,7 +508,6 @@ void EditWidgetIcons::updateWidgetsCustomIcons(bool check) m_ui->customIconsView->setCurrentIndex(index); } m_ui->defaultIconsView->selectionModel()->clearSelection(); - m_ui->addButton->setEnabled(true); m_ui->deleteButton->setEnabled(true); } } diff --git a/src/gui/EditWidgetIcons.h b/src/gui/EditWidgetIcons.h index 94630157d..e01ae4b1d 100644 --- a/src/gui/EditWidgetIcons.h +++ b/src/gui/EditWidgetIcons.h @@ -88,7 +88,7 @@ private slots: void fetchReadyRead(); void fetchCanceled(); void addCustomIconFromFile(); - void addCustomIcon(const QImage& icon); + bool addCustomIcon(const QImage& icon); void removeCustomIcon(); void updateWidgetsDefaultIcons(bool checked); void updateWidgetsCustomIcons(bool checked); diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 3d8ebf7be..fed34754b 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -42,6 +42,10 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updateButtonsEnabled(QString))); connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updatePasswordStrength(QString))); connect(m_ui->togglePasswordButton, SIGNAL(toggled(bool)), SLOT(togglePasswordShown(bool))); + connect(m_ui->buttonSimpleMode, SIGNAL(clicked()), SLOT(selectSimpleMode())); + connect(m_ui->buttonAdvancedMode, SIGNAL(clicked()), SLOT(selectAdvancedMode())); + connect(m_ui->buttonAddHex, SIGNAL(clicked()), SLOT(excludeHexChars())); + connect(m_ui->editExcludedChars, SIGNAL(textChanged(QString)), SLOT(updateGenerator())); connect(m_ui->buttonApply, SIGNAL(clicked()), SLOT(applyPassword())); connect(m_ui->buttonCopy, SIGNAL(clicked()), SLOT(copyPassword())); connect(m_ui->buttonGenerate, SIGNAL(clicked()), SLOT(regeneratePassword())); @@ -94,11 +98,34 @@ void PasswordGeneratorWidget::loadSettings() { // Password config m_ui->checkBoxLower->setChecked(config()->get("generator/LowerCase", PasswordGenerator::DefaultLower).toBool()); + m_ui->checkBoxLowerAdv->setChecked(config()->get("generator/LowerCase", PasswordGenerator::DefaultLower).toBool()); m_ui->checkBoxUpper->setChecked(config()->get("generator/UpperCase", PasswordGenerator::DefaultUpper).toBool()); + m_ui->checkBoxUpperAdv->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->checkBoxNumbersAdv->setChecked( + config()->get("generator/Numbers", PasswordGenerator::DefaultNumbers).toBool()); + m_ui->advancedBar->setVisible( + config()->get("generator/AdvancedMode", PasswordGenerator::DefaultAdvancedMode).toBool()); + m_ui->excludedChars->setVisible( + config()->get("generator/AdvancedMode", PasswordGenerator::DefaultAdvancedMode).toBool()); + m_ui->checkBoxExcludeAlike->setVisible( + config()->get("generator/AdvancedMode", PasswordGenerator::DefaultAdvancedMode).toBool()); + m_ui->checkBoxEnsureEvery->setVisible( + config()->get("generator/AdvancedMode", PasswordGenerator::DefaultAdvancedMode).toBool()); + m_ui->editExcludedChars->setText( + config()->get("generator/ExcludedChars", PasswordGenerator::DefaultExcludedChars).toString()); + m_ui->simpleBar->setVisible( + !(config()->get("generator/AdvancedMode", PasswordGenerator::DefaultAdvancedMode).toBool())); + m_ui->checkBoxBraces->setChecked(config()->get("generator/Braces", PasswordGenerator::DefaultBraces).toBool()); + m_ui->checkBoxQuotes->setChecked(config()->get("generator/Quotes", PasswordGenerator::DefaultQuotes).toBool()); + m_ui->checkBoxPunctuation->setChecked( + config()->get("generator/Punctuation", PasswordGenerator::DefaultPunctuation).toBool()); + m_ui->checkBoxDashes->setChecked(config()->get("generator/Dashes", PasswordGenerator::DefaultDashes).toBool()); + m_ui->checkBoxMath->setChecked(config()->get("generator/Math", PasswordGenerator::DefaultMath).toBool()); + m_ui->checkBoxLogograms->setChecked( + config()->get("generator/Logograms", PasswordGenerator::DefaultLogograms).toBool()); m_ui->checkBoxExtASCII->setChecked(config()->get("generator/EASCII", PasswordGenerator::DefaultEASCII).toBool()); + m_ui->checkBoxExtASCIIAdv->setChecked(config()->get("generator/EASCII", PasswordGenerator::DefaultEASCII).toBool()); m_ui->checkBoxExcludeAlike->setChecked( config()->get("generator/ExcludeAlike", PasswordGenerator::DefaultLookAlike).toBool()); m_ui->checkBoxEnsureEvery->setChecked( @@ -120,11 +147,25 @@ void PasswordGeneratorWidget::loadSettings() void PasswordGeneratorWidget::saveSettings() { // Password config - config()->set("generator/LowerCase", m_ui->checkBoxLower->isChecked()); - config()->set("generator/UpperCase", m_ui->checkBoxUpper->isChecked()); - config()->set("generator/Numbers", m_ui->checkBoxNumbers->isChecked()); + if (m_ui->simpleBar->isVisible()) { + config()->set("generator/LowerCase", m_ui->checkBoxLower->isChecked()); + config()->set("generator/UpperCase", m_ui->checkBoxUpper->isChecked()); + config()->set("generator/Numbers", m_ui->checkBoxNumbers->isChecked()); + config()->set("generator/EASCII", m_ui->checkBoxExtASCII->isChecked()); + } else { + config()->set("generator/LowerCase", m_ui->checkBoxLowerAdv->isChecked()); + config()->set("generator/UpperCase", m_ui->checkBoxUpperAdv->isChecked()); + config()->set("generator/Numbers", m_ui->checkBoxNumbersAdv->isChecked()); + config()->set("generator/EASCII", m_ui->checkBoxExtASCIIAdv->isChecked()); + } config()->set("generator/SpecialChars", m_ui->checkBoxSpecialChars->isChecked()); - config()->set("generator/EASCII", m_ui->checkBoxExtASCII->isChecked()); + config()->set("generator/Braces", m_ui->checkBoxBraces->isChecked()); + config()->set("generator/Punctuation", m_ui->checkBoxPunctuation->isChecked()); + config()->set("generator/Quotes", m_ui->checkBoxQuotes->isChecked()); + config()->set("generator/Dashes", m_ui->checkBoxDashes->isChecked()); + config()->set("generator/Math", m_ui->checkBoxMath->isChecked()); + config()->set("generator/Logograms", m_ui->checkBoxLogograms->isChecked()); + config()->set("generator/ExcludedChars", m_ui->editExcludedChars->text()); config()->set("generator/ExcludeAlike", m_ui->checkBoxExcludeAlike->isChecked()); config()->set("generator/EnsureEvery", m_ui->checkBoxEnsureEvery->isChecked()); config()->set("generator/Length", m_ui->spinBoxLength->value()); @@ -276,6 +317,48 @@ void PasswordGeneratorWidget::togglePasswordShown(bool showing) m_ui->togglePasswordButton->blockSignals(blockSignals); } +void PasswordGeneratorWidget::selectSimpleMode() +{ + m_ui->advancedBar->hide(); + m_ui->excludedChars->hide(); + m_ui->checkBoxExcludeAlike->hide(); + m_ui->checkBoxEnsureEvery->hide(); + m_ui->checkBoxUpper->setChecked(m_ui->checkBoxUpperAdv->isChecked()); + m_ui->checkBoxLower->setChecked(m_ui->checkBoxLowerAdv->isChecked()); + m_ui->checkBoxNumbers->setChecked(m_ui->checkBoxNumbersAdv->isChecked()); + m_ui->checkBoxSpecialChars->setChecked(m_ui->checkBoxBraces->isChecked() | m_ui->checkBoxPunctuation->isChecked() + | m_ui->checkBoxQuotes->isChecked() + | m_ui->checkBoxMath->isChecked() + | m_ui->checkBoxDashes->isChecked() + | m_ui->checkBoxLogograms->isChecked()); + m_ui->checkBoxExtASCII->setChecked(m_ui->checkBoxExtASCIIAdv->isChecked()); + m_ui->simpleBar->show(); +} + +void PasswordGeneratorWidget::selectAdvancedMode() +{ + m_ui->simpleBar->hide(); + m_ui->checkBoxUpperAdv->setChecked(m_ui->checkBoxUpper->isChecked()); + m_ui->checkBoxLowerAdv->setChecked(m_ui->checkBoxLower->isChecked()); + m_ui->checkBoxNumbersAdv->setChecked(m_ui->checkBoxNumbers->isChecked()); + m_ui->checkBoxBraces->setChecked(m_ui->checkBoxSpecialChars->isChecked()); + m_ui->checkBoxPunctuation->setChecked(m_ui->checkBoxSpecialChars->isChecked()); + m_ui->checkBoxQuotes->setChecked(m_ui->checkBoxSpecialChars->isChecked()); + m_ui->checkBoxMath->setChecked(m_ui->checkBoxSpecialChars->isChecked()); + m_ui->checkBoxDashes->setChecked(m_ui->checkBoxSpecialChars->isChecked()); + m_ui->checkBoxLogograms->setChecked(m_ui->checkBoxSpecialChars->isChecked()); + m_ui->checkBoxExtASCIIAdv->setChecked(m_ui->checkBoxExtASCII->isChecked()); + m_ui->advancedBar->show(); + m_ui->excludedChars->show(); + m_ui->checkBoxExcludeAlike->show(); + m_ui->checkBoxEnsureEvery->show(); +} + +void PasswordGeneratorWidget::excludeHexChars() +{ + m_ui->editExcludedChars->setText("GHIJKLMNOPQRSTUVWXYZghijklmnopqrstuvwxyz"); +} + void PasswordGeneratorWidget::colorStrengthIndicator(double entropy) { // Take the existing stylesheet and convert the text and background color to arguments @@ -306,24 +389,69 @@ PasswordGenerator::CharClasses PasswordGeneratorWidget::charClasses() { PasswordGenerator::CharClasses classes; - if (m_ui->checkBoxLower->isChecked()) { - classes |= PasswordGenerator::LowerLetters; - } + if (m_ui->simpleBar->isVisible()) { - if (m_ui->checkBoxUpper->isChecked()) { - classes |= PasswordGenerator::UpperLetters; - } + if (m_ui->checkBoxLower->isChecked()) { + classes |= PasswordGenerator::LowerLetters; + } - if (m_ui->checkBoxNumbers->isChecked()) { - classes |= PasswordGenerator::Numbers; - } + if (m_ui->checkBoxUpper->isChecked()) { + classes |= PasswordGenerator::UpperLetters; + } - if (m_ui->checkBoxSpecialChars->isChecked()) { - classes |= PasswordGenerator::SpecialCharacters; - } + if (m_ui->checkBoxNumbers->isChecked()) { + classes |= PasswordGenerator::Numbers; + } - if (m_ui->checkBoxExtASCII->isChecked()) { - classes |= PasswordGenerator::EASCII; + if (m_ui->checkBoxSpecialChars->isChecked()) { + classes |= PasswordGenerator::SpecialCharacters; + } + + if (m_ui->checkBoxExtASCII->isChecked()) { + classes |= PasswordGenerator::EASCII; + } + + } else { + + if (m_ui->checkBoxLowerAdv->isChecked()) { + classes |= PasswordGenerator::LowerLetters; + } + + if (m_ui->checkBoxUpperAdv->isChecked()) { + classes |= PasswordGenerator::UpperLetters; + } + + if (m_ui->checkBoxNumbersAdv->isChecked()) { + classes |= PasswordGenerator::Numbers; + } + + if (m_ui->checkBoxBraces->isChecked()) { + classes |= PasswordGenerator::Braces; + } + + if (m_ui->checkBoxPunctuation->isChecked()) { + classes |= PasswordGenerator::Punctuation; + } + + if (m_ui->checkBoxQuotes->isChecked()) { + classes |= PasswordGenerator::Quotes; + } + + if (m_ui->checkBoxDashes->isChecked()) { + classes |= PasswordGenerator::Dashes; + } + + if (m_ui->checkBoxMath->isChecked()) { + classes |= PasswordGenerator::Math; + } + + if (m_ui->checkBoxLogograms->isChecked()) { + classes |= PasswordGenerator::Logograms; + } + + if (m_ui->checkBoxExtASCIIAdv->isChecked()) { + classes |= PasswordGenerator::EASCII; + } } return classes; @@ -361,7 +489,22 @@ void PasswordGeneratorWidget::updateGenerator() if (classes.testFlag(PasswordGenerator::Numbers)) { minLength++; } - if (classes.testFlag(PasswordGenerator::SpecialCharacters)) { + if (classes.testFlag(PasswordGenerator::Braces)) { + minLength++; + } + if (classes.testFlag(PasswordGenerator::Punctuation)) { + minLength++; + } + if (classes.testFlag(PasswordGenerator::Quotes)) { + minLength++; + } + if (classes.testFlag(PasswordGenerator::Dashes)) { + minLength++; + } + if (classes.testFlag(PasswordGenerator::Math)) { + minLength++; + } + if (classes.testFlag(PasswordGenerator::Logograms)) { minLength++; } if (classes.testFlag(PasswordGenerator::EASCII)) { @@ -382,6 +525,11 @@ void PasswordGeneratorWidget::updateGenerator() m_passwordGenerator->setLength(m_ui->spinBoxLength->value()); m_passwordGenerator->setCharClasses(classes); + if (m_ui->simpleBar->isVisible()) { + m_passwordGenerator->setExcludedChars(""); + } else { + m_passwordGenerator->setExcludedChars(m_ui->editExcludedChars->text()); + } m_passwordGenerator->setFlags(flags); if (m_passwordGenerator->isValid()) { diff --git a/src/gui/PasswordGeneratorWidget.h b/src/gui/PasswordGeneratorWidget.h index 84d5484b9..5b65d7d22 100644 --- a/src/gui/PasswordGeneratorWidget.h +++ b/src/gui/PasswordGeneratorWidget.h @@ -65,6 +65,9 @@ private slots: void updateButtonsEnabled(const QString& password); void updatePasswordStrength(const QString& password); void togglePasswordShown(bool hidden); + void selectSimpleMode(); + void selectAdvancedMode(); + void excludeHexChars(); void passwordSliderMoved(); void passwordSpinBoxChanged(); diff --git a/src/gui/PasswordGeneratorWidget.ui b/src/gui/PasswordGeneratorWidget.ui index 03f70806f..e196ff89f 100644 --- a/src/gui/PasswordGeneratorWidget.ui +++ b/src/gui/PasswordGeneratorWidget.ui @@ -6,8 +6,8 @@ 0 0 - 547 - 352 + 571 + 394 @@ -19,7 +19,7 @@ - + QLayout::SetMinimumSize @@ -175,12 +175,6 @@ QProgressBar::chunk { - - - 0 - 0 - - QTabWidget::North @@ -204,155 +198,598 @@ QProgressBar::chunk { - - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Upper Case Letters - - - A-Z - - - true - - - optionButtons - - - - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Lower Case Letters - - - a-z - - - true - - - optionButtons - - - - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Numbers - - - 0-9 - - - true - - - optionButtons - - - - - - - - 0 - 25 - - - - Qt::StrongFocus - - - Special Characters - - - /*_& ... - - - true - - - optionButtons - - - - - - - - 0 - 25 - - - - - 16777215 - 16777215 - - - - Qt::StrongFocus - - - Extended ASCII - - - Extended ASCII - - - true - - - optionButtons - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Upper Case Letters + + + A-Z + + + true + + + optionButtons + + + + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Lower Case Letters + + + a-z + + + true + + + optionButtons + + + + + + + + 0 + 25 + + + + Qt::StrongFocus + + + Numbers + + + 0-9 + + + true + + + optionButtons + + + + + + + true + + + + 0 + 25 + + + + Qt::StrongFocus + + + Special Characters + + + /*_& ... + + + true + + + optionButtons + + + + + + + + 0 + 25 + + + + + 16777215 + 16777215 + + + + Qt::StrongFocus + + + Extended ASCII + + + ExtendedASCII + + + true + + + optionButtons + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 25 + + + + Switch to advanced mode + + + Advanced + + + optionButtons + + + + + + + + + + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 40 + 25 + + + + Qt::StrongFocus + + + Upper Case Letters A to F + + + A-Z + + + true + + + optionButtons + + + + + + + + 40 + 25 + + + + Qt::StrongFocus + + + Lower Case Letters A to F + + + a-z + + + true + + + optionButtons + + + + + + + + + + + + 40 + 25 + + + + Qt::StrongFocus + + + Numbers + + + 0-9 + + + true + + + optionButtons + + + + + + + + 40 + 25 + + + + Qt::StrongFocus + + + Braces + + + {[( + + + true + + + optionButtons + + + + + + + + + + + + 35 + 25 + + + + Qt::StrongFocus + + + Punctuation + + + .,:; + + + true + + + optionButtons + + + + + + + + 35 + 25 + + + + Qt::StrongFocus + + + Quotes + + + " ' + + + true + + + optionButtons + + + + + + + + + + + + 60 + 25 + + + + Qt::StrongFocus + + + Math + + + <*+!?= + + + true + + + optionButtons + + + + + + + + 60 + 25 + + + + Qt::StrongFocus + + + Dashes + + + \_|-/ + + + true + + + optionButtons + + + + + + + + + + + + 105 + 25 + + + + Qt::StrongFocus + + + Logograms + + + #$%&&@^`~ + + + true + + + optionButtons + + + + + + + + 105 + 25 + + + + Qt::StrongFocus + + + Extended ASCII + + + ExtendedASCII + + + true + + + optionButtons + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 25 + + + + Switch to simple mode + + + Simple + + + optionButtons + + + + + + + + + + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 25 + + + + Character set to exclude from generated password + + + true + + + + + + + Do not include: + + + + + + + + 0 + 25 + + + + Add non-hex letters to "do not include" list + + + Hex + + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + Excluded characters: "0", "1", "l", "I", "O", "|", "﹒" + Exclude look-alike characters @@ -534,6 +971,19 @@ QProgressBar::chunk { + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -585,9 +1035,6 @@ QProgressBar::chunk { - - - @@ -607,7 +1054,29 @@ QProgressBar::chunk { checkBoxLower checkBoxNumbers checkBoxSpecialChars + checkBoxExtASCII + buttonAdvancedMode + checkBoxUpperAdv + checkBoxNumbersAdv + checkBoxPunctuation + checkBoxMath + checkBoxLogograms + checkBoxBraces + checkBoxQuotes + checkBoxDashes + checkBoxExtASCIIAdv + editExcludedChars + buttonSimpleMode checkBoxExcludeAlike + checkBoxEnsureEvery + tabWidget + comboBoxWordList + sliderWordCount + spinBoxWordCount + editWordSeparator + buttonGenerate + buttonCopy + buttonApply diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index df5c37f83..261c9627e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -190,6 +190,9 @@ add_unit_test(NAME testykchallengeresponsekey add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testtools SOURCES TestTools.cpp + LIBS ${TEST_LIBRARIES}) + if(WITH_GUI_TESTS) add_subdirectory(gui) endif(WITH_GUI_TESTS) diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp index e5cc75c2c..f04aabcb5 100644 --- a/tests/TestKeys.cpp +++ b/tests/TestKeys.cpp @@ -24,7 +24,6 @@ #include "config-keepassx-tests.h" #include "core/Metadata.h" -#include "core/Tools.h" #include "crypto/Crypto.h" #include "crypto/CryptoHash.h" #include "crypto/kdf/AesKdf.h" diff --git a/tests/TestModified.cpp b/tests/TestModified.cpp index 0a9f0cfa2..60ea68e5f 100644 --- a/tests/TestModified.cpp +++ b/tests/TestModified.cpp @@ -23,7 +23,6 @@ #include "core/Database.h" #include "core/Group.h" #include "core/Metadata.h" -#include "core/Tools.h" #include "crypto/Crypto.h" QTEST_GUILESS_MAIN(TestModified) diff --git a/tests/TestTools.cpp b/tests/TestTools.cpp new file mode 100644 index 000000000..c3acd3c6d --- /dev/null +++ b/tests/TestTools.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "TestTools.h" + +#include +#include + +QTEST_GUILESS_MAIN(TestTools) + +namespace +{ + QString createDecimal(QString wholes, QString fractions, QString unit) + { + return wholes + QLocale().decimalPoint() + fractions + " " + unit; + } +} + +void TestTools::testHumanReadableFileSize() +{ + constexpr auto kibibyte = 1024u; + using namespace Tools; + + QCOMPARE(createDecimal("1", "00", "B"), humanReadableFileSize(1)); + QCOMPARE(createDecimal("1", "00", "KiB"), humanReadableFileSize(kibibyte)); + QCOMPARE(createDecimal("1", "00", "MiB"), humanReadableFileSize(kibibyte * kibibyte)); + QCOMPARE(createDecimal("1", "00", "GiB"), humanReadableFileSize(kibibyte * kibibyte * kibibyte)); + + QCOMPARE(QString("100 B"), humanReadableFileSize(100, 0)); + QCOMPARE(createDecimal("1", "10", "KiB"), humanReadableFileSize(kibibyte + 100)); + QCOMPARE(createDecimal("1", "001", "KiB"), humanReadableFileSize(kibibyte + 1, 3)); + QCOMPARE(createDecimal("15", "00", "KiB"), humanReadableFileSize(kibibyte * 15)); +} + +void TestTools::testIsHex() +{ + QVERIFY(Tools::isHex("0123456789abcdefABCDEF")); + QVERIFY(not Tools::isHex(QByteArray("0xnothex"))); +} + +void TestTools::testIsBase64() +{ + QVERIFY(Tools::isBase64(QByteArray("1234"))); + QVERIFY(Tools::isBase64(QByteArray("123="))); + QVERIFY(Tools::isBase64(QByteArray("12=="))); + QVERIFY(Tools::isBase64(QByteArray("abcd9876MN=="))); + QVERIFY(Tools::isBase64(QByteArray("abcd9876DEFGhijkMNO="))); + QVERIFY(not Tools::isBase64(QByteArray("abcd123=="))); + QVERIFY(not Tools::isBase64(QByteArray("abc_"))); + QVERIFY(not Tools::isBase64(QByteArray("123"))); +} diff --git a/tests/TestTools.h b/tests/TestTools.h new file mode 100644 index 000000000..56d354eca --- /dev/null +++ b/tests/TestTools.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_TESTTOOLS_H +#define KEEPASSX_TESTTOOLS_H + +#include "core/Tools.h" + +class TestTools : public QObject +{ + Q_OBJECT +private slots: + void testHumanReadableFileSize(); + void testIsHex(); + void testIsBase64(); +}; + +#endif // KEEPASSX_TESTTOOLS_H