Compare commits
1 Commits
feature/fi
...
refactor/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d141d4a5e |
@@ -54,7 +54,6 @@ IncludeCategories:
|
||||
IndentCaseLabels: false
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertNewlineAtEOF: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
|
||||
7
.github/CONTRIBUTING.md
vendored
@@ -15,7 +15,6 @@ These are just guidelines, not rules. Use your best judgment, and feel free to p
|
||||
* [Bug reports](#bug-reports)
|
||||
* [Discuss with the team](#discuss-with-the-team)
|
||||
* [Your first code contribution](#your-first-code-contribution)
|
||||
* [Using AI](#using-ai)
|
||||
* [Pull requests](#pull-requests)
|
||||
* [Translations](#translations)
|
||||
|
||||
@@ -75,10 +74,6 @@ Unsure where to begin contributing to KeePassXC? You can start by looking throug
|
||||
|
||||
Both issue lists are sorted by total number of comments. While not perfect, looking at the number of comments on an issue can give a general idea of how much an impact a given change will have.
|
||||
|
||||
### Using AI
|
||||
|
||||
Generative AI is fast becoming a first-party feature in most development environments, including GitHub itself. If you use Generative AI to write the vast majority of your submission (e.g., agent-based or vibe coding) then you **must document your use of AI** in your pull request. Please include the service you used and/or model that generated the code. All code submissions go through a rigorous review process regardless of the development workflow used.
|
||||
|
||||
### Pull requests
|
||||
|
||||
Along with our desire to hear your feedback and suggestions, we're also interested in accepting direct assistance in the form of code.
|
||||
@@ -87,7 +82,7 @@ All pull requests must comply with the above requirements and with the [stylegui
|
||||
|
||||
### Translations
|
||||
|
||||
Translations are managed on [Transifex](https://explore.transifex.com/keepassxc/keepassxc/) which offers a web interface.
|
||||
Translations are managed on [Transifex](https://www.transifex.com/keepassxc/keepassxc/) which offers a web interface.
|
||||
Please join an existing language team or request a new one if there is none.
|
||||
|
||||
If you open a Pull Request with new strings that require translations, you will need to run the following:
|
||||
|
||||
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,13 +1,12 @@
|
||||
[NOTE]: # ( Describe your changes in detail. Explain large or complex code modifications. )
|
||||
[NOTE]: # ( Describe your changes in detail, why is this change required? )
|
||||
[NOTE]: # ( Explain large or complex code modifications. )
|
||||
[NOTE]: # ( If it fixes an open issue, please add "Fixes #XXX". )
|
||||
[NOTE]: # ( If you used Generative AI to write the majority of your code, you must state this. )
|
||||
|
||||
|
||||
## Screenshots
|
||||
[NOTE]: # ( Do not include screenshots of your actual database! )
|
||||
[TIP]: # ( Use View -> Allow Screen Capture )
|
||||
|
||||
|
||||
## Testing strategy
|
||||
[NOTE]: # ( Please describe in detail how you tested your changes. )
|
||||
[TIP]: # ( We expect new code to be covered by unit tests and include helpful comments. )
|
||||
|
||||
38
.github/copilot-instructions.md
vendored
@@ -1,38 +0,0 @@
|
||||
This is a C++ based repository that uses Qt5 as a primary support and GUI library. This repository is for a password manager application that stores passwords
|
||||
and other highly sensitive information. The data format that passwords are stored is called KDBX which is a mixed binary and XML format that is fully encrypted
|
||||
at rest. This format is unpacked into a series of data structures: Database, Groups, and Entries. Please follow these guidelines when contributing:
|
||||
|
||||
## Code Standards
|
||||
|
||||
### Required Before Each Commit
|
||||
- Run `cmake --build . --target format` before committing any changes to ensure proper code formatting
|
||||
- This will run clang-format to ensure all code conforms to the style guide
|
||||
- From the checkout directory, also run `./release-tool i18n lupdate` to update translation files
|
||||
|
||||
### Development Flow
|
||||
- Setup Build Folder: `mkdir build; cd build`
|
||||
- Configure: `cmake -G Ninja -DWITH_XC_ALL=ON -DWITH_GUI_TESTS=ON ..`
|
||||
- Build: `cmake --build . -- -j $(nproc)`
|
||||
- Test: `ctest`
|
||||
|
||||
## Repository Structure
|
||||
- `docs/topics`: Documentation written in asciidoctor syntax
|
||||
- `src/`: Main source code files are under this subdirectory
|
||||
- `src/autotype`: Code that emulates a virtual keyboard to type into interfaces
|
||||
- `src/browser`: Interface with the KeePassXC Browser Extension using a JSON-based protocol
|
||||
- `src/cli`: Command Line Interface code
|
||||
- `src/core`: Contains files that define the data model and other shared code structures
|
||||
- `src/format`: Code for import/export and reading/writing of KDBX databases
|
||||
- `src/fdosecrets`: freedesktop.org Secret Service interface code
|
||||
- `src/quickunlock`: Quick unlock interfaces for various platforms
|
||||
- `src/sshagent`: SSH Agent interface code to load private keys from the database into ssh-agent
|
||||
- `tests/`: Test source code files
|
||||
- `tests/gui`: GUI test source code files
|
||||
|
||||
## Key Guidelines
|
||||
1. Follow C++20 and Qt5 best practices and idiomatic patterns
|
||||
2. Maintain existing code structure and organization
|
||||
3. Prefer not to edit cryptographic handling code or other sensitive parts of the code base
|
||||
4. Write unit tests for new functionality using QTest scaffolding
|
||||
5. Suggest changes to the `docs/topics` folder when appropriate
|
||||
6. Unless the change is simple, don't actually make edits to .ui files, just suggest the changes needed
|
||||
29
.github/workflows/copilot-setup-steps.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: "Copilot Setup Steps"
|
||||
|
||||
# Setup the environment for Copilot agents to run in
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
|
||||
jobs:
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Needed to clone the repository
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Install dependencies
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install --no-install-recommends build-essential cmake g++ ninja-build qtbase5-dev qtbase5-private-dev qttools5-dev qttools5-dev-tools libqt5svg5-dev libargon2-dev libkeyutils-dev libminizip-dev libbotan-2-dev libqrencode-dev zlib1g-dev asciidoctor libreadline-dev libpcsclite-dev libusb-1.0-0-dev libxi-dev libxtst-dev libqt5x11extras5-dev
|
||||
527
CMakeLists.txt
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
# Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
# Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -14,119 +14,80 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cmake_minimum_required(VERSION 3.10.0)
|
||||
cmake_minimum_required(VERSION 3.16.0)
|
||||
|
||||
project(KeePassXC)
|
||||
set(APP_ID "org.keepassxc.${PROJECT_NAME}")
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
|
||||
"Choose the type of build, options are: Debug Release RelWithDebInfo Profile"
|
||||
FORCE)
|
||||
endif()
|
||||
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
|
||||
if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug" OR CMAKE_BUILD_TYPE_LOWER STREQUAL "relwithdebinfo")
|
||||
set(IS_DEBUG_BUILD TRUE)
|
||||
endif()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
||||
|
||||
# Support Visual Studio Code
|
||||
include(CMakeToolsHelpers OPTIONAL)
|
||||
include(FeatureSummary)
|
||||
include(KPXCMacDeployHelpers)
|
||||
|
||||
include(CheckCCompilerFlag)
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(CheckCXXSourceCompiles)
|
||||
|
||||
option(WITH_TESTS "Enable building of unit tests" ON)
|
||||
option(WITH_GUI_TESTS "Enable building of GUI tests" OFF)
|
||||
option(WITH_DEV_BUILD "Use only for development. Disables/warns about deprecated methods." OFF)
|
||||
option(WITH_ASAN "Enable address sanitizer checks (Linux / macOS only)" OFF)
|
||||
option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF)
|
||||
option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON)
|
||||
option(WITH_CCACHE "Use ccache for build" OFF)
|
||||
|
||||
set(WITH_XC_ALL OFF CACHE BOOL "Build in all available plugins")
|
||||
|
||||
option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
|
||||
option(WITH_XC_NETWORKING "Include networking code (e.g. for downloading website icons)." OFF)
|
||||
option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF)
|
||||
option(WITH_XC_BROWSER_PASSKEYS "Passkeys support for browser integration." OFF)
|
||||
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
|
||||
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
|
||||
option(WITH_XC_KEESHARE "Sharing integration with KeeShare" OFF)
|
||||
option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON)
|
||||
if(UNIX AND NOT APPLE)
|
||||
option(WITH_XC_FDOSECRETS "Implement freedesktop.org Secret Storage Spec server side API." OFF)
|
||||
endif()
|
||||
option(WITH_XC_DOCS "Enable building of documentation" ON)
|
||||
|
||||
set(WITH_XC_X11 ON CACHE BOOL "Enable building with X11 deps")
|
||||
|
||||
if(APPLE)
|
||||
# Perform the platform checks before applying the stricter compiler flags.
|
||||
# Otherwise the kSecAccessControlTouchIDCurrentSet deprecation warning will result in an error.
|
||||
try_compile(XC_APPLE_COMPILER_SUPPORT_BIOMETRY
|
||||
${CMAKE_CURRENT_BINARY_DIR}/tiometry_test/
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cmake/compiler-checks/macos/control_biometry_support.mm)
|
||||
message(STATUS "Biometry compiler support: ${XC_APPLE_COMPILER_SUPPORT_BIOMETRY}")
|
||||
|
||||
try_compile(XC_APPLE_COMPILER_SUPPORT_TOUCH_ID
|
||||
${CMAKE_CURRENT_BINARY_DIR}/touch_id_test/
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cmake/compiler-checks/macos/control_touch_id_support.mm)
|
||||
message(STATUS "Touch ID compiler support: ${XC_APPLE_COMPILER_SUPPORT_TOUCH_ID}")
|
||||
|
||||
try_compile(XC_APPLE_COMPILER_SUPPORT_WATCH
|
||||
${CMAKE_CURRENT_BINARY_DIR}/tiometry_test/
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cmake/compiler-checks/macos/control_watch_support.mm)
|
||||
message(STATUS "Apple watch compiler support: ${XC_APPLE_COMPILER_SUPPORT_WATCH}")
|
||||
endif()
|
||||
|
||||
if(WITH_CCACHE)
|
||||
# Use the Compiler Cache (ccache) program
|
||||
# (install with: sudo apt get ccache)
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(NOT CCACHE_FOUND)
|
||||
message(FATAL_ERROR "ccache requested but cannot be found.")
|
||||
endif()
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_FOUND})
|
||||
endif()
|
||||
|
||||
if(WITH_XC_ALL)
|
||||
# Enable all options (except update check and docs)
|
||||
set(WITH_XC_AUTOTYPE ON)
|
||||
set(WITH_XC_NETWORKING ON)
|
||||
set(WITH_XC_BROWSER ON)
|
||||
set(WITH_XC_BROWSER_PASSKEYS ON)
|
||||
set(WITH_XC_YUBIKEY ON)
|
||||
set(WITH_XC_SSHAGENT ON)
|
||||
set(WITH_XC_KEESHARE ON)
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(WITH_XC_FDOSECRETS ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Prefer WITH_XC_NETWORKING setting over WITH_XC_UPDATECHECK
|
||||
if(NOT WITH_XC_NETWORKING AND WITH_XC_UPDATECHECK)
|
||||
message(STATUS "Disabling WITH_XC_UPDATECHECK because WITH_XC_NETWORKING is disabled")
|
||||
set(WITH_XC_UPDATECHECK OFF)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE AND NOT WITH_XC_X11)
|
||||
message(STATUS "Disabling WITH_XC_AUTOTYPE because WITH_XC_X11 is disabled")
|
||||
set(WITH_XC_AUTOTYPE OFF)
|
||||
endif()
|
||||
|
||||
# Version Number
|
||||
set(KEEPASSXC_VERSION_MAJOR "2")
|
||||
set(KEEPASSXC_VERSION_MINOR "8")
|
||||
set(KEEPASSXC_VERSION_PATCH "0")
|
||||
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
|
||||
set(OVERRIDE_VERSION "" CACHE STRING "Override the KeePassXC Version for Snapshot builds")
|
||||
|
||||
set(KEEPASSXC_BUILD_TYPE "Snapshot" CACHE STRING "Set KeePassXC build type to distinguish between stable releases and snapshots")
|
||||
set_property(CACHE KEEPASSXC_BUILD_TYPE PROPERTY STRINGS Snapshot Release PreRelease)
|
||||
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
|
||||
|
||||
# CMake Modules
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
||||
|
||||
include(CMakeToolsHelpers OPTIONAL) # Support Visual Studio Code
|
||||
include(FeatureSummary)
|
||||
include(KPXCMacDeployHelpers)
|
||||
include(CLangFormat)
|
||||
|
||||
include(CompilerFlags)
|
||||
include(CheckCCompilerFlag)
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(CheckCXXSourceCompiles)
|
||||
|
||||
# Build Scope
|
||||
option(WITH_TESTS "Enable building of unit tests" ON)
|
||||
option(WITH_GUI_TESTS "Enable building of GUI tests" OFF)
|
||||
option(WITH_WARN_DEPRECATED "Development only: warn about deprecated methods, including Qt." OFF)
|
||||
option(WITH_ASAN "Enable address sanitizer checks (Linux / macOS only)" OFF)
|
||||
option(WITH_COVERAGE "Use to build with coverage tests (GCC only)." OFF)
|
||||
option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON)
|
||||
option(WITH_CCACHE "Use ccache for build" OFF)
|
||||
option(WITH_X11 "Enable building with X11 dependencies" ON)
|
||||
|
||||
# Advanced Features Control
|
||||
option(KPXC_MINIMAL "Build KeePassXC with the minimal feature set required for basic usage" OFF)
|
||||
option(KPXC_FEATURE_BROWSER "Browser integration and passkeys support" ON)
|
||||
option(KPXC_FEATURE_SSHAGENT "SSH Agent integration" ON)
|
||||
option(KPXC_FEATURE_FDOSECRETS "freedesktop.org Secret Service integration; replace system keyring" ON)
|
||||
|
||||
if(KPXC_MINIMAL)
|
||||
# Disable advanced features in minimal mode
|
||||
set(KPXC_FEATURE_BROWSER OFF)
|
||||
set(KPXC_FEATURE_SSHAGENT OFF)
|
||||
set(KPXC_FEATURE_FDOSECRETS OFF)
|
||||
endif()
|
||||
|
||||
# Minor Feature Flags
|
||||
option(KPXC_FEATURE_NETWORK "Include code that reaches out to external networks (e.g. downloading icons)" ON)
|
||||
option(KPXC_FEATURE_UPDATES "Include automatic update checks; disable for managed distributions" ON)
|
||||
option(KPXC_FEATURE_DOCS "Build offline documentation; requires asciidoctor tool" ON)
|
||||
|
||||
# Reconcile update feature with overall network feature
|
||||
if(NOT KPXC_FEATURE_NETWORK AND KPXC_FEATURE_UPDATES)
|
||||
message(STATUS "Disabling KPXC_FEATURE_UPDATES because KPXC_FEATURE_NETWORK is disabled")
|
||||
set(KPXC_FEATURE_UPDATES OFF)
|
||||
endif()
|
||||
|
||||
# FDO Secrets is only available on Linux
|
||||
if(NOT UNIX OR APPLE OR HAIKU)
|
||||
set(KPXC_FEATURE_FDOSECRETS OFF)
|
||||
endif()
|
||||
|
||||
# Define feature summaries
|
||||
add_feature_info("Browser" KPXC_FEATURE_BROWSER "Browser integration and passkeys support")
|
||||
add_feature_info("SSH Agent" KPXC_FEATURE_SSHAGENT "SSH Agent integration")
|
||||
if(UNIX AND NOT APPLE)
|
||||
add_feature_info("Secret Service" KPXC_FEATURE_FDOSECRETS "Replace system keyring with freedesktop.org Secret Service integration")
|
||||
endif()
|
||||
add_feature_info("Networking" KPXC_FEATURE_NETWORK "Code that can reach out to external networks is included (e.g. downloading icons)")
|
||||
add_feature_info("Update Checks" KPXC_FEATURE_UPDATES "Periodic update checks can be performed")
|
||||
add_feature_info("Documentation" KPXC_FEATURE_DOCS "Offline documentation")
|
||||
|
||||
# Retrieve git HEAD revision hash
|
||||
set(GIT_HEAD_OVERRIDE "" CACHE STRING "Manually set the Git HEAD hash when missing (eg, when no .git folder exists)")
|
||||
@@ -142,7 +103,12 @@ elseif(EXISTS ${CMAKE_SOURCE_DIR}/.gitrev)
|
||||
endif()
|
||||
message(STATUS "Found Git HEAD Revision: ${GIT_HEAD}\n")
|
||||
|
||||
# Check if on a tag, if so build as a release
|
||||
# KeePassXC Versioning and Build Type
|
||||
set(OVERRIDE_VERSION "" CACHE STRING "Override the KeePassXC Version for Snapshot builds")
|
||||
set(KEEPASSXC_BUILD_TYPE "Snapshot" CACHE STRING "Set KeePassXC build type to distinguish between stable releases and snapshots")
|
||||
set_property(CACHE KEEPASSXC_BUILD_TYPE PROPERTY STRINGS Snapshot Release)
|
||||
|
||||
# Check if on a tag or has .version file, if so build as a release
|
||||
execute_process(COMMAND git tag --points-at HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_TAG
|
||||
@@ -157,67 +123,103 @@ endif()
|
||||
|
||||
string(REGEX REPLACE "(\r?\n)+" "" OVERRIDE_VERSION "${OVERRIDE_VERSION}")
|
||||
if(OVERRIDE_VERSION)
|
||||
if(OVERRIDE_VERSION MATCHES "^[\\.0-9]+-beta[0-9]*")
|
||||
set(KEEPASSXC_BUILD_TYPE "PreRelease")
|
||||
set(KEEPASSXC_VERSION ${OVERRIDE_VERSION})
|
||||
elseif(OVERRIDE_VERSION MATCHES "^[\\.0-9]+$")
|
||||
if(OVERRIDE_VERSION MATCHES "^[\\.0-9]+$")
|
||||
set(KEEPASSXC_BUILD_TYPE "Release")
|
||||
set(KEEPASSXC_VERSION ${OVERRIDE_VERSION})
|
||||
else()
|
||||
set(KEEPASSXC_BUILD_TYPE "Snapshot")
|
||||
set(KEEPASSXC_VERSION ${OVERRIDE_VERSION})
|
||||
endif()
|
||||
else()
|
||||
if(KEEPASSXC_BUILD_TYPE STREQUAL "PreRelease")
|
||||
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-preview")
|
||||
elseif(KEEPASSXC_BUILD_TYPE STREQUAL "Snapshot")
|
||||
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-snapshot")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(KEEPASSXC_BUILD_TYPE STREQUAL "Release")
|
||||
set(KEEPASSXC_BUILD_TYPE_RELEASE ON)
|
||||
elseif(KEEPASSXC_BUILD_TYPE STREQUAL "PreRelease")
|
||||
set(KEEPASSXC_BUILD_TYPE_PRE_RELEASE ON)
|
||||
else()
|
||||
set(KEEPASSXC_BUILD_TYPE_SNAPSHOT ON)
|
||||
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-snapshot")
|
||||
endif()
|
||||
|
||||
message(STATUS "Setting up build for KeePassXC v${KEEPASSXC_VERSION}\n")
|
||||
|
||||
# Distribution info
|
||||
set(KEEPASSXC_DIST ON)
|
||||
set(KEEPASSXC_DIST_TYPE "Other" CACHE STRING "KeePassXC Distribution Type")
|
||||
set_property(CACHE KEEPASSXC_DIST_TYPE PROPERTY STRINGS Snap AppImage Flatpak Other)
|
||||
set(KEEPASSXC_DIST_TYPE "Native" CACHE STRING "KeePassXC Distribution Type")
|
||||
set_property(CACHE KEEPASSXC_DIST_TYPE PROPERTY STRINGS Snap AppImage Flatpak Native)
|
||||
if(KEEPASSXC_DIST_TYPE STREQUAL "Snap")
|
||||
set(KEEPASSXC_DIST_SNAP ON)
|
||||
elseif(KEEPASSXC_DIST_TYPE STREQUAL "AppImage")
|
||||
set(KEEPASSXC_DIST_APPIMAGE ON)
|
||||
elseif(KEEPASSXC_DIST_TYPE STREQUAL "Flatpak")
|
||||
set(KEEPASSXC_DIST_FLATPAK ON)
|
||||
elseif(KEEPASSXC_DIST_TYPE STREQUAL "Other")
|
||||
unset(KEEPASSXC_DIST)
|
||||
endif()
|
||||
|
||||
# Standards
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Compiler Features
|
||||
if(APPLE)
|
||||
# Perform the platform checks before applying the stricter compiler flags.
|
||||
# Otherwise the kSecAccessControlTouchIDCurrentSet deprecation warning will result in an error.
|
||||
try_compile(XC_APPLE_COMPILER_SUPPORT_BIOMETRY
|
||||
${CMAKE_BINARY_DIR}/macos-trycompile/
|
||||
${CMAKE_SOURCE_DIR}/cmake/compiler-checks/control_biometry_support.mm)
|
||||
message(STATUS "Biometry compiler support: ${XC_APPLE_COMPILER_SUPPORT_BIOMETRY}")
|
||||
|
||||
try_compile(XC_APPLE_COMPILER_SUPPORT_TOUCH_ID
|
||||
${CMAKE_BINARY_DIR}/macos-trycompile/
|
||||
${CMAKE_SOURCE_DIR}/cmake/compiler-checks/control_touch_id_support.mm)
|
||||
message(STATUS "Touch ID compiler support: ${XC_APPLE_COMPILER_SUPPORT_TOUCH_ID}")
|
||||
|
||||
try_compile(XC_APPLE_COMPILER_SUPPORT_WATCH
|
||||
${CMAKE_BINARY_DIR}/macos-trycompile/
|
||||
${CMAKE_SOURCE_DIR}/cmake/compiler-checks/control_watch_support.mm)
|
||||
message(STATUS "Apple watch compiler support: ${XC_APPLE_COMPILER_SUPPORT_WATCH}")
|
||||
|
||||
try_compile(HAVE_PT_DENY_ATTACH
|
||||
${CMAKE_BINARY_DIR}/macos-trycompile/
|
||||
${CMAKE_SOURCE_DIR}/cmake/compiler-checks/ptrace_deny_attach.cpp)
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
check_cxx_source_compiles("#include <sys/prctl.h>
|
||||
int main() { prctl(PR_SET_DUMPABLE, 0); return 0; }"
|
||||
HAVE_PR_SET_DUMPABLE)
|
||||
|
||||
check_cxx_source_compiles("#include <malloc.h>
|
||||
int main() { return 0; }"
|
||||
HAVE_MALLOC_H)
|
||||
|
||||
check_cxx_source_compiles("#include <malloc.h>
|
||||
int main() { malloc_usable_size(NULL); return 0; }"
|
||||
HAVE_MALLOC_USABLE_SIZE)
|
||||
|
||||
check_cxx_source_compiles("#include <sys/resource.h>
|
||||
int main() {
|
||||
struct rlimit limit;
|
||||
limit.rlim_cur = 0;
|
||||
limit.rlim_max = 0;
|
||||
setrlimit(RLIMIT_CORE, &limit);
|
||||
return 0;
|
||||
}" HAVE_RLIMIT_CORE)
|
||||
endif()
|
||||
|
||||
# ccache support
|
||||
if(WITH_CCACHE)
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(NOT CCACHE_FOUND)
|
||||
message(FATAL_ERROR "ccache requested but cannot be found.")
|
||||
endif()
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_FOUND})
|
||||
endif()
|
||||
|
||||
# Create position independent code for shared libraries and executables
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14.0")
|
||||
cmake_policy(SET CMP0083 NEW)
|
||||
include(CheckPIESupported)
|
||||
check_pie_supported()
|
||||
endif()
|
||||
|
||||
# Find Botan early since the version affects subsequent compiler options
|
||||
find_package(Botan REQUIRED)
|
||||
if(BOTAN_VERSION VERSION_GREATER_EQUAL "3.0.0")
|
||||
set(WITH_XC_BOTAN3 TRUE)
|
||||
elseif(BOTAN_VERSION VERSION_LESS "2.11.0")
|
||||
# Check for minimum Botan version
|
||||
message(FATAL_ERROR "Botan 2.11.0 or higher is required")
|
||||
endif()
|
||||
include_directories(SYSTEM ${BOTAN_INCLUDE_DIR})
|
||||
|
||||
# Create position independent code for shared libraries and executables
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
if("${CMAKE_SIZEOF_VOID_P}" EQUAL "4")
|
||||
set(IS_32BIT TRUE)
|
||||
endif()
|
||||
@@ -235,66 +237,7 @@ if("${CMAKE_CXX_COMPILER}" MATCHES "clang(\\+\\+)?$"
|
||||
set(CMAKE_COMPILER_IS_CLANGXX 1)
|
||||
endif()
|
||||
|
||||
macro(add_gcc_compiler_cxxflags FLAGS)
|
||||
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FLAGS}")
|
||||
endif()
|
||||
endmacro(add_gcc_compiler_cxxflags)
|
||||
|
||||
macro(add_gcc_compiler_cflags FLAGS)
|
||||
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_CLANG)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FLAGS}")
|
||||
endif()
|
||||
endmacro(add_gcc_compiler_cflags)
|
||||
|
||||
macro(add_gcc_compiler_flags FLAGS)
|
||||
add_gcc_compiler_cxxflags("${FLAGS}")
|
||||
add_gcc_compiler_cflags("${FLAGS}")
|
||||
endmacro(add_gcc_compiler_flags)
|
||||
|
||||
# Copies of above macros that first ensure the compiler understands a given flag
|
||||
# Because check_*_compiler_flag() sets -D with name, need to provide "safe" FLAGNAME
|
||||
macro(check_add_gcc_compiler_cxxflag FLAG FLAGNAME)
|
||||
check_cxx_compiler_flag("${FLAG}" CXX_HAS${FLAGNAME})
|
||||
if(CXX_HAS${FLAGNAME})
|
||||
add_gcc_compiler_cxxflags("${FLAG}")
|
||||
endif()
|
||||
endmacro(check_add_gcc_compiler_cxxflag)
|
||||
|
||||
macro(check_add_gcc_compiler_cflag FLAG FLAGNAME)
|
||||
check_c_compiler_flag("${FLAG}" CC_HAS${FLAGNAME})
|
||||
if(CC_HAS${FLAGNAME})
|
||||
add_gcc_compiler_cflags("${FLAG}")
|
||||
endif()
|
||||
endmacro(check_add_gcc_compiler_cflag)
|
||||
|
||||
# This is the "front-end" for the above macros
|
||||
# Optionally takes additional parameter(s) with language to check (currently "C" or "CXX")
|
||||
macro(check_add_gcc_compiler_flag FLAG)
|
||||
string(REGEX REPLACE "[-=]" "_" FLAGNAME "${FLAG}")
|
||||
set(check_lang_spec ${ARGN})
|
||||
list(LENGTH check_lang_spec num_extra_args)
|
||||
set(langs C CXX)
|
||||
if(num_extra_args GREATER 0)
|
||||
set(langs "${check_lang_spec}")
|
||||
endif()
|
||||
if("C" IN_LIST langs)
|
||||
check_add_gcc_compiler_cflag("${FLAG}" "${FLAGNAME}")
|
||||
endif()
|
||||
if("CXX" IN_LIST langs)
|
||||
check_add_gcc_compiler_cxxflag("${FLAG}" "${FLAGNAME}")
|
||||
endif()
|
||||
endmacro(check_add_gcc_compiler_flag)
|
||||
|
||||
add_definitions(-DQT_NO_EXCEPTIONS -DQT_STRICT_ITERATORS -DQT_NO_CAST_TO_ASCII)
|
||||
if(NOT IS_DEBUG_BUILD)
|
||||
add_definitions(-DQT_NO_DEBUG_OUTPUT)
|
||||
endif()
|
||||
|
||||
if(WITH_APP_BUNDLE)
|
||||
add_definitions(-DWITH_APP_BUNDLE)
|
||||
endif()
|
||||
|
||||
# Compiler Flags
|
||||
add_gcc_compiler_flags("-fno-common")
|
||||
find_package(OpenMP)
|
||||
if(OpenMP_FOUND)
|
||||
@@ -310,10 +253,8 @@ if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
|
||||
check_add_gcc_compiler_flag("-Wshadow-compatible-local")
|
||||
check_add_gcc_compiler_flag("-Wshadow-local")
|
||||
add_gcc_compiler_flags("-Werror")
|
||||
# This is needed since compiling against Botan3 requires compiling against C++20
|
||||
if(WITH_XC_BOTAN3)
|
||||
add_gcc_compiler_cxxflags("-Wno-error=deprecated-enum-enum-conversion -Wno-error=deprecated")
|
||||
endif()
|
||||
# C++20 marks enum arithmetic as deprecated, but we use it in Botan and Qt5
|
||||
add_gcc_compiler_cxxflags("-Wno-deprecated-enum-enum-conversion -Wno-error=deprecated ")
|
||||
endif()
|
||||
|
||||
if (NOT HAIKU)
|
||||
@@ -358,14 +299,6 @@ if(UNIX AND NOT APPLE)
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro,-z,now")
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
if(WITH_XC_BOTAN3)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
else()
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
endif()
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
check_cxx_compiler_flag("-fsized-deallocation" CXX_HAS_fsized_deallocation)
|
||||
if(CXX_HAS_fsized_deallocation)
|
||||
# Do additional check: the deallocation functions must be there too.
|
||||
@@ -383,7 +316,7 @@ if(APPLE AND CMAKE_COMPILER_IS_CLANGXX)
|
||||
add_gcc_compiler_cxxflags("-stdlib=libc++")
|
||||
endif()
|
||||
|
||||
if(WITH_DEV_BUILD)
|
||||
if(WITH_WARN_DEPRECATED)
|
||||
add_definitions(-DQT_DEPRECATED_WARNINGS)
|
||||
else()
|
||||
add_definitions(-DQT_NO_DEPRECATED_WARNINGS)
|
||||
@@ -392,54 +325,36 @@ endif()
|
||||
|
||||
# MSVC specific options
|
||||
if (MSVC)
|
||||
if(MSVC_TOOLSET_VERSION LESS 141)
|
||||
message(FATAL_ERROR "Only Microsoft Visual Studio 17 and newer are supported!")
|
||||
if(MSVC_TOOLSET_VERSION LESS 142)
|
||||
message(FATAL_ERROR "Only Microsoft Visual Studio 2019 and newer are supported!")
|
||||
endif()
|
||||
add_compile_options(/permissive- /utf-8 /MP)
|
||||
if(IS_DEBUG_BUILD)
|
||||
add_compile_options(/Zf)
|
||||
if(MSVC_TOOLSET_VERSION GREATER 141)
|
||||
add_compile_definitions(/fsanitize=address)
|
||||
endif()
|
||||
# Turn on multi-processor support and faster PDB generation (/Zf)
|
||||
add_compile_options(/permissive- /utf-8 /MP /Zf)
|
||||
# Enable built-in ASAN
|
||||
add_compile_definitions(/fsanitize=address)
|
||||
|
||||
# Enable high entropy ASLR on release builds
|
||||
if(CMAKE_BUILD_TYPE_LOWER STREQUAL "release")
|
||||
add_compile_options(/guard:cf)
|
||||
add_link_options(/DYNAMICBASE /HIGHENTROPYVA /GUARD:CF)
|
||||
endif()
|
||||
elseif(MINGW)
|
||||
# Enable high entropy ASLR on release builds
|
||||
if(CMAKE_BUILD_TYPE_LOWER STREQUAL "release")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase -Wl,--high-entropy-va")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase -Wl,--high-entropy-va")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(CMAKE_RC_COMPILER_INIT windres)
|
||||
enable_language(RC)
|
||||
if(MINGW)
|
||||
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
|
||||
endif()
|
||||
if(NOT IS_DEBUG_BUILD)
|
||||
if(MSVC)
|
||||
# By default MSVC enables NXCOMPAT
|
||||
add_compile_options(/guard:cf)
|
||||
add_link_options(/DYNAMICBASE /HIGHENTROPYVA /GUARD:CF)
|
||||
else(MINGW)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
||||
# Enable high entropy ASLR for 64-bit builds
|
||||
if(NOT IS_32BIT)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(APPLE AND WITH_APP_BUNDLE OR WIN32)
|
||||
set(PROGNAME KeePassXC)
|
||||
else()
|
||||
set(PROGNAME keepassxc)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(CLI_INSTALL_DIR ".")
|
||||
set(PROXY_INSTALL_DIR ".")
|
||||
set(BIN_INSTALL_DIR ".")
|
||||
set(PLUGIN_INSTALL_DIR ".")
|
||||
set(DATA_INSTALL_DIR "share")
|
||||
elseif(APPLE AND WITH_APP_BUNDLE)
|
||||
set(PROGNAME KeePassXC)
|
||||
set(BUNDLE_INSTALL_DIR "${PROGNAME}.app/Contents")
|
||||
set(CMAKE_INSTALL_MANDIR "${BUNDLE_INSTALL_DIR}/Resources/man")
|
||||
set(CLI_INSTALL_DIR "${BUNDLE_INSTALL_DIR}/MacOS")
|
||||
@@ -447,9 +362,10 @@ elseif(APPLE AND WITH_APP_BUNDLE)
|
||||
set(BIN_INSTALL_DIR "${BUNDLE_INSTALL_DIR}/MacOS")
|
||||
set(PLUGIN_INSTALL_DIR "${BUNDLE_INSTALL_DIR}/PlugIns")
|
||||
set(DATA_INSTALL_DIR "${BUNDLE_INSTALL_DIR}/Resources")
|
||||
add_definitions(-DWITH_APP_BUNDLE)
|
||||
else()
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(PROGNAME keepassxc)
|
||||
set(CLI_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
|
||||
set(PROXY_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
|
||||
set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
|
||||
@@ -457,10 +373,6 @@ else()
|
||||
set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/keepassxc")
|
||||
endif()
|
||||
|
||||
if(WITH_TESTS)
|
||||
enable_testing()
|
||||
endif(WITH_TESTS)
|
||||
|
||||
if(WITH_COVERAGE)
|
||||
# Include code coverage, use with -DCMAKE_BUILD_TYPE=Debug
|
||||
include(CodeCoverage)
|
||||
@@ -489,11 +401,10 @@ if(WITH_COVERAGE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(CLangFormat)
|
||||
|
||||
# Find Qt
|
||||
set(QT_COMPONENTS Core Network Concurrent Gui Svg Widgets Test LinguistTools)
|
||||
if(UNIX AND NOT APPLE)
|
||||
if(WITH_XC_X11)
|
||||
if(WITH_X11)
|
||||
list(APPEND QT_COMPONENTS X11Extras)
|
||||
endif()
|
||||
find_package(Qt5 COMPONENTS ${QT_COMPONENTS} DBus REQUIRED)
|
||||
@@ -512,14 +423,13 @@ else()
|
||||
find_package(Qt5 COMPONENTS ${QT_COMPONENTS} REQUIRED)
|
||||
endif()
|
||||
|
||||
# Minimum Qt version check
|
||||
if(Qt5Core_VERSION VERSION_LESS "5.12.0")
|
||||
message(FATAL_ERROR "Qt version 5.12.0 or higher is required")
|
||||
endif()
|
||||
|
||||
get_filename_component(Qt5_PREFIX ${Qt5_DIR}/../../.. REALPATH)
|
||||
if(APPLE)
|
||||
# Add includes under Qt5 Prefix in case Qt6 is also installed
|
||||
include_directories(SYSTEM ${Qt5_PREFIX}/include)
|
||||
# C++20 is not supported before Qt 5.15.0
|
||||
if(Qt5Core_VERSION VERSION_LESS "5.15.0")
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
endif()
|
||||
|
||||
# Process moc automatically
|
||||
@@ -529,7 +439,17 @@ set(CMAKE_AUTOUIC ON)
|
||||
# Process .qrc files automatically
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
add_definitions(-DQT_NO_EXCEPTIONS -DQT_STRICT_ITERATORS -DQT_NO_CAST_TO_ASCII)
|
||||
if(NOT CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
|
||||
add_definitions(-DQT_NO_DEBUG_OUTPUT)
|
||||
endif()
|
||||
|
||||
get_filename_component(Qt5_PREFIX ${Qt5_DIR}/../../.. REALPATH)
|
||||
|
||||
if(APPLE)
|
||||
# Add includes under Qt5 Prefix in case Qt6 is also installed
|
||||
include_directories(SYSTEM ${Qt5_PREFIX}/include)
|
||||
|
||||
set(CMAKE_MACOSX_RPATH TRUE)
|
||||
find_program(MACDEPLOYQT_EXE macdeployqt HINTS ${Qt5_PREFIX}/bin ${Qt5_PREFIX}/tools/qt5/bin ENV PATH)
|
||||
if(NOT MACDEPLOYQT_EXE)
|
||||
@@ -545,9 +465,16 @@ elseif(WIN32)
|
||||
message(STATUS "Using windeployqt: ${WINDEPLOYQT_EXE}")
|
||||
endif()
|
||||
|
||||
# Debian sets the build type to None for package builds.
|
||||
# Make sure we don't enable asserts there.
|
||||
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG)
|
||||
# Find Botan
|
||||
# TODO: Increase minimum to 2.19.1 and drop Argon2 package
|
||||
find_package(Botan REQUIRED)
|
||||
if(BOTAN_VERSION VERSION_GREATER_EQUAL "3.0.0")
|
||||
set(WITH_BOTAN3 TRUE)
|
||||
elseif(BOTAN_VERSION VERSION_LESS "2.12.0")
|
||||
# Check for minimum Botan version
|
||||
message(FATAL_ERROR "Botan 2.12.0 or higher is required")
|
||||
endif()
|
||||
include_directories(SYSTEM ${BOTAN_INCLUDE_DIR})
|
||||
|
||||
# Find Argon2 -- Botan 2.18 and below does not support threaded Argon2
|
||||
find_library(ARGON2_LIBRARIES NAMES argon2)
|
||||
@@ -564,70 +491,40 @@ include_directories(SYSTEM ${ZLIB_INCLUDE_DIR})
|
||||
# Find Minizip
|
||||
find_package(Minizip REQUIRED)
|
||||
|
||||
if(WITH_XC_YUBIKEY)
|
||||
find_package(PCSC REQUIRED)
|
||||
include_directories(SYSTEM ${PCSC_INCLUDE_DIRS})
|
||||
# Find PCSC and LibUSB for hardware key support
|
||||
find_package(PCSC REQUIRED)
|
||||
include_directories(SYSTEM ${PCSC_INCLUDE_DIRS})
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
find_library(LIBUSB_LIBRARIES NAMES usb-1.0 REQUIRED)
|
||||
find_path(LIBUSB_INCLUDE_DIR NAMES libusb.h PATH_SUFFIXES "libusb-1.0" "libusb" REQUIRED)
|
||||
include_directories(SYSTEM ${LIBUSB_INCLUDE_DIR})
|
||||
endif()
|
||||
if(UNIX AND NOT APPLE)
|
||||
find_library(LIBUSB_LIBRARIES NAMES usb-1.0 REQUIRED)
|
||||
find_path(LIBUSB_INCLUDE_DIR NAMES libusb.h PATH_SUFFIXES "libusb-1.0" "libusb" REQUIRED)
|
||||
include_directories(SYSTEM ${LIBUSB_INCLUDE_DIR})
|
||||
|
||||
# For PolKit QuickUnlock
|
||||
find_library(KEYUTILS_LIBRARIES NAMES keyutils REQUIRED)
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
check_cxx_source_compiles("#include <sys/prctl.h>
|
||||
int main() { prctl(PR_SET_DUMPABLE, 0); return 0; }"
|
||||
HAVE_PR_SET_DUMPABLE)
|
||||
|
||||
check_cxx_source_compiles("#include <malloc.h>
|
||||
int main() { return 0; }"
|
||||
HAVE_MALLOC_H)
|
||||
|
||||
check_cxx_source_compiles("#include <malloc.h>
|
||||
int main() { malloc_usable_size(NULL); return 0; }"
|
||||
HAVE_MALLOC_USABLE_SIZE)
|
||||
|
||||
check_cxx_source_compiles("#include <sys/resource.h>
|
||||
int main() {
|
||||
struct rlimit limit;
|
||||
limit.rlim_cur = 0;
|
||||
limit.rlim_max = 0;
|
||||
setrlimit(RLIMIT_CORE, &limit);
|
||||
return 0;
|
||||
}" HAVE_RLIMIT_CORE)
|
||||
|
||||
if(APPLE)
|
||||
check_cxx_source_compiles("#include <sys/types.h>
|
||||
#include <sys/ptrace.h>
|
||||
int main() { ptrace(PT_DENY_ATTACH, 0, 0, 0); return 0; }"
|
||||
HAVE_PT_DENY_ATTACH)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include_directories(SYSTEM ${ZLIB_INCLUDE_DIR})
|
||||
|
||||
# Find zxcvbn or use the bundled version
|
||||
find_library(ZXCVBN_LIBRARIES zxcvbn)
|
||||
if(NOT ZXCVBN_LIBRARIES)
|
||||
add_subdirectory(src/thirdparty/zxcvbn)
|
||||
set(ZXCVBN_LIBRARIES zxcvbn)
|
||||
endif(NOT ZXCVBN_LIBRARIES)
|
||||
endif()
|
||||
|
||||
# Add KeePassXC sources and tests
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(share)
|
||||
if(WITH_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif(WITH_TESTS)
|
||||
|
||||
if(WITH_XC_DOCS)
|
||||
if(WITH_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
if(KPXC_FEATURE_DOCS)
|
||||
add_subdirectory(docs)
|
||||
endif()
|
||||
|
||||
if(PRINT_SUMMARY)
|
||||
# This will print ENABLED, REQUIRED and DISABLED
|
||||
feature_summary(WHAT ALL)
|
||||
else()
|
||||
# This will only print ENABLED and DISABLED feature
|
||||
feature_summary(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:")
|
||||
feature_summary(WHAT DISABLED_FEATURES DESCRIPTION "Disabled features:")
|
||||
endif()
|
||||
# Print out summary information
|
||||
message(STATUS "")
|
||||
feature_summary(QUIET_ON_EMPTY WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:")
|
||||
feature_summary(QUIET_ON_EMPTY WHAT DISABLED_FEATURES DESCRIPTION "Disabled features:")
|
||||
|
||||
1
COPYING
@@ -223,7 +223,6 @@ Files: share/icons/application/scalable/actions/application-exit.svg
|
||||
share/icons/application/scalable/actions/totp-copy.svg
|
||||
share/icons/application/scalable/actions/totp-copy-password.svg
|
||||
share/icons/application/scalable/actions/totp-edit.svg
|
||||
share/icons/application/scalable/actions/totp-invalid.svg
|
||||
share/icons/application/scalable/actions/trash.svg
|
||||
share/icons/application/scalable/actions/url-copy.svg
|
||||
share/icons/application/scalable/actions/user-guide.svg
|
||||
|
||||
71
INSTALL.md
@@ -1,19 +1,17 @@
|
||||
Build and Install KeePassXC
|
||||
=================
|
||||
# Build and Install KeePassXC
|
||||
|
||||
This document will guide you through the steps to build and install KeePassXC from source.
|
||||
For more information, see also the [_Building KeePassXC_](https://github.com/keepassxreboot/keepassxc/wiki/Building-KeePassXC) page on the wiki.
|
||||
|
||||
The [QuickStart Guide](https://keepassxc.org/docs/KeePassXC_GettingStarted.html) gets you started using KeePassXC on your Windows, macOS, or Linux computer using pre-compiled binaries from the [downloads page](https://keepassxc.org/download).
|
||||
|
||||
Toolchain and Build Dependencies
|
||||
================================
|
||||
## Toolchain and Build Dependencies
|
||||
|
||||
The following build tools must exist within your PATH:
|
||||
|
||||
* cmake (>= 3.10.0)
|
||||
* cmake (>= 3.16.0)
|
||||
* make (>= 4.2) or ninja (>= 1.10)
|
||||
* g++ (>= 4.9) or clang++ (>= 6.0)
|
||||
* g++ (>= 9.3.0) or clang++ (>= 10.0)
|
||||
* asciidoctor (>= 2.0)
|
||||
|
||||
* Besides a working C++ toolchain, KeePassXC also has a number of direct build and runtime dependencies. For detailed information about how to install them, please refer to the GitHub wiki:
|
||||
@@ -22,8 +20,8 @@ The following build tools must exist within your PATH:
|
||||
* [Set up Build Environment on Windows](https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-Windows)
|
||||
* [Set up Build Environment on macOS](https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-macOS)
|
||||
|
||||
Build Steps
|
||||
===========
|
||||
## Build Steps
|
||||
|
||||
We recommend using the release tool to perform builds, please read up-to-date instructions [on our wiki](https://github.com/keepassxreboot/keepassxc/wiki/Building-KeePassXC#building-using-the-release-tool).
|
||||
|
||||
To compile from source, open a **Terminal (Linux/MacOS)**, the **MSVC Tools Command Prompt (Windows)**, or **MSYS2-MinGW shell (Windows)**. For code development on Windows, you can use Visual Studio 2022, Visual Studio Code, or CLion.
|
||||
@@ -55,10 +53,10 @@ To compile from source, open a **Terminal (Linux/MacOS)**, the **MSVC Tools Comm
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DWITH_XC_ALL=ON ..
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
|
||||
If you have `vcpkg` installed, add `-DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake` to the `cmake` command to automatically download and install all required build and runtime dependencies locally to your build directory before compiling KeePassXC. Using `vcpkg` is the preferred way to install dependencies on macOS and required on Windows if using the MSVC toolchain.
|
||||
|
||||
For more detailed build instructions for each platform, please refer to the [GitHub wiki](https://github.com/keepassxreboot/keepassxc/wiki/Building-KeePassXC).
|
||||
@@ -77,49 +75,39 @@ When building with ASAN support on macOS, you need to use `export ASAN_OPTIONS=d
|
||||
|
||||
If you are using MSYS2, you have to add ```-G "MSYS Makefiles"``` at the beginning of the cmake command.
|
||||
|
||||
CMake Configuration Options
|
||||
==========================
|
||||
|
||||
## Recommended CMake Build Parameters
|
||||
|
||||
```
|
||||
-DCMAKE_VERBOSE_MAKEFILE=ON
|
||||
-DCMAKE_BUILD_TYPE=<RelWithDebInfo/Debug/Release>
|
||||
-DWITH_GUI_TESTS=ON
|
||||
```
|
||||
|
||||
## Additional CMake Parameters
|
||||
|
||||
KeePassXC comes with a variety of build options that can turn on/off features. Most notably, we allow you to build the application with all TCP/IP networking code disabled. Please note that we still require and link against Qt5's network library in order to use local named pipes on all operating systems. Each of these build options are supplied at the time of calling cmake:
|
||||
KeePassXC comes with a variety of build options that can turn on/off features. Each of these build options are supplied at the time of calling cmake:
|
||||
|
||||
```
|
||||
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
|
||||
-DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF)
|
||||
-DWITH_XC_BROWSER=[ON|OFF] Enable/Disable KeePassXC-Browser extension support (default: OFF)
|
||||
-DWITH_XC_BROWSER_PASSKEYS=[ON|OFF] Enable/Disable Passkeys support for browser integration (default: OFF)
|
||||
-DWITH_XC_NETWORKING=[ON|OFF] Enable/Disable Networking support (e.g., favicon downloading) (default: OFF)
|
||||
-DWITH_XC_SSHAGENT=[ON|OFF] Enable/Disable SSHAgent support (default: OFF)
|
||||
-DWITH_XC_FDOSECRETS=[ON|OFF] (Linux Only) Enable/Disable Freedesktop.org Secrets Service support (default:OFF)
|
||||
-DWITH_XC_KEESHARE=[ON|OFF] Enable/Disable KeeShare group synchronization extension (default: OFF)
|
||||
-DWITH_XC_ALL=[ON|OFF] Enable/Disable compiling all plugins above (default: OFF)
|
||||
-DKPXC_MINIMAL=[ON|OFF] Build KeePassXC with the minimal feature set required for basic usage (default: OFF)
|
||||
-DKPXC_FEATURE_BROWSER=[ON|OFF] Browser integration and passkeys support (default: ON)
|
||||
-DKPXC_FEATURE_SSHAGENT=[ON|OFF] SSH Agent integration (default: ON)
|
||||
-DKPXC_FEATURE_FDOSECRETS=[ON|OFF] (Linux Only) freedesktop.org Secret Service integration; replace system keyring (default:ON)
|
||||
|
||||
-DWITH_XC_UPDATECHECK=[ON|OFF] Enable/Disable automatic updating checking (requires WITH_XC_NETWORKING) (default: ON)
|
||||
-DKPXC_FEATURE_NETWORK=[ON|OFF] Include code that reaches out to external networks (e.g. downloading icons) (default: ON)
|
||||
-DKPXC_FEATURE_UPDATES=[ON|OFF] Include automatic update checks; disable for managed distributions (requires networking) (default: ON)
|
||||
-DKPXC_FEATURE_DOCS=[ON|OFF] Build offline documentation; requires asciidoctor tool (default: ON)
|
||||
|
||||
-DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON)
|
||||
-DWITH_GUI_TESTS=[ON|OFF] Enable/Disable building of GUI tests (default: OFF)
|
||||
-DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF)
|
||||
-DWITH_WARN_DEPRECATED=[ON|OFF] Development only: warn about deprecated methods, including Qt. (default: OFF)
|
||||
-DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux / macOS only) (default: OFF)
|
||||
-DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF)
|
||||
-DWITH_APP_BUNDLE=[ON|OFF] Enable Application Bundle for macOS (default: ON)
|
||||
-DWITH_CCACHE=[ON|OFF] Use ccache for build speed optimization (default: OFF)
|
||||
-DWITH_X11=[ON|OFF] Enable building with X11 dependencies (default: ON)
|
||||
|
||||
-DKEEPASSXC_BUILD_TYPE=[Snapshot|PreRelease|Release] Set the build type to show/hide stability warnings (default: "Snapshot")
|
||||
-DKEEPASSXC_DIST_TYPE=[Snap|AppImage|Other] Specify the distribution method (default: "Other")
|
||||
-DKEEPASSXC_BUILD_TYPE=[Snapshot|Release] Set the build type to show/hide stability warnings (default: "Snapshot")
|
||||
-DKEEPASSXC_DIST_TYPE=[Snap|AppImage|Flatpak|Native] Specify the distribution method (default: "Native")
|
||||
-DOVERRIDE_VERSION=[X.X.X] Specify a version number when building. Used with snapshot builds (default: "")
|
||||
-DGIT_HEAD_OVERRIDE=[XXXXXXX] Specify the 7 digit git commit ref for this build. Used with distribution builds (default: "")
|
||||
```
|
||||
|
||||
Installation
|
||||
============
|
||||
Note: Even though you can build the application with all TCP/IP networking code disabled, we still require and link against
|
||||
Qt5's network library to use local named pipes on all operating systems.
|
||||
|
||||
## Installation
|
||||
|
||||
After you have successfully built KeePassXC, install the binary by executing the following:
|
||||
|
||||
@@ -127,8 +115,7 @@ After you have successfully built KeePassXC, install the binary by executing the
|
||||
sudo make install
|
||||
```
|
||||
|
||||
Packaging
|
||||
=========
|
||||
## Packaging
|
||||
|
||||
You can create a package to redistribute KeePassXC (zip, deb, rpm, dmg, etc..). Refer to [keepassxc-packaging](https://github.com/keepassxreboot/keepassxc-packaging) for packaging scripts.
|
||||
|
||||
@@ -138,21 +125,23 @@ To package using CMake, run the following command using whichever [generators](h
|
||||
cpack -G "ZIP;WIX"
|
||||
```
|
||||
|
||||
Testing
|
||||
=======
|
||||
## Testing
|
||||
|
||||
You can perform tests on the built executables with:
|
||||
|
||||
```
|
||||
make test ARGS+="--output-on-failure"
|
||||
```
|
||||
|
||||
On Linux, if you are not currently running on an X Server or Wayland, run the tests as follows:
|
||||
|
||||
```
|
||||
make test ARGS+="-E test\(cli\|gui\) --output-on-failure"
|
||||
xvfb-run -e errors -a --server-args="-screen 0 1024x768x24" make test ARGS+="-R test\(cli\|gui\) --output-on-failure"
|
||||
```
|
||||
|
||||
Common parameters:
|
||||
|
||||
```
|
||||
CTEST_OUTPUT_ON_FAILURE=1
|
||||
ARGS+=-jX
|
||||
|
||||
18
README.md
@@ -1,4 +1,5 @@
|
||||
# <img src="https://keepassxc.org/assets/img/keepassxc.svg" width="40" height="40"/> KeePassXC
|
||||
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/6326)
|
||||
[/statusIcon)](https://ci.keepassxc.org/?guest=1)
|
||||
[](https://codecov.io/gh/keepassxreboot/keepassxc)
|
||||
@@ -10,16 +11,22 @@
|
||||
[KeePassXC](https://keepassxc.org) is a modern, secure, and open-source password manager that stores and manages your most sensitive information. You can run KeePassXC on Windows, macOS, and Linux systems. KeePassXC is for people with extremely high demands of secure personal data management. It saves many different types of information, such as usernames, passwords, URLs, attachments, and notes in an offline, encrypted file that can be stored in any location, including private and public cloud solutions. For easy identification and management, user-defined titles and icons can be specified for entries. In addition, entries are sorted into customizable groups. An integrated search function allows you to use advanced patterns to easily find any entry in your database. A customizable, fast, and easy-to-use password generator utility allows you to create passwords with any combination of characters or easy to remember passphrases.
|
||||
|
||||
## Quick Start
|
||||
|
||||
The [QuickStart Guide](https://keepassxc.org/docs/KeePassXC_GettingStarted.html) gets you started using KeePassXC on your Windows, macOS, or Linux computer using pre-compiled binaries from the [downloads page](https://keepassxc.org/download). Additionally, individual Linux distributions may ship their own versions, so please check your distribution's package list to see if KeePassXC is available. Detailed documentation is available in the [User Guide](https://keepassxc.org/docs/KeePassXC_UserGuide.html).
|
||||
|
||||
## Features List
|
||||
|
||||
KeePassXC has numerous features for novice and power users alike. Our goal is to create an application that can be used by anyone while still offering advanced features to those that need them.
|
||||
|
||||
### Basic
|
||||
### Core Features
|
||||
|
||||
* Create, open, and save databases in the KDBX format (KeePass-compatible with KDBX4 and KDBX3)
|
||||
* All information is encrypted at rest and never exposed outside the program
|
||||
* Store sensitive information in entries that are organized by groups
|
||||
* Search for entries
|
||||
* Password generator
|
||||
* Search for entries
|
||||
* TOTP storage and generation
|
||||
* YubiKey/OnlyKey challenge-response support
|
||||
* Auto-Type passwords into applications
|
||||
* Browser integration with Google Chrome, Mozilla Firefox, Microsoft Edge, Chromium, Vivaldi, Brave, and Tor-Browser
|
||||
* Support for passkeys using the browser integration
|
||||
@@ -33,10 +40,7 @@ KeePassXC has numerous features for novice and power users alike. Our goal is to
|
||||
* Field references between entries
|
||||
* File attachments and custom attributes
|
||||
* Entry history and data restoration
|
||||
* YubiKey/OnlyKey challenge-response support
|
||||
* Command line interface (keepassxc-cli)
|
||||
* Auto-Open databases
|
||||
* KeeShare shared databases (import, export, and synchronize)
|
||||
* SSH Agent integration
|
||||
* FreeDesktop.org Secret Service (replace Gnome keyring, etc.)
|
||||
* Additional encryption choices: Twofish and ChaCha20
|
||||
@@ -56,10 +60,6 @@ You may directly contribute your own code by submitting a pull request. Please r
|
||||
|
||||
Contributors are required to adhere to the project's [Code of Conduct](CODE-OF-CONDUCT.md).
|
||||
|
||||
## Generative AI
|
||||
|
||||
Generative AI is fast becoming a first-party feature in most development environments, including GitHub itself. If the majority of a code submission is made using Generative AI (e.g., agent-based or vibe coding) then **we will document that in the pull request.** All code submissions go through a rigorous review process regardless of the development workflow or submitter.
|
||||
|
||||
## License
|
||||
|
||||
KeePassXC code is licensed under GPL-2 or GPL-3. Additional licensing for third-party files is detailed in [COPYING](./COPYING).
|
||||
|
||||
65
cmake/CompilerFlags.cmake
Normal file
@@ -0,0 +1,65 @@
|
||||
# Copyright (C) 2024 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/>.
|
||||
|
||||
macro(add_gcc_compiler_cxxflags FLAGS)
|
||||
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${FLAGS}")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(add_gcc_compiler_cflags FLAGS)
|
||||
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_CLANG)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${FLAGS}")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(add_gcc_compiler_flags FLAGS)
|
||||
add_gcc_compiler_cxxflags("${FLAGS}")
|
||||
add_gcc_compiler_cflags("${FLAGS}")
|
||||
endmacro()
|
||||
|
||||
# Copies of above macros that first ensure the compiler understands a given flag
|
||||
# Because check_*_compiler_flag() sets -D with name, need to provide "safe" FLAGNAME
|
||||
macro(check_add_gcc_compiler_cxxflag FLAG FLAGNAME)
|
||||
check_cxx_compiler_flag("${FLAG}" CXX_HAS${FLAGNAME})
|
||||
if(CXX_HAS${FLAGNAME})
|
||||
add_gcc_compiler_cxxflags("${FLAG}")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(check_add_gcc_compiler_cflag FLAG FLAGNAME)
|
||||
check_c_compiler_flag("${FLAG}" CC_HAS${FLAGNAME})
|
||||
if(CC_HAS${FLAGNAME})
|
||||
add_gcc_compiler_cflags("${FLAG}")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
# This is the "front-end" for the above macros
|
||||
# Optionally takes additional parameter(s) with language to check (currently "C" or "CXX")
|
||||
macro(check_add_gcc_compiler_flag FLAG)
|
||||
string(REGEX REPLACE "[-=]" "_" FLAGNAME "${FLAG}")
|
||||
set(check_lang_spec ${ARGN})
|
||||
list(LENGTH check_lang_spec num_extra_args)
|
||||
set(langs C CXX)
|
||||
if(num_extra_args GREATER 0)
|
||||
set(langs "${check_lang_spec}")
|
||||
endif()
|
||||
if("C" IN_LIST langs)
|
||||
check_add_gcc_compiler_cflag("${FLAG}" "${FLAGNAME}")
|
||||
endif()
|
||||
if("CXX" IN_LIST langs)
|
||||
check_add_gcc_compiler_cxxflag("${FLAG}" "${FLAGNAME}")
|
||||
endif()
|
||||
endmacro()
|
||||
@@ -24,7 +24,7 @@ if(NOT PCSC_FOUND)
|
||||
|
||||
# Additional search paths for Windows if not running in Visual Studio environment
|
||||
if (WIN32)
|
||||
# Resolve the ambiguity of using two names for one architecture
|
||||
# Resolve the ambiguity of using two names for one architechture
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x64")
|
||||
set(ARCH_DIR "x64")
|
||||
else()
|
||||
|
||||
8
cmake/compiler-checks/ptrace_deny_attach.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
ptrace(PT_DENY_ATTACH, 0, 0, 0);
|
||||
return 0;
|
||||
}
|
||||
@@ -7,7 +7,6 @@ KeePassXC Team <team@keepassxc.org>
|
||||
:imagesdir: images
|
||||
:stylesheet: styles/dark.css
|
||||
:toc: left
|
||||
:experimental:
|
||||
ifdef::backend-pdf[]
|
||||
:title-page:
|
||||
:title-logo-image: {imagesdir}/kpxc_logo.png
|
||||
|
||||
@@ -7,7 +7,6 @@ KeePassXC Team <team@keepassxc.org>
|
||||
:stylesheet: styles/dark.css
|
||||
:toc: left
|
||||
:sectanchors:
|
||||
:experimental:
|
||||
ifdef::backend-pdf[]
|
||||
:title-page:
|
||||
:title-logo-image: {imagesdir}/kpxc_logo.png
|
||||
@@ -37,8 +36,6 @@ include::topics/Passkeys.adoc[tags=*]
|
||||
|
||||
include::topics/AutoType.adoc[tags=*]
|
||||
|
||||
include::topics/SecretService.adoc[tags=*]
|
||||
|
||||
include::topics/SSHAgent.adoc[tags=*]
|
||||
|
||||
include::topics/Reference.adoc[tags=*]
|
||||
|
||||
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 55 KiB |
@@ -4,4 +4,3 @@ KeePassXC Team <team@keepassxc.org>
|
||||
:stylesheet: ../styles/dark.css
|
||||
:icons: font
|
||||
:toc: left
|
||||
:experimental:
|
||||
|
||||
@@ -24,13 +24,13 @@ You can also set the time to remember the last used entry between presses of the
|
||||
=== Configure Auto-Type Sequences
|
||||
Each entry in your database can have multiple Auto-Type sequences associated with various window titles. Simulated key presses can be sent to any other currently open window of your choice (web browser windows, login dialogs boxes, and so on). When the Global Auto-Type hotkey is pressed, KeePassXC will search your database for entries matching the current selected window title.
|
||||
|
||||
NOTE: The default Auto-Type sequence is `{USERNAME}{TAB}{PASSWORD}{ENTER}`. This means that it first types the username of the selected entry, then presses the kbd:[Tab] key, then types the password of the entry and finally presses the kbd:[Enter] key.
|
||||
NOTE: The default Auto-Type sequence is `{USERNAME}{TAB}{PASSWORD}{ENTER}`. This means that it first types the username of the selected entry, then presses the `Tab` key, then types the password of the entry and finally presses the `Enter` key.
|
||||
|
||||
TIP: To change the default Auto-Type sequence for all entries of your database, edit the root (top-most) group of your database and set a specific sequence. Child groups and entries will inherit this sequence by default.
|
||||
|
||||
To configure Auto-Type sequences for your entries, perform the following steps:
|
||||
|
||||
1. Navigate to the entries list and open the desired entry for editing. Click the _Auto-Type_ item from the left-hand menu bar *(1)*. Press the kbd:[+] button *(2)* to add a new sequence entry. Select the desired window using the drop-down menu, or simply type a window title in the box *(3)*.
|
||||
1. Navigate to the entries list and open the desired entry for editing. Click the _Auto-Type_ item from the left-hand menu bar *(1)*. Press the `+` button *(2)* to add a new sequence entry. Select the desired window using the drop-down menu, or simply type a window title in the box *(3)*.
|
||||
+
|
||||
TIP: You can use an asterisk (`\*`) to match any value (e.g., when a window title contains a dynamic filename or website name). Set the window title to `*` to match all windows. Leave the window title blank to offer additional default Auto-Type sequences, such as custom attributes.
|
||||
+
|
||||
@@ -60,7 +60,7 @@ image::autotype_entry_sequences.png[]
|
||||
|Press the corresponding keyboard key
|
||||
|
||||
|{UP}, {DOWN}, {LEFT}, {RIGHT} |Press the corresponding arrow key
|
||||
|{LEFTBRACE}, {RIGHTBRACE} |Press kbd:[{] or kbd:[}], respectively
|
||||
|{LEFTBRACE}, {RIGHTBRACE} |Press `{` or `}`, respectively
|
||||
|{<KEY> X} |Repeat <KEY> X times (e.g., {SPACE 5} inserts five spaces)
|
||||
|{DELAY=X} |Set delay between key presses to X milliseconds
|
||||
|{DELAY X} |Pause typing for X milliseconds
|
||||
@@ -89,7 +89,7 @@ When you press the global Auto-Type hotkey, KeePassXC searches all unlocked data
|
||||
.Auto-Type sequence selection
|
||||
image::autotype_selection_dialog.png[,70%]
|
||||
|
||||
Perform the selected Auto-Type sequence by double clicking the desired row or pressing kbd:[Enter]. Press the up and down arrows to navigate the list. Sequences can be filtered through the text edit field.
|
||||
Perform the selected Auto-Type sequence by double clicking the desired row or pressing _Enter_. Press the up and down arrows to navigate the list. Sequences can be filtered through the text edit field.
|
||||
|
||||
.Auto-Type search database
|
||||
image::autotype_selection_dialog_search.png[,70%]
|
||||
@@ -104,7 +104,7 @@ The option to type just the username, password, or current TOTP value is availab
|
||||
TIP: On Windows, you will see an option to use a virtual keyboard in this sub-menu. This is an experimental feature that allows you to type into virtual machines by simulating actual keyboard presses. Some international keyboards may be unsupported due to limitations in the Windows API.
|
||||
|
||||
=== Performing Entry-Level Auto-Type
|
||||
You can quickly activate the default Auto-Type sequence for a particular entry using Entry-Level Auto-Type. For this operation, the KeePassXC window will be minimized and the Auto-Type sequence occurs in the previously selected window. You can perform Entry-Level Auto-Type from the toolbar icon *(A)*, entry context menu *(B)*, or by pressing kbd:[Ctrl+Shift+V].
|
||||
You can quickly activate the default Auto-Type sequence for a particular entry using Entry-Level Auto-Type. For this operation, the KeePassXC window will be minimized and the Auto-Type sequence occurs in the previously selected window. You can perform Entry-Level Auto-Type from the toolbar icon *(A)*, entry context menu *(B)*, or by pressing `Ctrl+Shift+V`.
|
||||
|
||||
WARNING: Be careful when using Entry-Level Auto-Type as you can inadvertently type into the wrong window. For example, a chat window or email.
|
||||
|
||||
|
||||
@@ -107,34 +107,12 @@ You can then choose to update/add the credentials to your KeePassXC database dir
|
||||
4. When you have successfully submitted the password on the website, a popup will appear asking you to either update an existing entry or add a new one.
|
||||
|
||||
// tag::advanced[]
|
||||
=== Browser Integration Report
|
||||
You can see a cross-section of all browser-related settings applied to entries within a database through the Browser Statistics report. To access, use the _Database_ -> _Database reports..._ menu option then click on _Browser Statistics_ on the left-hand menu. From here you can see all entries with URLs applied to them, explicitly allowed and denied URLs, and any entries with custom browser settings.
|
||||
=== Browser statistics
|
||||
You can see a cross-section of all browser-related settings applied to entries within a database through the Browser Statistics report. To access these, use the _Database_ -> _Database reports..._ menu option then click on _Browser Statistics_ on the left-hand menu. From here you can see all entries with URLs applied to them, explicitly allowed and denied URLs, and any entries with custom browser settings.
|
||||
|
||||
TIP: You can delete remembered site settings from the report by right clicking the entry you want to reset and selecting "Delete plugin data from entry".
|
||||
|
||||
.Browser Integration Report
|
||||
.Browser statistics
|
||||
image::browser_statistics.png[]
|
||||
|
||||
=== Additional Fill-In Fields
|
||||
Sometimes login pages have additional fields you would like to fill (e.g., account number). Use the following instructions to add them:
|
||||
|
||||
1. Edit the entry you want to add fields to. Go to the advanced tab and add the attributes you need. Each attribute *must start with* `KPH:`, but otherwise the name does not matter. If multiple KPH attributes are defined, they are used in alphabetical order (i.e., the order shown in KeePassXC).
|
||||
2. Within the browser, navigate to the page you want to use the additional fields on. Select the "Choose Custom Login Fields" button from the extension popup window. Choose Username, Password and String Field(s). Confirm the selections.
|
||||
3. Refresh the web page. The new KPH attribute(s) should be filled to the extra fields.
|
||||
|
||||
.String Fields Selection in Browser
|
||||
image:browser_integration_additional_attribute.png[]
|
||||
|
||||
=== Clearing Remembered Sites
|
||||
Entries that you have chosen to remember allow/deny rules are stored in their respect custom data fields. You can clear all of these remembered settings at once through the database settings. Follow these steps:
|
||||
|
||||
1. Go to *Database* → *Database Settings* or click the database settings icon in the toolbar.
|
||||
2. Go to the *Browser Integration* tab, then click on the *Forget all site-specific settings on entries* button.
|
||||
3. Confirm this action in the popup dialog. This cannot be undone once the database is saved.
|
||||
+
|
||||
.Clear Remembered Sites
|
||||
image::browser_integration_clear_sites.png[,100%]
|
||||
|
||||
=== Advanced Usage
|
||||
You can configure unique browser integration behavior for each entry. This allows you to add multiple URLs to an entry, hide an entry from the browser integration, and more. To access these settings, open an entry for editing then click on _Browser Integration_ option in the left-hand menu *(1)*.
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ NOTE: On Windows, you will be prompted to authenticate to Windows Hello after un
|
||||
.Windows Hello example
|
||||
image::quick_unlock_windows_hello.png[]
|
||||
|
||||
When your database is locked, you will see the following unlock dialog. Simply press kbd:[Enter] or click on _Unlock Database_ to initiate the biometric authentication process. If you are using a hardware key (e.g. Yubikey), it must be connected to your computer to complete the unlock.
|
||||
When your database is locked, you will see the following unlock dialog. Simply press _Enter_ or click on _Unlock Database_ to initiate the biometric authentication process. If you are using a hardware key (e.g. Yubikey), it must be connected to your computer to complete the unlock.
|
||||
|
||||
.Quick Unlock
|
||||
image::quick_unlock.png[]
|
||||
@@ -92,7 +92,7 @@ All the details such as usernames, passwords, URLs, attachments, notes, and so o
|
||||
|
||||
To add an entry, perform the following step:
|
||||
|
||||
1. Navigate to Entries > New Entry (or press kbd:[Ctrl+N]). The following screen appears:
|
||||
1. Navigate to Entries > New Entry (Or, press Ctrl+N). The following screen appears:
|
||||
+
|
||||
.Adding a new entry
|
||||
image::edit_entry.png[]
|
||||
@@ -115,7 +115,7 @@ To edit the details in an entry, perform the following steps:
|
||||
|
||||
1. Select the entry you want to edit.
|
||||
|
||||
2. Press kbd:[Enter], click the edit toolbar icon, or right-click and select Edit Entry from the menu.
|
||||
2. Press `Enter`, click the edit toolbar icon, or right-click and select Edit Entry from the menu.
|
||||
|
||||
3. Make the desired changes.
|
||||
|
||||
@@ -156,13 +156,13 @@ TIP: Each KeePass application has different default icons. If you use a mobile a
|
||||
==== Deleting an Entry
|
||||
To delete an entry, perform the following steps:
|
||||
|
||||
1. Select the entry you want to delete and press the kbd:[Del] button on your keyboard.
|
||||
1. Select the entry you want to delete and press the `Delete` button on your keyboard.
|
||||
|
||||
2. You will be prompted to move the entry to the Recycle Bin (if enabled).
|
||||
+
|
||||
NOTE: You can disable the recycle bin within the Database Settings. If the recycle bin is disabled then deleted entries will be permanently removed from the database.
|
||||
|
||||
3. To permanently delete the entry, navigate to the Recycle Bin, select the entry you want to delete and press the kbd:[Del] button on your keyboard.
|
||||
3. To permanently delete the entry, navigate to the Recycle Bin, select the entry you want to delete and press the `Delete` button on your keyboard.
|
||||
|
||||
// tag::advanced[]
|
||||
==== Clone an Entry
|
||||
@@ -170,7 +170,7 @@ Creating a clone of an entry provides you a ready-to-use template for creating n
|
||||
|
||||
To create a clone of an existing entry, perform the following steps:
|
||||
|
||||
1. Right-click on the entry for which you want to create a clone and select _Clone Entry_. Alternatively, select the desired entry and press kbd:[Ctrl+K].
|
||||
1. Right-click on the entry for which you want to create a clone and select _Clone Entry_. Alternatively, select the desired entry and press `Ctrl+K`.
|
||||
+
|
||||
.Clone entry from context menu
|
||||
image::clone_entry.png[]
|
||||
@@ -375,7 +375,7 @@ image::database_settings.png[]
|
||||
* *Database name:* This is the default identifier for your database and is shown in the tab bar and title bar (when active). You can change this name as desired.
|
||||
* *Database description:* Provide some meaningful description for your database.
|
||||
* *Default username:* Provide a default username for all new entries that you create in this database.
|
||||
* *Public Database Metadata:* Here you can set a public (unencrypted) name, icon, and color for your database. This is used on the database unlock screen to help distinguish multiple databases from each other.
|
||||
* *Public Databse Metadata:* Here you can set a public (unencrypted) name, icon, and color for your database. This is used on the database unlock screen to help distinguish multiple databases from each other.
|
||||
* *Max history items:* This is the maximum number of history items that are stored for each entry. When you set this to 0, no history will be saved. Set this value to a low value to prevent the database from getting too large (we recommend no more than 10).
|
||||
* *Max. history size:* When the history of an entry gets above this size, it is truncated. For example, this happens when entries have large attachments. Set this value small to prevent the database from getting too large (we recommend 6 MiB).
|
||||
* *Use recycle bin:* Select this check-box if you want deleted entries to move to the recycle bin instead of being permanently removed. The recycle bin will be created if it does not already exist after your first deletion. To delete entries permanently, you must empty the recycle bin manually.
|
||||
|
||||
@@ -3,63 +3,52 @@ include::.sharedheader[]
|
||||
:imagesdir: ../images
|
||||
|
||||
// tag::content[]
|
||||
NOTE: On macOS please substitute kbd:[Ctrl] with kbd:[Cmd] (AKA kbd:[⌘]).
|
||||
NOTE: On macOS please substitute `Ctrl` with `Cmd` (aka `⌘`).
|
||||
|
||||
[grid=rows, frame=none, width=75%]
|
||||
|===
|
||||
|Action | Keyboard Shortcut
|
||||
|Action | Keyboard Shortcut
|
||||
|
||||
|Settings | kbd:[Ctrl + ,]
|
||||
|Open Database | kbd:[Ctrl + O]
|
||||
|Save Database | kbd:[Ctrl + S]
|
||||
|Save Database As | kbd:[Ctrl + Shift + S]
|
||||
|New Database | kbd:[Ctrl + Shift + N]
|
||||
|Close Database | kbd:[Ctrl + W] +
|
||||
_or_ +
|
||||
kbd:[Ctrl + F4]
|
||||
|Lock Current Database | kbd:[Ctrl + L]
|
||||
|Lock All Databases | kbd:[Ctrl + Shift + L]
|
||||
|Database Settings | kbd:[Ctrl + Shift + ,]
|
||||
|Database Reports | kbd:[Ctrl + Shift + R]
|
||||
|Quit | kbd:[Ctrl + Q]
|
||||
|New Entry | kbd:[Ctrl + N]
|
||||
|Edit Entry | kbd:[Enter] +
|
||||
_or_ +
|
||||
kbd:[Ctrl + E]
|
||||
|Delete Entry | kbd:[Del]
|
||||
|Clone Entry | kbd:[Ctrl + D]
|
||||
|Copy Username | kbd:[Ctrl + B]
|
||||
|Copy Password | kbd:[Ctrl + C]
|
||||
|Copy URL | kbd:[Ctrl + U]
|
||||
|Open URL | kbd:[Ctrl + Shift + U]
|
||||
|Copy TOTP | kbd:[Ctrl + T]
|
||||
|Copy Password and TOTP | kbd:[Ctrl + Y]
|
||||
|Show TOTP | kbd:[Ctrl + Shift + T]
|
||||
|Trigger AutoType | kbd:[Ctrl + Shift + V]
|
||||
|Add key to SSH Agent | kbd:[Ctrl + H]
|
||||
|Remove key from SSH Agent | kbd:[Ctrl + Shift + H]
|
||||
|Jump to Group (from search) | kbd:[Ctrl + Shift + J]
|
||||
|Move entry up (if unsorted) | kbd:[Ctrl + Alt + Up]
|
||||
|Move entry down (if unsorted) | kbd:[Ctrl + Alt + Down]
|
||||
|Sort Groups A-Z | kbd:[Ctrl + Down]
|
||||
|Sort Groups Z-A | kbd:[Ctrl + Up]
|
||||
|Minimize Window | kbd:[Ctrl + M]
|
||||
|Hide Window | kbd:[Ctrl + Shift + M]
|
||||
|Select Next Database Tab | kbd:[Ctrl + Tab] +
|
||||
_or_ +
|
||||
kbd:[Ctrl + PgDn]
|
||||
|Select Previous Database Tab | kbd:[Ctrl + Shift + Tab] +
|
||||
_or_ +
|
||||
kbd:[Ctrl + PgUp]
|
||||
|Select the nth database | kbd:[Ctrl + <n>], where kbd:[<n>] is the number of the database tab
|
||||
|Toggle Passwords Hidden | kbd:[Ctrl + Shift + C]
|
||||
|Toggle Usernames Hidden | kbd:[Ctrl + Shift + B]
|
||||
|Focus Groups (edit if focused) | kbd:[F1]
|
||||
|Focus Entries (edit if focused) | kbd:[F2]
|
||||
|Focus Search | kbd:[F3] +
|
||||
_or_ +
|
||||
kbd:[Ctrl + F]
|
||||
|Clear Search | kbd:[Esc]
|
||||
|Show Keyboard Shortcuts | kbd:[Ctrl + /]
|
||||
|Settings | Ctrl + ,
|
||||
|Open Database | Ctrl + O
|
||||
|Save Database | Ctrl + S
|
||||
|Save Database As | Ctrl + Shift + S
|
||||
|New Database | Ctrl + Shift + N
|
||||
|Close Database | Ctrl + W ; Ctrl + F4
|
||||
|Lock Current Database | Ctrl + L
|
||||
|Lock All Databases | Ctrl + Shift + L
|
||||
|Database Settings | Ctrl + Shift + ,
|
||||
|Database Reports | Ctrl + Shift + R
|
||||
|Quit | Ctrl + Q
|
||||
|New Entry | Ctrl + N
|
||||
|Edit Entry | Enter ; Ctrl + E
|
||||
|Delete Entry | Delete
|
||||
|Clone Entry | Ctrl + D
|
||||
|Copy Username | Ctrl + B
|
||||
|Copy Password | Ctrl + C
|
||||
|Copy URL | Ctrl + U
|
||||
|Open URL | Ctrl + Shift + U
|
||||
|Copy TOTP | Ctrl + T
|
||||
|Copy Password and TOTP | Ctrl + Y
|
||||
|Show TOTP | Ctrl + Shift + T
|
||||
|Trigger AutoType | Ctrl + Shift + V
|
||||
|Add key to SSH Agent | Ctrl + H
|
||||
|Remove key from SSH Agent | Ctrl + Shift + H
|
||||
|Move entry up (if unsorted) | Ctrl + Alt + Up
|
||||
|Move entry down (if unsorted) | Ctrl + Alt + Down
|
||||
|Sort Groups A-Z | Ctrl + Down
|
||||
|Sort Groups Z-A | Ctrl + Up
|
||||
|Minimize Window | Ctrl + M
|
||||
|Hide Window | Ctrl + Shift + M
|
||||
|Select Next Database Tab | Ctrl + Tab ; Ctrl + PageDn
|
||||
|Select Previous Database Tab | Ctrl + Shift + Tab ; Ctrl + PageUp
|
||||
|Select the nth database | Ctrl + n, where n is the number of the database tab
|
||||
|Toggle Passwords Hidden | Ctrl + Shift + C
|
||||
|Toggle Usernames Hidden | Ctrl + Shift + B
|
||||
|Focus Groups (edit if focused) | F1
|
||||
|Focus Entries (edit if focused) | F2
|
||||
|Focus Search | F3 ; Ctrl + F
|
||||
|Clear Search | Escape
|
||||
|Show Keyboard Shortcuts | Ctrl + /
|
||||
|===
|
||||
// end::content[]
|
||||
|
||||
@@ -19,8 +19,8 @@ image::password_generator.png[]
|
||||
|
||||
3. Select the length of the desired password by dragging the Length slider.
|
||||
4. Select the character-sets that you want to include in your password.
|
||||
5. Use the regenerate button (kbd:[Ctrl + R]) to make a new password using the chosen options.
|
||||
6. Use the clipboard button (kbd:[Ctrl + C]) to copy the generated password to the clipboard.
|
||||
5. Use the regenerate button (Ctrl + R) to make a new password using the chosen options.
|
||||
6. Use the clipboard button (Ctrl + C) to copy the generated password to the clipboard.
|
||||
7. Click the Advanced button to specify additional conditions for your desired password.
|
||||
+
|
||||
.Advanced Password Generator Options
|
||||
@@ -39,6 +39,6 @@ Word Count slider.
|
||||
3. In the Word Separator field, enter a character, word, number, or space that you want to use as a separator between the words in your passphrase.
|
||||
4. _(Optional)_ You can choose a word case between lower, upper, and title case options.
|
||||
5. _(Optional)_ You can also load your own custom word lists. Click the plus sign button to the right of the wordlist selection dialog to choose a custom word list. You can download alternative lists from the https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases[EFF's Website] or from https://github.com/redacted/XKCD-password-generator#additional-languages[GitHub].
|
||||
6. Click the Regenerate button (kbd:[Ctrl + R]) to generate a new random passphrase.
|
||||
7. Click the Clipboard button (kbd:[Ctrl + C]) to copy the passphrase to the clipboard.
|
||||
6. Click the Regenerate button (Ctrl + R) to generate a new random passphrase.
|
||||
7. Click the Clipboard button (Ctrl + C) to copy the passphrase to the clipboard.
|
||||
// end::content[]
|
||||
|
||||
@@ -77,8 +77,8 @@ Examples: +
|
||||
|Press the corresponding keyboard key
|
||||
|
||||
|{UP}, {DOWN}, {LEFT}, {RIGHT} |Press the corresponding arrow key
|
||||
|{F1}, {F2}, ..., {F16} |Press kbd:[F1], kbd:[F2], etc.
|
||||
|{LEFTBRACE}, {RIGHTBRACE} |Press kbd:[{] or kbd:[}], respectively
|
||||
|{F1}, {F2}, ..., {F16} |Press F1, F2, etc.
|
||||
|{LEFTBRACE}, {RIGHTBRACE} |Press `{` or `}`, respectively
|
||||
|{<KEY> X} |Repeat <KEY> X times (e.g., {SPACE 5} inserts five spaces)
|
||||
|{DELAY=X} |Set delay between key presses to X milliseconds
|
||||
|{DELAY X} |Pause typing for X milliseconds
|
||||
@@ -90,10 +90,10 @@ Examples: +
|
||||
|===
|
||||
|Modifier |Description
|
||||
|
||||
|+ |kbd:[Shift]
|
||||
|^ |kbd:[Ctrl]
|
||||
|% |kbd:[Alt]
|
||||
|# |kbd:[Win]/kbd:[Cmd]
|
||||
|+ |SHIFT
|
||||
|^ |CTRL
|
||||
|% |ALT
|
||||
|# |WIN/CMD
|
||||
|===
|
||||
*Text Conversions:*
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
= KeePassXC – Secret Service Integration
|
||||
include::.sharedheader[]
|
||||
:imagesdir: ../images
|
||||
|
||||
// tag::content[]
|
||||
== Secret Service Integration
|
||||
This feature allows KeePassXC to act as a Secret Service provider over DBus. It enables applications to store and retrieve secrets securely via the https://www.freedesktop.org/wiki/Specifications/secret-storage-spec/[Secret Storage specification]. While running, KeePassXC acts as a Secret Service server registered on DBus so clients like seahorse, python-secretstorage, secret-tool, or other implementations can connect and access the exposed database in KeePassXC.
|
||||
|
||||
=== Enabling the Integration
|
||||
Only one secret service provider can be enabled at a time. You may have to disable other providers, such as GNOME Keyring or KWallet, to use KeePassXC as a secret service provider. You will see a notice when attempting to enable KeePassXC as the secret service provider if another is already running.
|
||||
|
||||
To replace most third party secret service providers with KeePassXC, run the following shell snippet:
|
||||
|
||||
```bash
|
||||
mkdir -p "${XDG_DATA_HOME:-${HOME}/.local/share}/dbus-1/services"
|
||||
cat > "${XDG_DATA_HOME:-${HOME}/.local/share}/dbus-1/services/org.freedesktop.secrets.service" <<EOF
|
||||
[D-BUS Service]
|
||||
Name=org.freedesktop.secrets
|
||||
Exec=/usr/bin/keepassxc
|
||||
EOF
|
||||
```
|
||||
|
||||
NOTE: You may need to restart your session or log out and back in for the changes to take effect.
|
||||
|
||||
1. Open KeePassXC → **Tools → Settings → Secret Service Integration** → check **Enable KeePassXC Freedesktop.org Secret Service Integration**. Then press OK to save this setting and enable the integration. Go back into this settings screen to see currently open databases that you can unlock and edit their exposure to secret service.
|
||||
+
|
||||
.Secret Service Settings
|
||||
image::secretservice_enable_settings.png[]
|
||||
|
||||
2. Either click the pencil icon in the previous settings screen, or go to **Database → Database Settings → Secret Service Integration**. Enable **Expose entries under this group**, and select the desired group. All entries within this group and all subgroups will be exposed to the service.
|
||||
+
|
||||
.Secret Service Database Settings
|
||||
image::secretservice_database_settings.png[]
|
||||
|
||||
3. Use apps that integrate with secret service to start saving and using credentials within KeePassXC. If you enabled confirmation prior to access, you will see the following dialog:
|
||||
+
|
||||
.Secret Service Access Confirmation Dialog
|
||||
image::secretservice_access_dialog.png[]
|
||||
|
||||
TIP: When applications use `secret-tool` and you have access confirmation enabled, then you will be prompted each time credentials are requested. This is due to `secret-tool` obtaining a new process id each time it is run.
|
||||
|
||||
=== Implementation Details
|
||||
|
||||
* The user can specify the database and group that is exposed to the service.
|
||||
* Desktop notifications when a secret is retrieved and access confirmation dialogs.
|
||||
* `FdoSecrets::Service` is the top level DBus service. There is one `FdoSecrets::Collection` per opened database tab and each entry under the exposed database group has a corresponding `FdoSecrets::Item` DBus object.
|
||||
* The following entry attributes are exposed to the secret service: Title, Username, Password, URL, Notes, TOTP, and non-protected Custom Attributes.
|
||||
// end::content[]
|
||||
@@ -18,16 +18,7 @@ image::main_interface.png[]
|
||||
|
||||
*(D) Preview* – Shows a preview of the selected group or entry. You can interact with most information stored in an entry from here without opening the entry for editing. You can temporarily hide this preview using the down-arrow button on the right hand side or completely disable it from the View menu.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Starting with version 2.7.0, double-click copying of entry usernames and passwords is disabled by default.
|
||||
|
||||
To enable it:
|
||||
|
||||
. Open *KeePassXC*, and navigate to *Tools* → *Settings*.
|
||||
. In the left sidebar, select *General*.
|
||||
. Under *Entry Management*, check the box for _"Copy data on double clicking field in entry view"_.
|
||||
====
|
||||
TIP: You can enable double-click copying of entry username and password in the Application Security Settings. This is turned off by default starting with version 2.7.0.
|
||||
|
||||
=== Toolbar
|
||||
The toolbar provides a quick way to perform common tasks with your database. Some entries in the toolbar are dynamically disabled based on the information contained in the selected entry. Every common action in KeePassXC can be controlled with a keyboard shortcut as well.
|
||||
|
||||
20
release-tool
@@ -47,7 +47,6 @@ CMAKE_OPTIONS=""
|
||||
CPACK_GENERATORS="WIX;ZIP"
|
||||
COMPILER="g++"
|
||||
MAKE_OPTIONS="-j$(getconf _NPROCESSORS_ONLN)"
|
||||
BUILD_PLUGINS="all"
|
||||
INSTALL_PREFIX="/usr/local"
|
||||
ORIG_BRANCH=""
|
||||
ORIG_CWD="$(pwd)"
|
||||
@@ -132,8 +131,6 @@ Options:
|
||||
-m, --make-options Make options for compiling sources (default: '${MAKE_OPTIONS}')
|
||||
-g, --generators Additional CPack generators (default: '${CPACK_GENERATORS}')
|
||||
-i, --install-prefix Install prefix (default: '${INSTALL_PREFIX}')
|
||||
-p, --plugins Space-separated list of plugins to build
|
||||
(default: ${BUILD_PLUGINS})
|
||||
--snapshot Don't checkout the release tag
|
||||
-n, --no-source-tarball Don't build source tarball
|
||||
-h, --help Show this help
|
||||
@@ -829,10 +826,6 @@ build() {
|
||||
INSTALL_PREFIX="$2"
|
||||
shift ;;
|
||||
|
||||
-p|--plugins)
|
||||
BUILD_PLUGINS="$2"
|
||||
shift ;;
|
||||
|
||||
-n|--no-source-tarball)
|
||||
build_source_tarball=false ;;
|
||||
|
||||
@@ -870,13 +863,9 @@ build() {
|
||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Snapshot -DOVERRIDE_VERSION=${RELEASE_NAME}"
|
||||
else
|
||||
checkWorkingTreeClean
|
||||
if echo "$TAG_NAME" | grep -qE '\-(alpha|beta)[0-9]+$'; then
|
||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=PreRelease"
|
||||
logInfo "Checking out pre-release tag '${TAG_NAME}'..."
|
||||
else
|
||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Release"
|
||||
logInfo "Checking out release tag '${TAG_NAME}'..."
|
||||
fi
|
||||
|
||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Release"
|
||||
logInfo "Checking out release tag '${TAG_NAME}'..."
|
||||
|
||||
if ! git checkout "$TAG_NAME" > /dev/null 2>&1; then
|
||||
exitError "Failed to check out target branch."
|
||||
@@ -922,9 +911,6 @@ build() {
|
||||
cd "${OUTPUT_DIR}/build-release"
|
||||
|
||||
logInfo "Configuring sources..."
|
||||
for p in ${BUILD_PLUGINS}; do
|
||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_XC_$(echo $p | tr '[:lower:]' '[:upper:]')=On"
|
||||
done
|
||||
if [ -n "$OS_LINUX" ] && ${build_appimage}; then
|
||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_DIST_TYPE=AppImage"
|
||||
# linuxdeploy requires /usr as install prefix
|
||||
|
||||
@@ -253,7 +253,6 @@ function Invoke-GpgSignFiles([string[]] $files, [string] $key) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Handle errors and restore state
|
||||
$OrigDir = (Get-Location).Path
|
||||
$OrigBranch = & git rev-parse --abbrev-ref HEAD
|
||||
@@ -345,6 +344,7 @@ if ($Merge) {
|
||||
Write-Host "All done!"
|
||||
Write-Host "Please merge the release branch back into the develop branch now and then push your changes."
|
||||
Write-Host "Don't forget to also push the tags using 'git push --tags'."
|
||||
|
||||
} elseif ($Build) {
|
||||
$Vcpkg = (Resolve-Path "$Vcpkg/scripts/buildsystems/vcpkg.cmake").Path
|
||||
|
||||
@@ -357,6 +357,13 @@ if ($Merge) {
|
||||
|
||||
Test-RequiredPrograms
|
||||
|
||||
# Create directories
|
||||
New-Item "$OutDir" -ItemType Directory -Force | Out-Null
|
||||
$OutDir = (Resolve-Path $OutDir).Path
|
||||
|
||||
$BuildDir = "$OutDir\build-release"
|
||||
New-Item "$BuildDir" -ItemType Directory -Force | Out-Null
|
||||
|
||||
if ($Snapshot) {
|
||||
$Tag = "HEAD"
|
||||
$SourceBranch = & git rev-parse --abbrev-ref HEAD
|
||||
@@ -366,38 +373,26 @@ if ($Merge) {
|
||||
} else {
|
||||
Test-WorkingTreeClean
|
||||
|
||||
# Clear output directory
|
||||
if (Test-Path $OutDir) {
|
||||
Remove-Item $OutDir -Recurse
|
||||
# Clear build directory except for installed vcpkg files to prevent having to re-sign everything
|
||||
if (Test-Path $BuildDir) {
|
||||
Get-ChildItem $BuildDir -Recurse | Where-Object {$_.FullName -notlike "$BuildDir\vcpkg_installed*"} | Remove-Item -Recurse -Force
|
||||
}
|
||||
|
||||
if ($Version -match "-beta\d*$") {
|
||||
$CMakeOptions = "-DKEEPASSXC_BUILD_TYPE=PreRelease $CMakeOptions"
|
||||
} else {
|
||||
$CMakeOptions = "-DKEEPASSXC_BUILD_TYPE=Release $CMakeOptions"
|
||||
}
|
||||
$CMakeOptions = "-DKEEPASSXC_BUILD_TYPE=Release $CMakeOptions"
|
||||
|
||||
# Setup Tag if not defined then checkout tag
|
||||
if ($Tag -eq "" -or $Tag -eq $null) {
|
||||
if ($Tag -eq "" -or $null -eq $Tag) {
|
||||
$Tag = $Version
|
||||
}
|
||||
Write-Host "Checking out tag 'tags/$Tag' to build." -ForegroundColor Cyan
|
||||
Invoke-Cmd "git" "checkout `"tags/$Tag`""
|
||||
}
|
||||
|
||||
# Create directories
|
||||
New-Item "$OutDir" -ItemType Directory -Force | Out-Null
|
||||
$OutDir = (Resolve-Path $OutDir).Path
|
||||
|
||||
$BuildDir = "$OutDir\build-release"
|
||||
New-Item "$BuildDir" -ItemType Directory -Force | Out-Null
|
||||
|
||||
# Enter build directory
|
||||
Set-Location "$BuildDir"
|
||||
|
||||
# Setup CMake options
|
||||
$CMakeOptions = "-DWITH_XC_ALL=ON -DWITH_TESTS=OFF -DCMAKE_BUILD_TYPE=Release $CMakeOptions"
|
||||
$CMakeOptions = "-DCMAKE_TOOLCHAIN_FILE:FILEPATH=`"$Vcpkg`" -DX_VCPKG_APPLOCAL_DEPS_INSTALL=ON $CMakeOptions"
|
||||
$CMakeOptions = "-DWITH_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE:FILEPATH=`"$Vcpkg`" -DX_VCPKG_APPLOCAL_DEPS_INSTALL=ON"
|
||||
|
||||
Write-Host "Configuring build..." -ForegroundColor Cyan
|
||||
Invoke-Cmd "cmake" "-G `"$CMakeGenerator`" $CMakeOptions `"$SourceDir`""
|
||||
@@ -436,6 +431,7 @@ if ($Merge) {
|
||||
# Restore state
|
||||
Invoke-Command {git checkout $OrigBranch}
|
||||
Set-Location "$OrigDir"
|
||||
|
||||
} elseif ($Sign) {
|
||||
Test-RequiredPrograms
|
||||
|
||||
@@ -459,8 +455,8 @@ if ($Merge) {
|
||||
# SIG # Begin signature block
|
||||
# MIImVAYJKoZIhvcNAQcCoIImRTCCJkECAQExDzANBglghkgBZQMEAgEFADB5Bgor
|
||||
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
|
||||
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCRMgDV7DQ6PzRo
|
||||
# 3ULpsxL1VU2JvIFnZPXlxq/hkfU2Y6CCH2owggYUMIID/KADAgECAhB6I67aU2mW
|
||||
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBnhvt4NN16Hfle
|
||||
# eDfbtgcr43dOfERdBCPSlLlozTRPoaCCH2owggYUMIID/KADAgECAhB6I67aU2mW
|
||||
# D5HIPlz0x+M/MA0GCSqGSIb3DQEBDAUAMFcxCzAJBgNVBAYTAkdCMRgwFgYDVQQK
|
||||
# Ew9TZWN0aWdvIExpbWl0ZWQxLjAsBgNVBAMTJVNlY3RpZ28gUHVibGljIFRpbWUg
|
||||
# U3RhbXBpbmcgUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNMzYwMzIxMjM1OTU5
|
||||
@@ -632,34 +628,34 @@ if ($Merge) {
|
||||
# Z28gTGltaXRlZDErMCkGA1UEAxMiU2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5n
|
||||
# IENBIFIzNgIQBkM/zMzkM6iSzBe3RqWMZTANBglghkgBZQMEAgEFAKCBhDAYBgor
|
||||
# BgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEE
|
||||
# MBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCCw
|
||||
# CjBOFSrHIl5SZxVeFP1D+IfXa4B5pNieNHIkm0/SqTANBgkqhkiG9w0BAQEFAASC
|
||||
# AgAgFK2xkUz0aie9HSo0e4qyDk83CNX9G/GR7+DObTay5l7OYVZIdB2kOZIS8UbH
|
||||
# 4gMSsjplIVObVyf1DjGGCctq4bFDABL7wpwqm7P3tEjs2d/HK2Yxoe1c8YFTYMJJ
|
||||
# Vc6Q9l/nZA7ZC/SCH1NyEgK+w3vQ6SARudN8/ZgFVa1P3DdwOADmLD774v3bOUKq
|
||||
# XKDOySeYD7bkCekPv6yx6DnrWBBsYIKFRv2Yv4duThki4CC1FMgEVTmdBDJIP3R8
|
||||
# 1BgXjPvVxYX3aQ9emC3KluyNr/BEPZiVdwBjXCE60n7g/Y8qNgqY0ZaImSpl9MFx
|
||||
# VkrxE7iNfBcBE8xVCghyDahs1BxyEeEdQk+QlLD1Cv3KGODlyWjgncDAX7fnkC6l
|
||||
# M7KUttjXGi9uQG3g2dUCX+744wPhRg+DBfch2Em70I0kYsPY6ETyrQogZdi6QzKO
|
||||
# Hlf/hUW0o9HCc6BrTSL4y8G0mlKVCgUpMOjlrip88bvW05ZUX20arGKxGg1uxFIA
|
||||
# r7wvQyFn+RvNc0kqWt/xgwp3HAc80ABPCYumLqGwucBWisiMt4P2s+fkLpYJdC/n
|
||||
# pS/3fRoepfGmv8J1WAIjGiO7e12aDrTQqNP+2RUzkNpy2eRQDL+3VUFQOQqEfkVL
|
||||
# Y6wpN6nB7olNULhPUlwZChf49v/h+XUxhgHozWN576qoyqGCAyIwggMeBgkqhkiG
|
||||
# MBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCB3
|
||||
# VDtldlpqUJGX7ENprygJeJxr+V2oUTlguFLaSOkkcTANBgkqhkiG9w0BAQEFAASC
|
||||
# AgAIu1lvIg5zEwMJ87vlGg8H5BvvXdCiQllHuZir0ULpGs/IG03o3uvL9bI8IEt0
|
||||
# Elq5ePQ1qlg2z3BT3a+7+SyMChGVVoko3U60aQyjjlXPtzh7HUEKdQWDyrabHmEk
|
||||
# YACxpRF/h6FoJAkQqVr5cF5d+Ey8SdfE9h9MyqcaxeWtJPEFI/qjp4sfQuJZuXpN
|
||||
# saallK9xM+xKrigwTx8s7C/clWZX8OtAIIegpf8mnU5ETYS6XnvefcAMsXKBmxjI
|
||||
# 01GadXwlpJ/I7d/1LrZ5OjOmqVrAWnJEOkdV5+5xJbnJInPvgnBOrLeQyR7QEU6V
|
||||
# XtiD6k+LqgfU7bP8AQCouz33M2DdxmfKZPPReMuZaNI3RvNX9M49wAXey/iuy+pm
|
||||
# G9+DwGT3IHCIhOrL5GimdTO8vcVkdzIFdPgAb/jOR4n8P3VRaPH1sH0rO5vHuwfG
|
||||
# nO7Mrb4AMYo4sP0RXkIGWXhj0Z7VrajAYHtcSnwCedUbHxk9faDbF5Vrh4kTCt+T
|
||||
# utMXrepDzK6kWtr8BX68aA/meoSzN0ZhOchWbWXxC4kPSTtNTnBHAWVKaCjxBVq4
|
||||
# J+CTlKGLdj78SVMTeE8guyifYNiRkrqRCuXghKQZEIN0ry1SbpQA27wZQWVJrL8E
|
||||
# XE0F54gWMGrwqCik1GSfAgPYZd3IWdLc6jPR9ec/uS/QaaGCAyIwggMeBgkqhkiG
|
||||
# 9w0BCQYxggMPMIIDCwIBATBpMFUxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0
|
||||
# aWdvIExpbWl0ZWQxLDAqBgNVBAMTI1NlY3RpZ28gUHVibGljIFRpbWUgU3RhbXBp
|
||||
# bmcgQ0EgUjM2AhA6UmoshM5V5h1l/MwS2OmJMA0GCWCGSAFlAwQCAgUAoHkwGAYJ
|
||||
# KoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjQwOTE0MDMy
|
||||
# MTU1WjA/BgkqhkiG9w0BCQQxMgQwwrUMFcAva5866cdprEw/weWm4EfoAA4SCloN
|
||||
# B50191F7ps9XQIxGfsz+g0vQxzxfMA0GCSqGSIb3DQEBAQUABIICAC3qVFmWQWkL
|
||||
# kn/AYJPZ3B7Yvwq0P7SqcHO9w5FiV5wsznH6xfvkTzXssQLhKaZdqypnHCTNth8D
|
||||
# 7mgr6zZYh5CgQQ3SSG2q0xVzs3wanJmZ4g6I7bVeGMLv47tFnCed9G3aP5cywDBn
|
||||
# vMOiwZnQR1WwM8T6qE4sAb4lKXUYDbIVB1DMRAF3j2rQMAN9e9jF6Ok+ZyQqpBSl
|
||||
# ve2vBR0TgFXeyidwiz6O2I1FWc1OzwMchbJTANbQqWRKuiQ6gm0Bj/S8dalBb77I
|
||||
# jxS0Tn7kRH1Sr50ZfWRSxj7H7afsQOKbDHxhWFhctvQfbrmbNj+gHcm9j/rSPpU7
|
||||
# zj5OvgKyYQnjiLjCnGBTmSML2ZwvXhPv2XkFQ2yL2nYWTRqLjARdcP62kSrkQxEa
|
||||
# DLAZ7mcndE+HZVMllBGVI9/H5hkE7jINBU4gNvyqQQqF3xTatJMldyrXCQ6R9wfN
|
||||
# LsdyFB177vZXLrS1EymCzq1COpbrw3oa/LXP+1hZFhoaOYy00LUnCU5Zjd8UFWIh
|
||||
# FDj3Z7O/Xz3P8BR4t7PGqUu3x8UbxcsGDH0w0e3pvPmxXiBZlspjNieg073YNKxU
|
||||
# Yuj0b3cX/cpYH0M0Ne/tXuHwbZthwwll3vytT7Aa+oglejolDQjRc8Gv5KW0dUK3
|
||||
# LmVw9eforeFUrTExSEc/0jf29BmZz9do
|
||||
# KoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjUwMTAzMDQx
|
||||
# MzI4WjA/BgkqhkiG9w0BCQQxMgQwKlrwmwJXs6mQ/rkiyCJkh8uDUj2DERGpO4aA
|
||||
# iq1TY56EYG/QvCktwMVZYRTI8Cu1MA0GCSqGSIb3DQEBAQUABIICAGyQ98hsyLlN
|
||||
# dOnWHLSncZ5ovUQBXEAeeMxuRhB3G7bpdcpi4a/t7q+tLzLH2CKrpCZ5dS0tbRd9
|
||||
# ng7kH4X8WbllElxzvnnwAmPrYDfX/VO8bVS/JJ8qmfrtgHhs+4dtMFkV11VXNRYs
|
||||
# IC2xbYMbwuH7cWA/L+muDADM0VLONKB3HECPhbIhfHCSKJr8i2cYyW3AixC77SPG
|
||||
# mimDoh3umEJLjMvCsGJ32BrVbXKR4hBCOxmDN470nxKDuEurW6JvDh6GjevcdzeT
|
||||
# ungxb0pnFIfj5IMhjjrZhccCQK1ekvFLmb/ybzfZGbXGb8QHJP4JvFm8sWNAccIn
|
||||
# dQ3SixhyKW+gOC2r0URTPrIDpbdHuGnm3LVBPhBijrJrv7w1/neEQFFLFXfrjgJb
|
||||
# K8zc2IMYPW+Vn/hclNGfK8EOBqLGvsn7xB0K1f13kVt7QT36nIYojIE5MwWhvPDi
|
||||
# 1oIDdJEvZS/P8tmX6lwAXxPRQagPVhNefUUHk11/kUaBUGGi+bRpKOgcS1fPqsP1
|
||||
# 1RQMSEzjsnGEzLxxqa+AtxLDre2vBJsllCD4JJwrpkQW+xu014bZleJ3i1IFDUaR
|
||||
# E16d6Qi4KEC0RfZ1NwahKQFqBj3wBCMLGEY6fNndE4jVGO0BkP3vO23pt7n4xmM+
|
||||
# swJJsR48RDIDkSiV+2vPvlk/9KXVW9ji
|
||||
# SIG # End signature block
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M14.47 15.08L11 13V7H12.5V12.25L15.58 14.08C15.17 14.36 14.79 14.7 14.47 15.08M13.08 19.92C12.72 19.97 12.37 20 12 20C7.58 20 4 16.42 4 12S7.58 4 12 4 20 7.58 20 12C20 12.37 19.97 12.72 19.92 13.08C20.61 13.18 21.25 13.4 21.84 13.72C21.94 13.16 22 12.59 22 12C22 6.5 17.5 2 12 2S2 6.5 2 12C2 17.5 6.47 22 12 22C12.59 22 13.16 21.94 13.72 21.84C13.4 21.25 13.18 20.61 13.08 19.92M21.12 15.46L19 17.59L16.88 15.47L15.47 16.88L17.59 19L15.47 21.12L16.88 22.54L19 20.41L21.12 22.54L22.54 21.12L20.41 19L22.54 16.88L21.12 15.46Z" /></svg>
|
||||
|
Before Width: | Height: | Size: 602 B |
@@ -92,7 +92,6 @@
|
||||
<file>application/scalable/actions/totp-copy.svg</file>
|
||||
<file>application/scalable/actions/totp-copy-password.svg</file>
|
||||
<file>application/scalable/actions/totp-edit.svg</file>
|
||||
<file>application/scalable/actions/totp-invalid.svg</file>
|
||||
<file>application/scalable/actions/trash.svg</file>
|
||||
<file>application/scalable/actions/url-copy.svg</file>
|
||||
<file>application/scalable/actions/user-guide.svg</file>
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
<!-- Custom Controls for KPXC Installer -->
|
||||
<Control Id="DesktopShortcutCheckBox" Type="CheckBox" X="20" Y="150" Width="290" Height="17" Property="INSTALLDESKTOPSHORTCUT" CheckBoxValue="1" Text="Create a shortcut on the desktop" />
|
||||
<Control Id="AutostartCheckBox" Type="CheckBox" X="20" Y="170" Width="290" Height="17" Property="AUTOSTARTPROGRAM" CheckBoxValue="1" Text="Autostart KeePassXC on login" />
|
||||
<Control Id="AddToPathCheckBox" Type="CheckBox" X="20" Y="190" Width="290" Height="17" Property="ADDTOPATH" CheckBoxValue="1" Text="Add KeePassXC to the PATH environment variable" />
|
||||
</Dialog>
|
||||
</UI>
|
||||
</Fragment>
|
||||
|
||||
@@ -56,13 +56,6 @@
|
||||
<Condition>AUTOSTARTPROGRAM</Condition>
|
||||
</Component>
|
||||
|
||||
<!-- Add KeePassXC to PATH -->
|
||||
<Component Id="AddKeePassXCToPath" Guid="*" Directory="INSTALL_ROOT">
|
||||
<Condition>ADDTOPATH</Condition>
|
||||
<Environment Id="KeePassXCSystemPathEntryENV" Action="set" Part="last" Name="PATH" Value="[INSTALL_ROOT]" System="yes"/>
|
||||
<RegistryValue Root="HKCU" Key="Software\KeePassXC" Name="AddedToPATH" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<DirectoryRef Id="TARGETDIR">
|
||||
<!-- Startmenu shortcuts -->
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
@@ -80,7 +73,6 @@
|
||||
Name="KeePassXC - User Guide"
|
||||
Target="[#CM_FP_share.docs.KeePassXC_UserGuide.html]"
|
||||
WorkingDirectory="INSTALL_ROOT" />
|
||||
<RemoveFile Id="RemoveShortcuts" Name="*.*" On="uninstall" />
|
||||
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall" />
|
||||
<RegistryValue Root="HKCU" Key="Software\KeePassXC" Name="StartMenuShortcut" Type="integer" Value="1" KeyPath="yes"/>
|
||||
</Component>
|
||||
@@ -112,21 +104,15 @@
|
||||
<RegistrySearch Id="AutoStartSearch" Root="HKCU" Key="Software\Microsoft\Windows\CurrentVersion\Run" Name="$(var.CPACK_PACKAGE_NAME)" Type="raw" />
|
||||
</Property>
|
||||
<Property Id="INSTALLDESKTOPSHORTCUT" Secure="yes" />
|
||||
<Property Id="ADDTOPATH" Value="1" Secure="yes" />
|
||||
<Property Id="ADDTOPATH_REGISTRY">
|
||||
<RegistrySearch Id="AddToPathSearch" Root="HKCU" Key="Software\KeePassXC" Name="AddedToPATH" Type="raw" />
|
||||
</Property>
|
||||
|
||||
<!-- Set properties based on existing conditions, prevents changing state on upgrade -->
|
||||
<SetProperty Id="AUTOSTARTPROGRAM" After="AppSearch" Value="" Sequence="first">AUTOSTARTPROGRAM="0" OR (WIX_UPGRADE_DETECTED AND NOT AUTOSTARTPROGRAM_REGISTRY)</SetProperty>
|
||||
<SetProperty Id="ADDTOPATH" After="AppSearch" Value="" Sequence="first">ADDTOPATH="0" OR (WIX_UPGRADE_DETECTED AND NOT ADDTOPATH_REGISTRY)</SetProperty>
|
||||
<SetProperty Id="AUTOSTARTPROGRAM" After="AppSearch" Value="">AUTOSTARTPROGRAM="0" OR (WIX_UPGRADE_DETECTED AND NOT AUTOSTARTPROGRAM_REGISTRY)</SetProperty>
|
||||
<SetProperty Id="LicenseAccepted" After="AppSearch" Value="1">WIX_UPGRADE_DETECTED</SetProperty>
|
||||
|
||||
<FeatureRef Id="ProductFeature">
|
||||
<ComponentRef Id="ApplicationShortcuts" />
|
||||
<ComponentRef Id="Autostart" />
|
||||
<ComponentRef Id="DesktopShortcut" />
|
||||
<ComponentRef Id="AddKeePassXCToPath"/>
|
||||
</FeatureRef>
|
||||
|
||||
<!-- Action to launch application after installer exits -->
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2023 KeePassXC Team <team@keepassxc.org>
|
||||
# Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
# Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@@ -16,18 +16,6 @@
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
add_feature_info(Auto-Type WITH_XC_AUTOTYPE "Automatic password typing")
|
||||
add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network access code (e.g. for downloading website icons)")
|
||||
add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with KeePassXC-Browser")
|
||||
add_feature_info(Passkeys WITH_XC_BROWSER_PASSKEYS "Passkeys support for browser integration")
|
||||
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
|
||||
add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare")
|
||||
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
|
||||
add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking")
|
||||
if(UNIX AND NOT APPLE)
|
||||
add_feature_info(FdoSecrets WITH_XC_FDOSECRETS "Implement freedesktop.org Secret Storage Spec server side API.")
|
||||
endif()
|
||||
|
||||
set(core_SOURCES
|
||||
core/Alloc.cpp
|
||||
core/AutoTypeAssociations.cpp
|
||||
@@ -96,6 +84,11 @@ set(core_SOURCES
|
||||
keys/FileKey.cpp
|
||||
keys/PasswordKey.cpp
|
||||
keys/ChallengeResponseKey.cpp
|
||||
keys/drivers/YubiKey.h
|
||||
keys/drivers/YubiKey.cpp
|
||||
keys/drivers/YubiKeyInterface.cpp
|
||||
keys/drivers/YubiKeyInterfaceUSB.cpp
|
||||
keys/drivers/YubiKeyInterfacePCSC.cpp
|
||||
streams/HashedBlockStream.cpp
|
||||
streams/HashingStream.cpp
|
||||
streams/HmacBlockStream.cpp
|
||||
@@ -134,7 +127,6 @@ set(gui_SOURCES
|
||||
gui/IconModels.cpp
|
||||
gui/KMessageWidget.cpp
|
||||
gui/MainWindow.cpp
|
||||
gui/MergeDialog.cpp
|
||||
gui/MessageBox.cpp
|
||||
gui/MessageWidget.cpp
|
||||
gui/PasswordWidget.cpp
|
||||
@@ -160,14 +152,8 @@ set(gui_SOURCES
|
||||
gui/entry/EntryAttachmentsModel.cpp
|
||||
gui/entry/EntryAttachmentsWidget.cpp
|
||||
gui/entry/EntryAttributesModel.cpp
|
||||
gui/entry/EditEntryAttachmentsDialog.cpp
|
||||
gui/entry/NewEntryAttachmentsDialog.cpp
|
||||
gui/entry/PreviewEntryAttachmentsDialog.cpp
|
||||
gui/entry/attachments/TextAttachmentsWidget.cpp
|
||||
gui/entry/attachments/ImageAttachmentsWidget.cpp
|
||||
gui/entry/attachments/ImageAttachmentsView.cpp
|
||||
gui/entry/attachments/TextAttachmentsPreviewWidget.cpp
|
||||
gui/entry/attachments/TextAttachmentsEditWidget.cpp
|
||||
gui/entry/attachments/AttachmentWidget.cpp
|
||||
gui/entry/EntryHistoryModel.cpp
|
||||
gui/entry/EntryModel.cpp
|
||||
gui/entry/EntryView.cpp
|
||||
@@ -201,6 +187,7 @@ set(gui_SOURCES
|
||||
gui/reports/ReportsPageHibp.cpp
|
||||
gui/reports/ReportsWidgetStatistics.cpp
|
||||
gui/reports/ReportsPageStatistics.cpp
|
||||
gui/osutils/DeviceListener.cpp
|
||||
gui/osutils/OSUtilsBase.cpp
|
||||
gui/osutils/ScreenLockListener.cpp
|
||||
gui/osutils/ScreenLockListenerPrivate.cpp
|
||||
@@ -222,6 +209,7 @@ set(gui_SOURCES
|
||||
|
||||
if(APPLE)
|
||||
list(APPEND gui_SOURCES
|
||||
gui/osutils/macutils/DeviceListenerMac.cpp
|
||||
gui/osutils/macutils/MacPasteboard.cpp
|
||||
gui/osutils/macutils/MacUtils.cpp
|
||||
gui/osutils/macutils/ScreenLockListenerMac.cpp
|
||||
@@ -235,14 +223,16 @@ endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
list(APPEND gui_SOURCES
|
||||
gui/osutils/nixutils/DeviceListenerLibUsb.cpp
|
||||
gui/osutils/nixutils/NixUtils.cpp
|
||||
gui/osutils/nixutils/ScreenLockListenerDBus.cpp
|
||||
gui/osutils/nixutils/NixUtils.cpp)
|
||||
)
|
||||
if("${CMAKE_SYSTEM}" MATCHES "Linux")
|
||||
list(APPEND core_SOURCES
|
||||
quickunlock/Polkit.cpp
|
||||
quickunlock/PolkitDbusTypes.cpp)
|
||||
endif()
|
||||
if(WITH_XC_X11)
|
||||
if(WITH_X11)
|
||||
list(APPEND gui_SOURCES
|
||||
gui/osutils/nixutils/X11Funcs.cpp)
|
||||
endif()
|
||||
@@ -260,15 +250,11 @@ if(UNIX AND NOT APPLE)
|
||||
quickunlock/dbus/org.freedesktop.PolicyKit1.Authority.xml
|
||||
polkit_dbus
|
||||
)
|
||||
|
||||
find_library(KEYUTILS_LIBRARIES NAMES keyutils)
|
||||
if(NOT KEYUTILS_LIBRARIES)
|
||||
message(FATAL_ERROR "Could not find libkeyutils")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
list(APPEND gui_SOURCES
|
||||
gui/osutils/winutils/DeviceListenerWin.cpp
|
||||
gui/osutils/winutils/ScreenLockListenerWin.cpp
|
||||
gui/osutils/winutils/WinUtils.cpp)
|
||||
if (MSVC)
|
||||
@@ -276,86 +262,24 @@ if(WIN32)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_XC_YUBIKEY)
|
||||
list(APPEND gui_SOURCES gui/osutils/DeviceListener.cpp)
|
||||
if(APPLE)
|
||||
list(APPEND gui_SOURCES gui/osutils/macutils/DeviceListenerMac.cpp)
|
||||
elseif(UNIX)
|
||||
list(APPEND gui_SOURCES gui/osutils/nixutils/DeviceListenerLibUsb.cpp)
|
||||
elseif(WIN32)
|
||||
list(APPEND gui_SOURCES gui/osutils/winutils/DeviceListenerWin.cpp)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(browser)
|
||||
add_subdirectory(proxy)
|
||||
if(WITH_XC_BROWSER)
|
||||
if(KPXC_FEATURE_BROWSER)
|
||||
set(browser_LIB browser)
|
||||
list(APPEND gui_SOURCES
|
||||
gui/dbsettings/DatabaseSettingsWidgetBrowser.cpp
|
||||
gui/entry/EntryURLModel.cpp
|
||||
gui/reports/ReportsWidgetBrowserStatistics.cpp
|
||||
gui/reports/ReportsPageBrowserStatistics.cpp)
|
||||
endif()
|
||||
|
||||
if(WITH_XC_BROWSER_PASSKEYS)
|
||||
list(APPEND gui_SOURCES
|
||||
gui/reports/ReportsWidgetPasskeys.cpp
|
||||
gui/reports/ReportsPageBrowserStatistics.cpp
|
||||
gui/reports/ReportsPagePasskeys.cpp
|
||||
gui/reports/ReportsWidgetBrowserStatistics.cpp
|
||||
gui/reports/ReportsWidgetPasskeys.cpp
|
||||
gui/passkeys/PasskeyExporter.cpp
|
||||
gui/passkeys/PasskeyExportDialog.cpp
|
||||
gui/passkeys/PasskeyImporter.cpp
|
||||
gui/passkeys/PasskeyImportDialog.cpp)
|
||||
endif()
|
||||
|
||||
add_subdirectory(autotype)
|
||||
add_subdirectory(cli)
|
||||
add_subdirectory(qrcode)
|
||||
set(qrcode_LIB qrcode)
|
||||
|
||||
add_subdirectory(keeshare)
|
||||
if(WITH_XC_KEESHARE)
|
||||
set(keeshare_LIB keeshare)
|
||||
endif()
|
||||
|
||||
add_subdirectory(sshagent)
|
||||
if(WITH_XC_SSHAGENT)
|
||||
set(sshagent_LIB sshagent)
|
||||
endif()
|
||||
|
||||
add_subdirectory(fdosecrets)
|
||||
if(WITH_XC_FDOSECRETS)
|
||||
set(fdosecrets_LIB fdosecrets)
|
||||
endif()
|
||||
|
||||
add_subdirectory(thirdparty)
|
||||
|
||||
set(autotype_SOURCES
|
||||
autotype/AutoType.cpp
|
||||
autotype/AutoTypeAction.cpp
|
||||
autotype/AutoTypeMatchModel.cpp
|
||||
autotype/AutoTypeMatchView.cpp
|
||||
autotype/AutoTypeSelectDialog.cpp
|
||||
autotype/PickcharsDialog.cpp
|
||||
autotype/WindowSelectComboBox.cpp)
|
||||
|
||||
add_library(autotype STATIC ${autotype_SOURCES})
|
||||
target_link_libraries(autotype Qt5::Core Qt5::Widgets)
|
||||
|
||||
if(WITH_XC_YUBIKEY)
|
||||
list(APPEND core_SOURCES
|
||||
keys/drivers/YubiKey.h
|
||||
keys/drivers/YubiKey.cpp
|
||||
keys/drivers/YubiKeyInterface.cpp
|
||||
keys/drivers/YubiKeyInterfaceUSB.cpp
|
||||
keys/drivers/YubiKeyInterfacePCSC.cpp)
|
||||
else()
|
||||
list(APPEND core_SOURCES
|
||||
keys/drivers/YubiKey.h
|
||||
keys/drivers/YubiKeyStub.cpp)
|
||||
endif()
|
||||
|
||||
if(WITH_XC_NETWORKING)
|
||||
if(KPXC_FEATURE_NETWORK)
|
||||
list(APPEND gui_SOURCES
|
||||
networking/HibpDownloader.cpp
|
||||
networking/NetworkManager.cpp
|
||||
@@ -365,12 +289,36 @@ if(WITH_XC_NETWORKING)
|
||||
gui/IconDownloaderDialog.cpp)
|
||||
endif()
|
||||
|
||||
add_subdirectory(cli)
|
||||
add_subdirectory(thirdparty)
|
||||
|
||||
add_subdirectory(autotype)
|
||||
set(autotype_LIB autotype)
|
||||
|
||||
# TODO: Refactor to gui sources
|
||||
add_subdirectory(qrcode)
|
||||
set(qrcode_LIB qrcode)
|
||||
|
||||
# TODO: Move to gui sources on refactor
|
||||
add_subdirectory(keeshare)
|
||||
set(keeshare_LIB keeshare)
|
||||
|
||||
add_subdirectory(sshagent)
|
||||
if(KPXC_FEATURE_SSHAGENT)
|
||||
set(sshagent_LIB sshagent)
|
||||
endif()
|
||||
|
||||
add_subdirectory(fdosecrets)
|
||||
if(KPXC_FEATURE_FDOSECRETS)
|
||||
set(fdosecrets_LIB fdosecrets)
|
||||
endif()
|
||||
|
||||
configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h)
|
||||
configure_file(git-info.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/git-info.h)
|
||||
|
||||
# Core Library Definition
|
||||
add_library(keepassxc_core STATIC ${core_SOURCES})
|
||||
set_target_properties(keepassxc_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE)
|
||||
set_target_properties(keepassxc_core PROPERTIES COMPILE_DEFINITIONS KPXC_BUILDING_CORE)
|
||||
target_link_libraries(keepassxc_core
|
||||
${qrcode_LIB}
|
||||
Qt5::Core
|
||||
@@ -386,12 +334,12 @@ target_link_libraries(keepassxc_core
|
||||
|
||||
# GUI Library Definition
|
||||
add_library(keepassxc_gui STATIC ${gui_SOURCES})
|
||||
set_target_properties(keepassxc_gui PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE)
|
||||
set_target_properties(keepassxc_gui PROPERTIES COMPILE_DEFINITIONS KPXC_BUILDING_CORE)
|
||||
target_link_libraries(keepassxc_gui
|
||||
keepassxc_core
|
||||
Qt5::Network
|
||||
Qt5::Widgets
|
||||
autotype
|
||||
${autotype_LIB}
|
||||
${browser_LIB}
|
||||
${fdosecrets_LIB}
|
||||
${keeshare_LIB}
|
||||
@@ -408,7 +356,7 @@ if(HAIKU)
|
||||
endif()
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_link_libraries(keepassxc_core Qt5::DBus ${LIBUSB_LIBRARIES})
|
||||
if(WITH_XC_X11)
|
||||
if(WITH_X11)
|
||||
target_link_libraries(keepassxc_gui Qt5::X11Extras X11)
|
||||
endif()
|
||||
include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS})
|
||||
@@ -546,7 +494,7 @@ if(WIN32)
|
||||
COMPONENT Runtime)
|
||||
|
||||
# install OpenSSL library
|
||||
if(WITH_XC_NETWORKING)
|
||||
if(KPXC_FEATURE_NETWORK)
|
||||
find_file(OPENSSL_DLL
|
||||
NAMES libssl-3.dll libssl-3-x64.dll
|
||||
HINTS "${OPENSSL_ROOT_DIR}/bin"
|
||||
|
||||
@@ -148,9 +148,7 @@ AutoType::AutoType(QObject* parent, bool test)
|
||||
QString pluginPath = resources()->pluginPath(pluginName);
|
||||
|
||||
if (!pluginPath.isEmpty()) {
|
||||
#ifdef WITH_XC_AUTOTYPE
|
||||
loadPlugin(pluginPath);
|
||||
#endif
|
||||
}
|
||||
|
||||
connect(this, SIGNAL(autotypeFinished()), SLOT(resetAutoTypeState()));
|
||||
@@ -637,16 +635,10 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin
|
||||
// Platform-specific field clearing
|
||||
actions << QSharedPointer<AutoTypeClearField>::create();
|
||||
} else if (placeholder == "totp") {
|
||||
if (entry->hasValidTotp()) {
|
||||
// Entry totp (requires special handling)
|
||||
QString totp = entry->totp();
|
||||
for (const auto& ch : totp) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
} else if (entry->hasTotp()) {
|
||||
// Entry has TOTP configured but invalid settings
|
||||
error = tr("Entry has invalid TOTP settings");
|
||||
return {};
|
||||
// Entry totp (requires special handling)
|
||||
QString totp = entry->totp();
|
||||
for (const auto& ch : totp) {
|
||||
actions << QSharedPointer<AutoTypeKey>::create(ch);
|
||||
}
|
||||
} else if (placeholder.startsWith("pickchars")) {
|
||||
// Reset to the original capture to preserve case
|
||||
|
||||
@@ -37,7 +37,6 @@ enum MENU_FIELD
|
||||
USERNAME = 1,
|
||||
PASSWORD,
|
||||
TOTP,
|
||||
URL,
|
||||
};
|
||||
|
||||
AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
|
||||
@@ -265,8 +264,7 @@ void AutoTypeSelectDialog::updateActionMenu(const AutoTypeMatch& match)
|
||||
|
||||
bool hasUsername = !match.first->username().isEmpty();
|
||||
bool hasPassword = !match.first->password().isEmpty();
|
||||
bool hasTotp = match.first->hasValidTotp();
|
||||
bool hasUrl = !match.first->url().isEmpty();
|
||||
bool hasTotp = match.first->hasTotp();
|
||||
|
||||
for (auto action : m_actionMenu->actions()) {
|
||||
auto prop = action->property(MENU_FIELD_PROP_NAME);
|
||||
@@ -281,9 +279,6 @@ void AutoTypeSelectDialog::updateActionMenu(const AutoTypeMatch& match)
|
||||
case MENU_FIELD::TOTP:
|
||||
action->setEnabled(hasTotp);
|
||||
break;
|
||||
case MENU_FIELD::URL:
|
||||
action->setEnabled(hasUrl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,19 +290,15 @@ void AutoTypeSelectDialog::buildActionMenu()
|
||||
auto typeUsernameAction = new QAction(icons()->icon("auto-type"), tr("Type {USERNAME}"), this);
|
||||
auto typePasswordAction = new QAction(icons()->icon("auto-type"), tr("Type {PASSWORD}"), this);
|
||||
auto typeTotpAction = new QAction(icons()->icon("auto-type"), tr("Type {TOTP}"), this);
|
||||
auto typeUrlAction = new QAction(icons()->icon("auto-type"), tr("Type {URL}"), this);
|
||||
auto copyUsernameAction = new QAction(icons()->icon("username-copy"), tr("Copy Username"), this);
|
||||
auto copyPasswordAction = new QAction(icons()->icon("password-copy"), tr("Copy Password"), this);
|
||||
auto copyTotpAction = new QAction(icons()->icon("totp"), tr("Copy TOTP"), this);
|
||||
auto copyUrlAction = new QAction(icons()->icon("url-copy"), tr("Copy URL"), this);
|
||||
m_actionMenu->addAction(typeUsernameAction);
|
||||
m_actionMenu->addAction(typePasswordAction);
|
||||
m_actionMenu->addAction(typeTotpAction);
|
||||
m_actionMenu->addAction(typeUrlAction);
|
||||
m_actionMenu->addAction(copyUsernameAction);
|
||||
m_actionMenu->addAction(copyPasswordAction);
|
||||
m_actionMenu->addAction(copyTotpAction);
|
||||
m_actionMenu->addAction(copyUrlAction);
|
||||
|
||||
typeUsernameAction->setShortcut(Qt::CTRL + Qt::Key_1);
|
||||
typeUsernameAction->setProperty(MENU_FIELD_PROP_NAME, MENU_FIELD::USERNAME);
|
||||
@@ -333,18 +324,10 @@ void AutoTypeSelectDialog::buildActionMenu()
|
||||
submitAutoTypeMatch(match);
|
||||
});
|
||||
|
||||
typeUrlAction->setShortcut(Qt::CTRL + Qt::Key_4);
|
||||
typeUrlAction->setProperty(MENU_FIELD_PROP_NAME, MENU_FIELD::URL);
|
||||
connect(typeUrlAction, &QAction::triggered, this, [&] {
|
||||
auto match = m_ui->view->currentMatch();
|
||||
match.second = "{URL}";
|
||||
submitAutoTypeMatch(match);
|
||||
});
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
||||
auto typeVirtualAction = new QAction(icons()->icon("auto-type"), tr("Use Virtual Keyboard"), nullptr);
|
||||
m_actionMenu->insertAction(copyUsernameAction, typeVirtualAction);
|
||||
typeVirtualAction->setShortcut(Qt::CTRL + Qt::Key_5);
|
||||
typeVirtualAction->setShortcut(Qt::CTRL + Qt::Key_4);
|
||||
connect(typeVirtualAction, &QAction::triggered, this, [&] {
|
||||
m_virtualMode = true;
|
||||
activateCurrentMatch();
|
||||
@@ -381,29 +364,17 @@ void AutoTypeSelectDialog::buildActionMenu()
|
||||
}
|
||||
});
|
||||
|
||||
copyUrlAction->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_4);
|
||||
copyUrlAction->setProperty(MENU_FIELD_PROP_NAME, MENU_FIELD::URL);
|
||||
connect(copyUrlAction, &QAction::triggered, this, [&] {
|
||||
auto entry = m_ui->view->currentMatch().first;
|
||||
if (entry) {
|
||||
clipboard()->setText(entry->resolvePlaceholder(entry->url()));
|
||||
reject();
|
||||
}
|
||||
});
|
||||
|
||||
// Qt 5.10 introduced a new "feature" to hide shortcuts in context menus
|
||||
// Unfortunately, Qt::AA_DontShowShortcutsInContextMenus is broken, have to manually enable them
|
||||
typeUsernameAction->setShortcutVisibleInContextMenu(true);
|
||||
typePasswordAction->setShortcutVisibleInContextMenu(true);
|
||||
typeTotpAction->setShortcutVisibleInContextMenu(true);
|
||||
typeUrlAction->setShortcutVisibleInContextMenu(true);
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
||||
typeVirtualAction->setShortcutVisibleInContextMenu(true);
|
||||
#endif
|
||||
copyUsernameAction->setShortcutVisibleInContextMenu(true);
|
||||
copyPasswordAction->setShortcutVisibleInContextMenu(true);
|
||||
copyTotpAction->setShortcutVisibleInContextMenu(true);
|
||||
copyUrlAction->setShortcutVisibleInContextMenu(true);
|
||||
}
|
||||
|
||||
void AutoTypeSelectDialog::showEvent(QShowEvent* event)
|
||||
|
||||
@@ -51,18 +51,12 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><p>The following shortcuts are available:<br/>
|
||||
Ctrl+F - Focus search<br/>
|
||||
<string><p>You can use advanced search queries to find any entry in your open databases. The following shortcuts are useful:<br/>
|
||||
Ctrl+F - Toggle database search<br/>
|
||||
Ctrl+1 - Type username<br/>
|
||||
Ctrl+2 - Type password<br/>
|
||||
Ctrl+3 - Type TOTP<br/>
|
||||
Ctrl+4 - Type URL<br/>
|
||||
Ctrl+5 - Use Virtual Keyboard (Windows Only)<br/>
|
||||
Ctrl+Shift+1 - Copy username<br/>
|
||||
Ctrl+Shift+2 - Copy password<br/>
|
||||
Ctrl+Shift+3 - Copy TOTP<br/>
|
||||
Ctrl+Shift+4 - Copy URL<br/>
|
||||
</p></string>
|
||||
Ctrl+4 - Use Virtual Keyboard (Windows Only)</p></string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QToolButton {
|
||||
@@ -178,9 +172,6 @@ Ctrl+Shift+4 - Copy URL<br/>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>You can use advanced search queries to find any entry in your open databases.</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Search…</string>
|
||||
</property>
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
if(WITH_XC_AUTOTYPE)
|
||||
if(UNIX AND NOT APPLE AND NOT HAIKU)
|
||||
# Main auto-type static library
|
||||
set(autotype_SOURCES
|
||||
AutoType.cpp
|
||||
AutoTypeAction.cpp
|
||||
AutoTypeMatchModel.cpp
|
||||
AutoTypeMatchView.cpp
|
||||
AutoTypeSelectDialog.cpp
|
||||
PickcharsDialog.cpp
|
||||
WindowSelectComboBox.cpp)
|
||||
|
||||
add_library(autotype STATIC ${autotype_SOURCES})
|
||||
target_link_libraries(autotype Qt5::Core Qt5::Widgets)
|
||||
|
||||
# Platform specific auto-type implementations
|
||||
if(UNIX AND NOT APPLE AND NOT HAIKU)
|
||||
if(WITH_X11)
|
||||
find_package(X11 REQUIRED COMPONENTS Xi XTest)
|
||||
find_package(Qt5X11Extras 5.2 REQUIRED)
|
||||
find_package(Qt5X11Extras REQUIRED)
|
||||
if(PRINT_SUMMARY)
|
||||
add_feature_info(libXi X11_Xi_FOUND "The X11 Xi Protocol library is required for auto-type")
|
||||
add_feature_info(libXtst X11_XTest_FOUND "The X11 XTEST Protocol library is required for auto-type")
|
||||
@@ -9,13 +23,14 @@ if(WITH_XC_AUTOTYPE)
|
||||
endif()
|
||||
|
||||
add_subdirectory(xcb)
|
||||
elseif(APPLE)
|
||||
add_subdirectory(mac)
|
||||
elseif(WIN32)
|
||||
add_subdirectory(windows)
|
||||
endif()
|
||||
|
||||
if(WITH_TESTS)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
elseif(APPLE)
|
||||
add_subdirectory(mac)
|
||||
elseif(WIN32)
|
||||
add_subdirectory(windows)
|
||||
endif()
|
||||
|
||||
# Auto-type tests
|
||||
if(WITH_TESTS)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
@@ -150,13 +150,14 @@ void BrowserAccessControlDialog::selectionChanged()
|
||||
|
||||
bool BrowserAccessControlDialog::areAllDisabled() const
|
||||
{
|
||||
auto areAllDisabled = true;
|
||||
for (const auto& item : getAllItems()) {
|
||||
if (item->flags() != Qt::NoItemFlags) {
|
||||
return false;
|
||||
areAllDisabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return areAllDisabled;
|
||||
}
|
||||
|
||||
QList<QTableWidgetItem*> BrowserAccessControlDialog::getAllItems() const
|
||||
|
||||
@@ -17,11 +17,9 @@
|
||||
|
||||
#include "BrowserAction.h"
|
||||
#include "BrowserMessageBuilder.h"
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
#include "BrowserPasskeys.h"
|
||||
#include "PasskeyUtils.h"
|
||||
#endif
|
||||
#include "BrowserSettings.h"
|
||||
#include "PasskeyUtils.h"
|
||||
#include "core/Global.h"
|
||||
#include "core/Tools.h"
|
||||
|
||||
@@ -110,12 +108,10 @@ QJsonObject BrowserAction::handleAction(QLocalSocket* socket, const QJsonObject&
|
||||
return handleGlobalAutoType(json, action);
|
||||
} else if (action.compare("get-database-entries", Qt::CaseSensitive) == 0) {
|
||||
return handleGetDatabaseEntries(json, action);
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
} else if (action.compare(BROWSER_REQUEST_PASSKEYS_GET) == 0) {
|
||||
return handlePasskeysGet(json, action);
|
||||
} else if (action.compare(BROWSER_REQUEST_PASSKEYS_REGISTER) == 0) {
|
||||
return handlePasskeysRegister(json, action);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Action was not recognized
|
||||
@@ -519,7 +515,6 @@ QJsonObject BrowserAction::handleGlobalAutoType(const QJsonObject& json, const Q
|
||||
return buildResponse(action, browserRequest.incrementedNonce);
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
QJsonObject BrowserAction::handlePasskeysGet(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
if (!m_associated) {
|
||||
@@ -586,7 +581,6 @@ QJsonObject BrowserAction::handlePasskeysRegister(const QJsonObject& json, const
|
||||
const Parameters params{{"response", response}};
|
||||
return buildResponse(action, browserRequest.incrementedNonce, params);
|
||||
}
|
||||
#endif
|
||||
|
||||
QJsonObject BrowserAction::decryptMessage(const QString& message, const QString& nonce)
|
||||
{
|
||||
|
||||
@@ -84,19 +84,15 @@ private:
|
||||
QJsonObject handleGetTotp(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleDeleteEntry(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleGlobalAutoType(const QJsonObject& json, const QString& action);
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
QJsonObject handlePasskeysGet(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handlePasskeysRegister(const QJsonObject& json, const QString& action);
|
||||
#endif
|
||||
|
||||
private:
|
||||
QJsonObject buildResponse(const QString& action, const QString& nonce, const Parameters& params = {});
|
||||
QJsonObject getErrorReply(const QString& action, const int errorCode) const;
|
||||
QJsonObject decryptMessage(const QString& message, const QString& nonce);
|
||||
BrowserRequest decodeRequest(const QJsonObject& json);
|
||||
StringPairList getConnectionKeys(const BrowserRequest& browserRequest);
|
||||
|
||||
private:
|
||||
static const int MaxUrlLength;
|
||||
|
||||
QString m_clientPublicKey;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2024 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
|
||||
@@ -71,7 +71,7 @@ PublicKeyCredential BrowserPasskeys::buildRegisterPublicKeyCredential(const QJso
|
||||
}
|
||||
|
||||
const auto authenticatorAttachment = credentialCreationOptions["authenticatorAttachment"];
|
||||
const auto clientDataJson = credentialCreationOptions["clientDataJSON"].toString();
|
||||
const auto clientDataJson = credentialCreationOptions["clientDataJSON"].toObject();
|
||||
const auto extensions = credentialCreationOptions["extensions"].toString();
|
||||
const auto credentialId = testingVariables.credentialId.isEmpty()
|
||||
? browserMessageBuilder()->getRandomBytesAsBase64(ID_BYTES)
|
||||
@@ -98,7 +98,7 @@ PublicKeyCredential BrowserPasskeys::buildRegisterPublicKeyCredential(const QJso
|
||||
// Response
|
||||
QJsonObject responseObject;
|
||||
responseObject["attestationObject"] = browserMessageBuilder()->getBase64FromArray(attestationObject);
|
||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromArray(clientDataJson.toUtf8());
|
||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromJson(clientDataJson);
|
||||
responseObject["clientExtensionResults"] = credentialCreationOptions["clientExtensionResults"];
|
||||
|
||||
// Additions for extension side functions
|
||||
@@ -130,8 +130,8 @@ QJsonObject BrowserPasskeys::buildGetPublicKeyCredential(const QJsonObject& asse
|
||||
|
||||
const auto authenticatorData =
|
||||
buildAuthenticatorData(assertionOptions["rpId"].toString(), assertionOptions["extensions"].toString());
|
||||
const auto clientDataJson = assertionOptions["clientDataJson"].toString();
|
||||
const auto clientDataArray = clientDataJson.toUtf8();
|
||||
const auto clientDataJson = assertionOptions["clientDataJson"].toObject();
|
||||
const auto clientDataArray = QJsonDocument(clientDataJson).toJson(QJsonDocument::Compact);
|
||||
|
||||
const auto signature = buildSignature(authenticatorData, clientDataArray, privateKeyPem);
|
||||
if (signature.isEmpty()) {
|
||||
@@ -140,7 +140,7 @@ QJsonObject BrowserPasskeys::buildGetPublicKeyCredential(const QJsonObject& asse
|
||||
|
||||
QJsonObject responseObject;
|
||||
responseObject["authenticatorData"] = browserMessageBuilder()->getBase64FromArray(authenticatorData);
|
||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromArray(clientDataArray);
|
||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromJson(clientDataJson);
|
||||
responseObject["clientExtensionResults"] = assertionOptions["clientExtensionResults"];
|
||||
responseObject["signature"] = browserMessageBuilder()->getBase64FromArray(signature);
|
||||
responseObject["userHandle"] = userHandle;
|
||||
@@ -273,7 +273,7 @@ BrowserPasskeys::buildCredentialPrivateKey(int alg, const QString& predefinedFir
|
||||
try {
|
||||
Botan::Ed25519_PrivateKey key(*randomGen()->getRng());
|
||||
auto publicKey = key.get_public_key();
|
||||
#ifdef WITH_XC_BOTAN3
|
||||
#ifdef WITH_BOTAN3
|
||||
auto privateKey = key.raw_private_key_bits();
|
||||
#else
|
||||
auto privateKey = key.get_private_key();
|
||||
@@ -322,7 +322,7 @@ QByteArray BrowserPasskeys::buildSignature(const QByteArray& authenticatorData,
|
||||
std::vector<uint8_t> rawSignature;
|
||||
if (algName == "ECDSA") {
|
||||
Botan::ECDSA_PrivateKey privateKey(algId, privateKeyBytes);
|
||||
#ifdef WITH_XC_BOTAN3
|
||||
#ifdef WITH_BOTAN3
|
||||
Botan::PK_Signer signer(
|
||||
privateKey, *randomGen()->getRng(), "EMSA1(SHA-256)", Botan::Signature_Format::DerSequence);
|
||||
#else
|
||||
|
||||
@@ -155,4 +155,4 @@ void BrowserPasskeysConfirmationDialog::updateEntriesToTable(const QList<Entry*>
|
||||
|
||||
m_ui->credentialsTable->resizeColumnsToContents();
|
||||
m_ui->credentialsTable->horizontalHeader()->setStretchLastSection(true);
|
||||
}
|
||||
}
|
||||
@@ -23,20 +23,18 @@
|
||||
#include "BrowserEntrySaveDialog.h"
|
||||
#include "BrowserHost.h"
|
||||
#include "BrowserMessageBuilder.h"
|
||||
#include "BrowserPasskeys.h"
|
||||
#include "BrowserPasskeysClient.h"
|
||||
#include "BrowserPasskeysConfirmationDialog.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "PasskeyUtils.h"
|
||||
#include "core/EntryAttributes.h"
|
||||
#include "core/Tools.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/MessageBox.h"
|
||||
#include "gui/UrlTools.h"
|
||||
#include "gui/osutils/OSUtils.h"
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
#include "BrowserPasskeys.h"
|
||||
#include "BrowserPasskeysClient.h"
|
||||
#include "BrowserPasskeysConfirmationDialog.h"
|
||||
#include "PasskeyUtils.h"
|
||||
#include "gui/passkeys/PasskeyImporter.h"
|
||||
#endif
|
||||
#ifdef Q_OS_MACOS
|
||||
#include "gui/osutils/macutils/MacUtils.h"
|
||||
#endif
|
||||
@@ -57,11 +55,9 @@
|
||||
const QString BrowserService::KEEPASSXCBROWSER_NAME = QStringLiteral("KeePassXC-Browser Settings");
|
||||
const QString BrowserService::KEEPASSXCBROWSER_OLD_NAME = QStringLiteral("keepassxc-browser Settings");
|
||||
static const QString KEEPASSXCBROWSER_GROUP_NAME = QStringLiteral("KeePassXC-Browser Passwords");
|
||||
static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
static int KEEPASSXCBROWSER_PASSKEY_ICON = 13;
|
||||
static const QString PASSKEYS_DEFAULT_GROUP_NAME = QStringLiteral("KeePassXC-Browser Passkeys");
|
||||
#endif
|
||||
static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
|
||||
static int KEEPASSXCBROWSER_PASSKEY_ICON = 13;
|
||||
// These are for the settings and password conversion
|
||||
static const QString KEEPASSHTTP_NAME = QStringLiteral("KeePassHttp Settings");
|
||||
static const QString KEEPASSHTTP_GROUP_NAME = QStringLiteral("KeePassHttp Passwords");
|
||||
@@ -330,7 +326,6 @@ QJsonObject BrowserService::createNewGroup(const QString& groupName, bool isPass
|
||||
}
|
||||
#endif
|
||||
name = newGroup->name();
|
||||
newGroup->setCustomDataTriState(BrowserService::OPTION_HIDE_ENTRY, Group::Disable);
|
||||
uuid = Tools::uuidToHex(newGroup->uuid());
|
||||
previousGroup = newGroup;
|
||||
continue;
|
||||
@@ -413,7 +408,7 @@ BrowserService::findEntries(const EntryParameters& entryParameters, const String
|
||||
continue;
|
||||
|
||||
case Unknown:
|
||||
if (alwaysAllowAccess || (entryParameters.httpAuth && ignoreHttpAuth)) {
|
||||
if (alwaysAllowAccess) {
|
||||
allowedEntries.append(entry);
|
||||
} else {
|
||||
entriesToConfirm.append(entry);
|
||||
@@ -634,7 +629,6 @@ QString BrowserService::getKey(const QString& id)
|
||||
return db->metadata()->customData()->value(CustomData::getKeyWithPrefix(CustomData::BrowserKeyPrefix, id));
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
// Passkey registration
|
||||
QJsonObject BrowserService::showPasskeysRegisterPrompt(const QJsonObject& publicKeyOptions,
|
||||
const QString& origin,
|
||||
@@ -860,7 +854,6 @@ void BrowserService::addPasskeyToEntry(Entry* entry,
|
||||
|
||||
entry->endUpdate();
|
||||
}
|
||||
#endif
|
||||
|
||||
void BrowserService::addEntry(const EntryParameters& entryParameters,
|
||||
const QString& group,
|
||||
@@ -898,6 +891,16 @@ void BrowserService::addEntry(const EntryParameters& entryParameters,
|
||||
|
||||
const QString host = QUrl(entryParameters.siteUrl).host();
|
||||
const QString submitHost = QUrl(entryParameters.formUrl).host();
|
||||
BrowserEntryConfig config;
|
||||
config.allow(host);
|
||||
|
||||
if (!submitHost.isEmpty()) {
|
||||
config.allow(submitHost);
|
||||
}
|
||||
if (!entryParameters.realm.isEmpty()) {
|
||||
config.setRealm(entryParameters.realm);
|
||||
}
|
||||
config.save(entry);
|
||||
|
||||
if (downloadFavicon && m_currentDatabaseWidget) {
|
||||
m_currentDatabaseWidget->downloadFaviconInBackground(entry);
|
||||
@@ -1038,12 +1041,10 @@ QList<Entry*> BrowserService::searchEntries(const QSharedPointer<Database>& db,
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
// With Passkeys, check for the Relying Party instead of URL
|
||||
if (passkey && entry->attributes()->value(EntryAttributes::KPEX_PASSKEY_RELYING_PARTY) != siteUrl) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Additional URL check may have already inserted the entry to the list
|
||||
if (!entries.contains(entry)) {
|
||||
@@ -1182,7 +1183,7 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
||||
res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuidToHex());
|
||||
res["group"] = entry->resolveMultiplePlaceholders(entry->group()->name());
|
||||
|
||||
if (entry->hasValidTotp()) {
|
||||
if (entry->hasTotp()) {
|
||||
res["totp"] = entry->totp();
|
||||
}
|
||||
|
||||
@@ -1383,7 +1384,6 @@ bool BrowserService::shouldIncludeEntry(Entry* entry,
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
// Returns all Passkey entries for the current Relying Party
|
||||
QList<Entry*> BrowserService::getPasskeyEntries(const QString& rpId, const StringPairList& keyList)
|
||||
{
|
||||
@@ -1458,7 +1458,6 @@ QJsonObject BrowserService::getPasskeyError(int errorCode) const
|
||||
{
|
||||
return QJsonObject({{"errorCode", errorCode}});
|
||||
}
|
||||
#endif
|
||||
|
||||
bool BrowserService::handleURL(const QString& entryUrl,
|
||||
const QString& siteUrl,
|
||||
@@ -1562,11 +1561,11 @@ bool BrowserService::handleURLWithWildcards(const QUrl& entryQUrl, const QString
|
||||
}
|
||||
|
||||
// Escape illegal characters
|
||||
auto re = Tools::escapeRegex(firstPart);
|
||||
auto re = firstPart.replace(QRegularExpression(R"(([!\^\$\+\-\(\)@<>]))"), "\\\\1");
|
||||
|
||||
if (hostnameUsed) {
|
||||
// Replace all host parts with wildcards
|
||||
re = re.replace(QString("%1.").arg(UrlTools::URL_WILDCARD), "(.*?)\\.");
|
||||
re = re.replace(QString("%1.").arg(UrlTools::URL_WILDCARD), "(.*?)");
|
||||
}
|
||||
|
||||
// Append a + to the end of regex to match all paths after the last asterisk
|
||||
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
QSharedPointer<Database> getDatabase(const QUuid& rootGroupUuid = {});
|
||||
QSharedPointer<Database> selectedDatabase();
|
||||
QList<QSharedPointer<Database>> getOpenDatabases();
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
|
||||
QJsonObject showPasskeysRegisterPrompt(const QJsonObject& publicKeyOptions,
|
||||
const QString& origin,
|
||||
const QString& groupName,
|
||||
@@ -111,7 +111,7 @@ public:
|
||||
const QString& credentialId,
|
||||
const QString& userHandle,
|
||||
const QString& privateKey);
|
||||
#endif
|
||||
|
||||
void addEntry(const EntryParameters& entryParameters,
|
||||
const QString& group,
|
||||
const QString& groupUuid,
|
||||
@@ -186,7 +186,7 @@ private:
|
||||
bool removeFirstDomain(QString& hostname);
|
||||
bool
|
||||
shouldIncludeEntry(Entry* entry, const QString& url, const QString& submitUrl, const bool omitWwwSubdomain = false);
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
|
||||
QList<Entry*> getPasskeyEntries(const QString& rpId, const StringPairList& keyList);
|
||||
QList<Entry*>
|
||||
getPasskeyEntriesWithUserHandle(const QString& rpId, const QString& userId, const StringPairList& keyList);
|
||||
@@ -196,7 +196,7 @@ private:
|
||||
const QString& rpId,
|
||||
const StringPairList& keyList);
|
||||
QJsonObject getPasskeyError(int errorCode) const;
|
||||
#endif
|
||||
|
||||
bool handleURL(const QString& entryUrl,
|
||||
const QString& siteUrl,
|
||||
const QString& formUrl,
|
||||
@@ -223,9 +223,7 @@ private:
|
||||
Q_DISABLE_COPY(BrowserService);
|
||||
|
||||
friend class TestBrowser;
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
friend class TestPasskeys;
|
||||
#endif
|
||||
};
|
||||
|
||||
static inline BrowserService* browserService()
|
||||
|
||||
@@ -13,32 +13,28 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
if(WITH_XC_BROWSER)
|
||||
if(KPXC_FEATURE_BROWSER)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(browser_SOURCES
|
||||
BrowserAccessControlDialog.cpp
|
||||
BrowserAction.cpp
|
||||
BrowserCbor.cpp
|
||||
BrowserEntryConfig.cpp
|
||||
BrowserEntrySaveDialog.cpp
|
||||
BrowserHost.cpp
|
||||
BrowserMessageBuilder.cpp
|
||||
BrowserPasskeys.cpp
|
||||
BrowserPasskeysClient.cpp
|
||||
BrowserPasskeysConfirmationDialog.cpp
|
||||
BrowserSettingsPage.cpp
|
||||
BrowserSettingsWidget.cpp
|
||||
BrowserService.cpp
|
||||
BrowserSettings.cpp
|
||||
BrowserShared.cpp
|
||||
CustomTableWidget.cpp
|
||||
NativeMessageInstaller.cpp)
|
||||
|
||||
if(WITH_XC_BROWSER_PASSKEYS)
|
||||
list(APPEND browser_SOURCES
|
||||
BrowserCbor.cpp
|
||||
BrowserPasskeys.cpp
|
||||
BrowserPasskeysClient.cpp
|
||||
BrowserPasskeysConfirmationDialog.cpp
|
||||
PasskeyUtils.cpp)
|
||||
endif()
|
||||
NativeMessageInstaller.cpp
|
||||
PasskeyUtils.cpp)
|
||||
|
||||
add_library(browser STATIC ${browser_SOURCES})
|
||||
target_link_libraries(browser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${BOTAN_LIBRARIES})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2024 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
|
||||
@@ -53,8 +53,8 @@ bool PasskeyUtils::checkCredentialCreationOptions(const QJsonObject& credentialC
|
||||
{
|
||||
if (!credentialCreationOptions["attestation"].isString()
|
||||
|| credentialCreationOptions["attestation"].toString().isEmpty()
|
||||
|| !credentialCreationOptions["clientDataJSON"].isString()
|
||||
|| credentialCreationOptions["clientDataJSON"].toString().isEmpty()
|
||||
|| !credentialCreationOptions["clientDataJSON"].isObject()
|
||||
|| credentialCreationOptions["clientDataJSON"].toObject().isEmpty()
|
||||
|| !credentialCreationOptions["rp"].isObject() || credentialCreationOptions["rp"].toObject().isEmpty()
|
||||
|| !credentialCreationOptions["user"].isObject() || credentialCreationOptions["user"].toObject().isEmpty()
|
||||
|| !credentialCreationOptions["residentKey"].isBool() || credentialCreationOptions["residentKey"].isUndefined()
|
||||
@@ -75,7 +75,7 @@ bool PasskeyUtils::checkCredentialCreationOptions(const QJsonObject& credentialC
|
||||
// Basic check for the object that it contains necessary variables in a correct form
|
||||
bool PasskeyUtils::checkCredentialAssertionOptions(const QJsonObject& assertionOptions) const
|
||||
{
|
||||
if (!assertionOptions["clientDataJson"].isString() || assertionOptions["clientDataJson"].toString().isEmpty()
|
||||
if (!assertionOptions["clientDataJson"].isObject() || assertionOptions["clientDataJson"].toObject().isEmpty()
|
||||
|| !assertionOptions["rpId"].isString() || assertionOptions["rpId"].toString().isEmpty()
|
||||
|| !assertionOptions["userPresence"].isBool() || assertionOptions["userPresence"].isUndefined()
|
||||
|| !assertionOptions["userVerification"].isBool() || assertionOptions["userVerification"].isUndefined()) {
|
||||
@@ -352,11 +352,15 @@ ExtensionResult PasskeyUtils::buildExtensionData(QJsonObject& extensionObject) c
|
||||
return {};
|
||||
}
|
||||
|
||||
// Serialization order: https://w3c.github.io/webauthn/#clientdatajson-serialization
|
||||
QString PasskeyUtils::buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const
|
||||
QJsonObject PasskeyUtils::buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const
|
||||
{
|
||||
return QString("{\"type\":\"%1\",\"challenge\":\"%2\",\"origin\":\"%3\",\"crossOrigin\":false}")
|
||||
.arg((get ? QString("webauthn.get") : QString("webauthn.create")), publicKey["challenge"].toString(), origin);
|
||||
QJsonObject clientData;
|
||||
clientData["challenge"] = publicKey["challenge"];
|
||||
clientData["crossOrigin"] = false;
|
||||
clientData["origin"] = origin;
|
||||
clientData["type"] = get ? QString("webauthn.get") : QString("webauthn.create");
|
||||
|
||||
return clientData;
|
||||
}
|
||||
|
||||
QStringList PasskeyUtils::getAllowedCredentialsFromAssertionOptions(const QJsonObject& assertionOptions) const
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2024 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
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
bool isUserVerificationRequired(const QJsonObject& authenticatorSelection) const;
|
||||
bool isOriginAllowedWithLocalhost(bool allowLocalhostWithPasskeys, const QString& origin) const;
|
||||
ExtensionResult buildExtensionData(QJsonObject& extensionObject) const;
|
||||
QString buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const;
|
||||
QJsonObject buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const;
|
||||
QStringList getAllowedCredentialsFromAssertionOptions(const QJsonObject& assertionOptions) const;
|
||||
QString getCredentialIdFromEntry(const Entry* entry) const;
|
||||
QString getUsernameFromEntry(const Entry* entry) const;
|
||||
|
||||
@@ -27,9 +27,7 @@ DatabaseCommand::DatabaseCommand()
|
||||
positionalArguments.append({QString("database"), QObject::tr("Path of the database."), QString("")});
|
||||
options.append(Command::KeyFileOption);
|
||||
options.append(Command::NoPasswordOption);
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
options.append(Command::YubiKeyOption);
|
||||
#endif
|
||||
}
|
||||
|
||||
int DatabaseCommand::execute(const QStringList& arguments)
|
||||
@@ -55,11 +53,7 @@ int DatabaseCommand::execute(const QStringList& arguments)
|
||||
db = Utils::unlockDatabase(args.at(0),
|
||||
!parser->isSet(Command::NoPasswordOption),
|
||||
parser->value(Command::KeyFileOption),
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
parser->value(Command::YubiKeyOption),
|
||||
#else
|
||||
"",
|
||||
#endif
|
||||
parser->isSet(Command::QuietOption));
|
||||
if (!db) {
|
||||
return EXIT_FAILURE;
|
||||
|
||||
@@ -67,8 +67,8 @@ int DatabaseInfo::executeWithDatabase(QSharedPointer<Database> database, QShared
|
||||
out << QObject::tr("Number of short passwords") << ": " << QString::number(stats.shortPasswords) << Qt::endl;
|
||||
out << QObject::tr("Number of weak passwords") << ": " << QString::number(stats.weakPasswords) << Qt::endl;
|
||||
out << QObject::tr("Entries excluded from reports") << ": " << QString::number(stats.excludedEntries) << Qt::endl;
|
||||
out << QObject::tr("Average password length") << ": "
|
||||
<< QObject::tr("%1 character(s)", "", stats.averagePwdLength()).arg(stats.averagePwdLength()) << Qt::endl;
|
||||
out << QObject::tr("Average password length") << ": " << QObject::tr("%1 characters").arg(stats.averagePwdLength())
|
||||
<< Qt::endl;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -68,9 +68,11 @@ int Diceware::execute(const QStringList& arguments)
|
||||
dicewareGenerator.setWordList(wordListFile);
|
||||
}
|
||||
|
||||
// Show a warning if the wordlist is smaller than the recommended size
|
||||
if (!dicewareGenerator.isWordListValid()) {
|
||||
err << QObject::tr("Warning: the chosen wordlist is smaller than the minimum recommended size!") << Qt::endl;
|
||||
if (!dicewareGenerator.isValid()) {
|
||||
// We already validated the word count input so if the generator is invalid, it
|
||||
// must be because the word list is too small.
|
||||
err << QObject::tr("Cannot generate valid passphrases because the wordlist is too short") << Qt::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
QString password = dicewareGenerator.generatePassphrase();
|
||||
|
||||
@@ -51,9 +51,8 @@ Merge::Merge()
|
||||
options.append(Merge::KeyFileFromOption);
|
||||
options.append(Merge::NoPasswordFromOption);
|
||||
options.append(Merge::DryRunOption);
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
options.append(Merge::YubiKeyFromOption);
|
||||
#endif
|
||||
|
||||
positionalArguments.append({QString("database2"), QObject::tr("Path of the database to merge from."), QString("")});
|
||||
}
|
||||
|
||||
@@ -87,10 +86,10 @@ int Merge::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer
|
||||
}
|
||||
|
||||
Merger merger(db2.data(), database.data());
|
||||
auto changeList = merger.merge();
|
||||
QStringList changeList = merger.merge();
|
||||
|
||||
for (const auto& mergeChange : changeList) {
|
||||
out << "\t" << mergeChange.toString() << Qt::endl;
|
||||
for (auto& mergeChange : changeList) {
|
||||
out << "\t" << mergeChange << Qt::endl;
|
||||
}
|
||||
|
||||
if (!changeList.isEmpty() && !parser->isSet(Merge::DryRunOption)) {
|
||||
|
||||
@@ -21,10 +21,8 @@
|
||||
#include "core/Entry.h"
|
||||
#include "core/EntryAttributes.h"
|
||||
#include "core/Global.h"
|
||||
#include "keys/FileKey.h"
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
#include "keys/ChallengeResponseKey.h"
|
||||
#endif
|
||||
#include "keys/FileKey.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
@@ -170,7 +168,6 @@ namespace Utils
|
||||
compositeKey->addKey(fileKey);
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
if (!yubiKeySlot.isEmpty()) {
|
||||
unsigned int serial = 0;
|
||||
int slot;
|
||||
@@ -201,18 +198,14 @@ namespace Utils
|
||||
|
||||
YubiKey::instance()->findValidKeys();
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(yubiKeySlot);
|
||||
#endif // WITH_XC_YUBIKEY
|
||||
|
||||
auto db = QSharedPointer<Database>::create();
|
||||
QString error;
|
||||
if (db->open(databaseFilename, compositeKey, &error)) {
|
||||
return db;
|
||||
} else {
|
||||
if (!db->open(databaseFilename, compositeKey, &error)) {
|
||||
err << error << Qt::endl;
|
||||
return {};
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,34 +12,36 @@
|
||||
#define KEEPASSX_PLUGIN_DIR "@PLUGIN_INSTALL_DIR@"
|
||||
#define KEEPASSX_DATA_DIR "@DATA_INSTALL_DIR@"
|
||||
|
||||
#cmakedefine WITH_XC_AUTOTYPE
|
||||
#cmakedefine WITH_XC_NETWORKING
|
||||
#cmakedefine WITH_XC_BROWSER
|
||||
#cmakedefine WITH_XC_BROWSER_PASSKEYS
|
||||
#cmakedefine WITH_XC_YUBIKEY
|
||||
#cmakedefine WITH_XC_SSHAGENT
|
||||
#cmakedefine WITH_XC_KEESHARE
|
||||
#cmakedefine WITH_XC_UPDATECHECK
|
||||
#cmakedefine WITH_XC_FDOSECRETS
|
||||
#cmakedefine WITH_XC_DOCS
|
||||
#cmakedefine WITH_XC_X11
|
||||
#cmakedefine WITH_XC_BOTAN3
|
||||
/* Build Scope */
|
||||
#cmakedefine WITH_X11
|
||||
#cmakedefine WITH_BOTAN3
|
||||
|
||||
/* Advanced Features */
|
||||
#cmakedefine KPXC_FEATURE_BROWSER
|
||||
#cmakedefine KPXC_FEATURE_SSHAGENT
|
||||
#cmakedefine KPXC_FEATURE_FDOSECRETS
|
||||
|
||||
/* Minor Features */
|
||||
#cmakedefine KPXC_FEATURE_NETWORK
|
||||
#cmakedefine KPXC_FEATURE_UPDATES
|
||||
#cmakedefine KPXC_FEATURE_DOCS
|
||||
|
||||
/* Distribution */
|
||||
#cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@"
|
||||
#cmakedefine KEEPASSXC_BUILD_TYPE_RELEASE
|
||||
#cmakedefine KEEPASSXC_BUILD_TYPE_PRE_RELEASE
|
||||
#cmakedefine KEEPASSXC_BUILD_TYPE_SNAPSHOT
|
||||
|
||||
#cmakedefine KEEPASSXC_DIST
|
||||
#cmakedefine KEEPASSXC_DIST_TYPE "@KEEPASSXC_DIST_TYPE@"
|
||||
#cmakedefine KEEPASSXC_DIST_SNAP
|
||||
#cmakedefine KEEPASSXC_DIST_APPIMAGE
|
||||
#cmakedefine KEEPASSXC_DIST_FLATPAK
|
||||
|
||||
/* Security Test Results */
|
||||
#cmakedefine HAVE_PR_SET_DUMPABLE 1
|
||||
#cmakedefine HAVE_RLIMIT_CORE 1
|
||||
#cmakedefine HAVE_PT_DENY_ATTACH 1
|
||||
|
||||
/* macOS Feature Support */
|
||||
#cmakedefine01 XC_APPLE_COMPILER_SUPPORT_BIOMETRY()
|
||||
#cmakedefine01 XC_APPLE_COMPILER_SUPPORT_TOUCH_ID()
|
||||
#cmakedefine01 XC_APPLE_COMPILER_SUPPORT_WATCH()
|
||||
|
||||
@@ -208,7 +208,7 @@ namespace Bootstrap
|
||||
goto Cleanup;
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
#ifdef KPXC_FEATURE_SSHAGENT
|
||||
// OpenSSH for Windows ssh-agent service is running as LocalSystem
|
||||
if (!AddAccessAllowedAce(pACL,
|
||||
ACL_REVISION,
|
||||
|
||||
@@ -147,7 +147,6 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||
{Config::Security_HidePasswordPreviewPanel, {QS("Security/HidePasswordPreviewPanel"), Roaming, true}},
|
||||
{Config::Security_HideTotpPreviewPanel, {QS("Security/HideTotpPreviewPanel"), Roaming, false}},
|
||||
{Config::Security_AutoTypeAsk, {QS("Security/AutotypeAsk"), Roaming, true}},
|
||||
{Config::Security_AutoTypeSkipMainWindowConfirmation, {QS("Security/AutoTypeSkipMainWindowConfirmation"), Roaming, false}},
|
||||
{Config::Security_IconDownloadFallback, {QS("Security/IconDownloadFallback"), Roaming, false}},
|
||||
{Config::Security_NoConfirmMoveEntryToRecycleBin,{QS("Security/NoConfirmMoveEntryToRecycleBin"), Roaming, true}},
|
||||
{Config::Security_EnableCopyOnDoubleClick,{QS("Security/EnableCopyOnDoubleClick"), Roaming, false}},
|
||||
|
||||
@@ -98,7 +98,6 @@ public:
|
||||
GUI_CompactMode,
|
||||
GUI_CheckForUpdates,
|
||||
GUI_CheckForUpdatesIncludeBetas,
|
||||
SearchWaitForEnter,
|
||||
GUI_ShowExpiredEntriesOnDatabaseUnlock,
|
||||
GUI_ShowExpiredEntriesOnDatabaseUnlockOffsetDays,
|
||||
GUI_FontSizeOffset,
|
||||
@@ -129,7 +128,6 @@ public:
|
||||
Security_HidePasswordPreviewPanel,
|
||||
Security_HideTotpPreviewPanel,
|
||||
Security_AutoTypeAsk,
|
||||
Security_AutoTypeSkipMainWindowConfirmation,
|
||||
Security_IconDownloadFallback,
|
||||
Security_NoConfirmMoveEntryToRecycleBin,
|
||||
Security_EnableCopyOnDoubleClick,
|
||||
|
||||
@@ -826,12 +826,7 @@ void Database::updateTagList()
|
||||
}
|
||||
|
||||
m_tagList = tagSet.values();
|
||||
|
||||
QCollator collator;
|
||||
collator.setNumericMode(true);
|
||||
collator.setCaseSensitivity(Qt::CaseInsensitive);
|
||||
std::sort(m_tagList.begin(), m_tagList.end(), collator);
|
||||
|
||||
m_tagList.sort();
|
||||
emit tagListUpdated();
|
||||
}
|
||||
|
||||
|
||||
@@ -570,12 +570,6 @@ bool Entry::hasTotp() const
|
||||
return !m_data.totpSettings.isNull();
|
||||
}
|
||||
|
||||
bool Entry::hasValidTotp() const
|
||||
{
|
||||
auto error = Totp::checkValidSettings(m_data.totpSettings);
|
||||
return error.isEmpty();
|
||||
}
|
||||
|
||||
bool Entry::hasPasskey() const
|
||||
{
|
||||
return m_attributes->hasPasskey();
|
||||
@@ -587,13 +581,10 @@ void Entry::removePasskey()
|
||||
removeTag(tr("Passkey"));
|
||||
}
|
||||
|
||||
QString Entry::totp(bool* isValid) const
|
||||
QString Entry::totp() const
|
||||
{
|
||||
if (hasTotp()) {
|
||||
return Totp::generateTotp(m_data.totpSettings, isValid);
|
||||
}
|
||||
if (isValid) {
|
||||
*isValid = false;
|
||||
return Totp::generateTotp(m_data.totpSettings);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -959,68 +950,6 @@ bool Entry::equals(const Entry* other, CompareItemOptions options) const
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList Entry::calculateDifference(const Entry* other)
|
||||
{
|
||||
QStringList modifiedFields;
|
||||
|
||||
if (*attributes() != *other->attributes()) {
|
||||
bool foundAttribute = false;
|
||||
|
||||
if (title() != other->title()) {
|
||||
modifiedFields << tr("Title");
|
||||
foundAttribute = true;
|
||||
}
|
||||
if (username() != other->username()) {
|
||||
modifiedFields << tr("Username");
|
||||
foundAttribute = true;
|
||||
}
|
||||
if (password() != other->password()) {
|
||||
modifiedFields << tr("Password");
|
||||
foundAttribute = true;
|
||||
}
|
||||
if (url() != other->url()) {
|
||||
modifiedFields << tr("URL");
|
||||
foundAttribute = true;
|
||||
}
|
||||
if (notes() != other->notes()) {
|
||||
modifiedFields << tr("Notes");
|
||||
foundAttribute = true;
|
||||
}
|
||||
|
||||
if (!foundAttribute) {
|
||||
modifiedFields << tr("Custom Attributes");
|
||||
}
|
||||
}
|
||||
if (iconNumber() != other->iconNumber() || iconUuid() != other->iconUuid()) {
|
||||
modifiedFields << tr("Icon");
|
||||
}
|
||||
if (foregroundColor() != other->foregroundColor() || backgroundColor() != other->backgroundColor()) {
|
||||
modifiedFields << tr("Color");
|
||||
}
|
||||
if (timeInfo().expires() != other->timeInfo().expires()
|
||||
|| timeInfo().expiryTime() != other->timeInfo().expiryTime()) {
|
||||
modifiedFields << tr("Expiration");
|
||||
}
|
||||
if (totp() != other->totp()) {
|
||||
modifiedFields << tr("TOTP");
|
||||
}
|
||||
if (*customData() != *other->customData()) {
|
||||
modifiedFields << tr("Custom Data");
|
||||
}
|
||||
if (*attachments() != *other->attachments()) {
|
||||
modifiedFields << tr("Attachments");
|
||||
}
|
||||
if (*autoTypeAssociations() != *other->autoTypeAssociations() || autoTypeEnabled() != other->autoTypeEnabled()
|
||||
|| defaultAutoTypeSequence() != other->defaultAutoTypeSequence()) {
|
||||
modifiedFields << tr("Auto-Type");
|
||||
}
|
||||
if (tags() != other->tags()) {
|
||||
modifiedFields << tr("Tags");
|
||||
}
|
||||
|
||||
return modifiedFields;
|
||||
}
|
||||
|
||||
Entry* Entry::clone(CloneFlags flags) const
|
||||
{
|
||||
auto entry = new Entry();
|
||||
|
||||
@@ -109,7 +109,7 @@ public:
|
||||
QString password() const;
|
||||
QString notes() const;
|
||||
QString attribute(const QString& key) const;
|
||||
QString totp(bool* isValid = nullptr) const;
|
||||
QString totp() const;
|
||||
QString totpSettingsString() const;
|
||||
QSharedPointer<Totp::Settings> totpSettings() const;
|
||||
Group* previousParentGroup();
|
||||
@@ -126,7 +126,6 @@ public:
|
||||
void removePasskey();
|
||||
|
||||
bool hasTotp() const;
|
||||
bool hasValidTotp() const;
|
||||
bool isExpired() const;
|
||||
bool willExpireInDays(int days) const;
|
||||
void expireNow();
|
||||
@@ -179,13 +178,6 @@ public:
|
||||
|
||||
bool equals(const Entry* other, CompareItemOptions options = CompareItemDefault) const;
|
||||
|
||||
/**
|
||||
* Determine differences between attributes of this and another entry.
|
||||
*
|
||||
* @return The list of attribute names that are different between the two entries
|
||||
*/
|
||||
QStringList calculateDifference(const Entry* other);
|
||||
|
||||
enum CloneFlag
|
||||
{
|
||||
CloneNoFlags = 0,
|
||||
|
||||
@@ -221,13 +221,6 @@ bool EntrySearcher::searchEntryImpl(const Entry* entry)
|
||||
}
|
||||
found = false;
|
||||
break;
|
||||
case Field::Has:
|
||||
if (term.word.compare("totp", Qt::CaseInsensitive) == 0) {
|
||||
found = entry->hasTotp();
|
||||
break;
|
||||
}
|
||||
found = false;
|
||||
break;
|
||||
case Field::Uuid:
|
||||
found = term.regex.match(entry->uuidToHex()).hasMatch();
|
||||
break;
|
||||
@@ -267,7 +260,6 @@ void EntrySearcher::parseSearchTerms(const QString& searchString)
|
||||
{QStringLiteral("group"), Field::Group},
|
||||
{QStringLiteral("tag"), Field::Tag},
|
||||
{QStringLiteral("is"), Field::Is},
|
||||
{QStringLiteral("has"), Field::Has},
|
||||
{QStringLiteral("uuid"), Field::Uuid}};
|
||||
|
||||
// Group 1 = modifiers, Group 2 = field, Group 3 = quoted string, Group 4 = unquoted string
|
||||
|
||||
@@ -41,7 +41,6 @@ public:
|
||||
Group,
|
||||
Tag,
|
||||
Is,
|
||||
Has,
|
||||
Uuid
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <QTextStream>
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#if defined(KEEPASSX_BUILDING_CORE)
|
||||
#if defined(KPXC_BUILDING_CORE)
|
||||
#define KEEPASSXC_EXPORT Q_DECL_IMPORT
|
||||
#else
|
||||
#define KEEPASSXC_EXPORT Q_DECL_EXPORT
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2021 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
|
||||
@@ -17,14 +17,8 @@
|
||||
*/
|
||||
|
||||
#include "Group.h"
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#include "core/Config.h"
|
||||
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
#include "keeshare/KeeShare.h"
|
||||
#endif
|
||||
|
||||
#include "core/Global.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/Tools.h"
|
||||
@@ -457,7 +451,6 @@ const Group* Group::parentGroup() const
|
||||
void Group::setParent(Group* parent, int index, bool trackPrevious)
|
||||
{
|
||||
Q_ASSERT(parent);
|
||||
Q_ASSERT(this != parent);
|
||||
Q_ASSERT(index >= -1 && index <= parent->children().size());
|
||||
// setting a new parent for root groups is not allowed
|
||||
Q_ASSERT(!m_db || (m_db->rootGroup() != this));
|
||||
@@ -1143,24 +1136,6 @@ bool Group::resolveAutoTypeEnabled() const
|
||||
}
|
||||
}
|
||||
|
||||
bool Group::resolveBrowserOptionEnabled(const QString& option) const
|
||||
{
|
||||
switch (resolveCustomDataTriState(option, true)) {
|
||||
case Inherit:
|
||||
if (!m_parent) {
|
||||
return false;
|
||||
}
|
||||
return m_parent->resolveBrowserOptionEnabled(option);
|
||||
case Enable:
|
||||
return true;
|
||||
case Disable:
|
||||
return false;
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Entry* Group::addEntryWithPath(const QString& entryPath)
|
||||
{
|
||||
if (entryPath.isEmpty() || findEntryByPath(entryPath)) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2021 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
|
||||
@@ -94,7 +94,6 @@ public:
|
||||
Group::MergeMode mergeMode() const;
|
||||
bool resolveSearchingEnabled() const;
|
||||
bool resolveAutoTypeEnabled() const;
|
||||
bool resolveBrowserOptionEnabled(const QString& option) const;
|
||||
Entry* lastTopVisibleEntry() const;
|
||||
bool isExpired() const;
|
||||
bool isRecycled() const;
|
||||
|
||||
@@ -20,28 +20,28 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QTimer>
|
||||
|
||||
namespace
|
||||
{
|
||||
// Minimum timeout is 10 seconds
|
||||
constexpr int MIN_TIMEOUT = 10000;
|
||||
} // namespace
|
||||
|
||||
InactivityTimer::InactivityTimer(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_timer(new QTimer(this))
|
||||
, m_active(false)
|
||||
{
|
||||
m_timer->setSingleShot(false);
|
||||
m_timer->setSingleShot(true);
|
||||
connect(m_timer, SIGNAL(timeout()), SLOT(timeout()));
|
||||
}
|
||||
|
||||
void InactivityTimer::activate(int inactivityTimeout)
|
||||
void InactivityTimer::setInactivityTimeout(int inactivityTimeout)
|
||||
{
|
||||
Q_ASSERT(inactivityTimeout > 0);
|
||||
|
||||
m_timer->setInterval(inactivityTimeout);
|
||||
}
|
||||
|
||||
void InactivityTimer::activate()
|
||||
{
|
||||
if (!m_active) {
|
||||
qApp->installEventFilter(this);
|
||||
}
|
||||
m_active = true;
|
||||
m_resetBlocked = false;
|
||||
m_timer->setInterval(qMax(MIN_TIMEOUT, inactivityTimeout));
|
||||
m_timer->start();
|
||||
}
|
||||
|
||||
@@ -54,15 +54,12 @@ void InactivityTimer::deactivate()
|
||||
|
||||
bool InactivityTimer::eventFilter(QObject* watched, QEvent* event)
|
||||
{
|
||||
const auto type = event->type();
|
||||
const QEvent::Type type = event->type();
|
||||
// clang-format off
|
||||
if (!m_resetBlocked &&
|
||||
((type >= QEvent::MouseButtonPress && type <= QEvent::KeyRelease) ||
|
||||
(type >= QEvent::HoverEnter && type <= QEvent::HoverMove) ||
|
||||
type == QEvent::Wheel)) {
|
||||
if ((type >= QEvent::MouseButtonPress && type <= QEvent::KeyRelease)
|
||||
|| (type >= QEvent::HoverEnter && type <= QEvent::HoverMove)
|
||||
|| (type == QEvent::Wheel)) {
|
||||
m_timer->start();
|
||||
m_resetBlocked = true;
|
||||
QTimer::singleShot(500, this, [this]() { m_resetBlocked = false; });
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
@@ -76,7 +73,7 @@ void InactivityTimer::timeout()
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_active) {
|
||||
if (m_active && !m_timer->isActive()) {
|
||||
emit inactivityDetected();
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ class InactivityTimer : public QObject
|
||||
|
||||
public:
|
||||
explicit InactivityTimer(QObject* parent = nullptr);
|
||||
void activate(int inactivityTimeout);
|
||||
void setInactivityTimeout(int inactivityTimeout);
|
||||
void activate();
|
||||
void deactivate();
|
||||
|
||||
signals:
|
||||
@@ -43,8 +44,7 @@ private slots:
|
||||
|
||||
private:
|
||||
QTimer* m_timer;
|
||||
bool m_active = false;
|
||||
bool m_resetBlocked = false;
|
||||
bool m_active;
|
||||
QMutex m_emitMutx;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,107 +21,6 @@
|
||||
#include "core/Metadata.h"
|
||||
#include "core/Tools.h"
|
||||
|
||||
Merger::Change::Change(Type type, QString details)
|
||||
: m_type{type}
|
||||
, m_details{std::move(details)}
|
||||
{
|
||||
}
|
||||
|
||||
Merger::Change::Change(Type type, const Group& group, QString details)
|
||||
: m_type{type}
|
||||
, m_group{group.fullPath()}
|
||||
, m_uuid{group.uuid()}
|
||||
, m_details{std::move(details)}
|
||||
{
|
||||
}
|
||||
Merger::Change::Change(Type type, const Entry& entry, QString details)
|
||||
: m_type{type}
|
||||
, m_title{entry.title()}
|
||||
, m_uuid{entry.uuid()}
|
||||
, m_details{std::move(details)}
|
||||
{
|
||||
if (const auto* group = entry.group()) {
|
||||
m_group = group->fullPath();
|
||||
}
|
||||
}
|
||||
Merger::Change::Change(QString details)
|
||||
: m_details{std::move(details)}
|
||||
{
|
||||
}
|
||||
|
||||
bool Merger::Change::operator==(const Merger::Change& other) const
|
||||
{
|
||||
return m_type == other.m_type && m_group == other.m_group && m_title == other.m_title && m_uuid == other.m_uuid
|
||||
&& m_details == other.m_details;
|
||||
}
|
||||
|
||||
bool Merger::Change::operator!=(const Merger::Change& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
Merger::Change::Type Merger::Change::type() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
const QString& Merger::Change::title() const
|
||||
{
|
||||
return m_title;
|
||||
}
|
||||
const QString& Merger::Change::group() const
|
||||
{
|
||||
return m_group;
|
||||
}
|
||||
const QUuid& Merger::Change::uuid() const
|
||||
{
|
||||
return m_uuid;
|
||||
}
|
||||
const QString& Merger::Change::details() const
|
||||
{
|
||||
return m_details;
|
||||
}
|
||||
|
||||
QString Merger::Change::typeString() const
|
||||
{
|
||||
switch (m_type) {
|
||||
case Type::Added:
|
||||
return tr("Added");
|
||||
case Type::Modified:
|
||||
return tr("Modified");
|
||||
case Type::Moved:
|
||||
return tr("Moved");
|
||||
case Type::Deleted:
|
||||
return tr("Deleted");
|
||||
case Type::Metadata:
|
||||
return "Metadata";
|
||||
case Type::Unspecified:
|
||||
return "";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
QString Merger::Change::toString() const
|
||||
{
|
||||
QString result;
|
||||
if (m_type != Type::Unspecified) {
|
||||
result += QString("%1: ").arg(typeString());
|
||||
}
|
||||
if (!m_group.isEmpty()) {
|
||||
result += QString("'%1'").arg(m_group);
|
||||
}
|
||||
if (!m_title.isEmpty()) {
|
||||
result += QString("/'%1'").arg(m_title);
|
||||
}
|
||||
if (!m_uuid.isNull()) {
|
||||
result += QString(" [%1]").arg(m_uuid.toString());
|
||||
}
|
||||
if (!m_details.isEmpty()) {
|
||||
result += QString(" (%1)").arg(m_details);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Merger::Merger(const Database* sourceDb, Database* targetDb)
|
||||
: m_mode(Group::Default)
|
||||
{
|
||||
@@ -165,9 +64,8 @@ void Merger::setSkipDatabaseCustomData(bool state)
|
||||
m_skipCustomData = state;
|
||||
}
|
||||
|
||||
Merger::ChangeList Merger::merge(bool dryRun)
|
||||
QStringList Merger::merge()
|
||||
{
|
||||
m_dryRun = dryRun;
|
||||
// Order of merge steps is important - it is possible that we
|
||||
// create some items before deleting them afterwards
|
||||
ChangeList changes;
|
||||
@@ -176,10 +74,9 @@ Merger::ChangeList Merger::merge(bool dryRun)
|
||||
changes << mergeMetadata(m_context);
|
||||
|
||||
// At this point we have a list of changes we may want to show the user
|
||||
if (!changes.isEmpty() && !dryRun) {
|
||||
if (!changes.isEmpty()) {
|
||||
m_context.m_targetDb->markAsModified();
|
||||
}
|
||||
m_dryRun = false;
|
||||
return changes;
|
||||
}
|
||||
|
||||
@@ -191,58 +88,42 @@ Merger::ChangeList Merger::mergeGroup(const MergeContext& context)
|
||||
for (Entry* sourceEntry : sourceEntries) {
|
||||
Entry* targetEntry = context.m_targetRootGroup->findEntryByUuid(sourceEntry->uuid());
|
||||
if (!targetEntry) {
|
||||
changes << tr("Creating missing %1 [%2]").arg(sourceEntry->title(), sourceEntry->uuidToHex());
|
||||
// This entry does not exist at all. Create it.
|
||||
changes << Change(Change::Type::Added, *sourceEntry);
|
||||
if (!m_dryRun) {
|
||||
targetEntry = sourceEntry->clone(Entry::CloneIncludeHistory);
|
||||
moveEntry(targetEntry, context.m_targetGroup);
|
||||
}
|
||||
targetEntry = sourceEntry->clone(Entry::CloneIncludeHistory);
|
||||
moveEntry(targetEntry, context.m_targetGroup);
|
||||
} else {
|
||||
// Entry is already present in the database. Update it.
|
||||
const bool locationChanged =
|
||||
targetEntry->timeInfo().locationChanged() < sourceEntry->timeInfo().locationChanged();
|
||||
if (locationChanged && targetEntry->group() != context.m_targetGroup) {
|
||||
changes << Change(
|
||||
Change::Type::Moved, *sourceEntry, tr("Previous location: %1").arg(targetEntry->group()->name()));
|
||||
if (!m_dryRun) {
|
||||
moveEntry(targetEntry, context.m_targetGroup);
|
||||
}
|
||||
changes << tr("Relocating %1 [%2]").arg(sourceEntry->title(), sourceEntry->uuidToHex());
|
||||
moveEntry(targetEntry, context.m_targetGroup);
|
||||
}
|
||||
changes << resolveEntryConflict(context, sourceEntry, targetEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// merge child groups recursively
|
||||
// merge groups recursively
|
||||
const QList<Group*> sourceChildGroups = context.m_sourceGroup->children();
|
||||
for (Group* sourceChildGroup : sourceChildGroups) {
|
||||
bool groupCreated = false;
|
||||
Group* targetChildGroup = context.m_targetRootGroup->findGroupByUuid(sourceChildGroup->uuid());
|
||||
if (!targetChildGroup) {
|
||||
changes << Change(
|
||||
Change::Type::Added,
|
||||
*sourceChildGroup,
|
||||
tr("Number of entries in group: %1").arg(QString::number(sourceChildGroup->entries().size())));
|
||||
// Create the target group, it will be cleaned up later if in dry run mode
|
||||
changes << tr("Creating missing %1 [%2]").arg(sourceChildGroup->name(), sourceChildGroup->uuidToHex());
|
||||
targetChildGroup = sourceChildGroup->clone(Entry::CloneNoFlags, Group::CloneNoFlags);
|
||||
groupCreated = true;
|
||||
if (!m_dryRun) {
|
||||
moveGroup(targetChildGroup, context.m_targetGroup);
|
||||
TimeInfo timeinfo = targetChildGroup->timeInfo();
|
||||
timeinfo.setLocationChanged(sourceChildGroup->timeInfo().locationChanged());
|
||||
targetChildGroup->setTimeInfo(timeinfo);
|
||||
}
|
||||
moveGroup(targetChildGroup, context.m_targetGroup);
|
||||
TimeInfo timeinfo = targetChildGroup->timeInfo();
|
||||
timeinfo.setLocationChanged(sourceChildGroup->timeInfo().locationChanged());
|
||||
targetChildGroup->setTimeInfo(timeinfo);
|
||||
} else {
|
||||
bool locationChanged =
|
||||
targetChildGroup->timeInfo().locationChanged() < sourceChildGroup->timeInfo().locationChanged();
|
||||
if (locationChanged && targetChildGroup->parent() != context.m_targetGroup) {
|
||||
changes << Change(
|
||||
Change::Type::Moved, *sourceChildGroup, tr("Previous location: %1").arg(targetChildGroup->name()));
|
||||
if (!m_dryRun) {
|
||||
moveGroup(targetChildGroup, context.m_targetGroup);
|
||||
TimeInfo timeinfo = targetChildGroup->timeInfo();
|
||||
timeinfo.setLocationChanged(sourceChildGroup->timeInfo().locationChanged());
|
||||
targetChildGroup->setTimeInfo(timeinfo);
|
||||
}
|
||||
changes << tr("Relocating %1 [%2]").arg(sourceChildGroup->name(), sourceChildGroup->uuidToHex());
|
||||
moveGroup(targetChildGroup, context.m_targetGroup);
|
||||
TimeInfo timeinfo = targetChildGroup->timeInfo();
|
||||
timeinfo.setLocationChanged(sourceChildGroup->timeInfo().locationChanged());
|
||||
targetChildGroup->setTimeInfo(timeinfo);
|
||||
}
|
||||
changes << resolveGroupConflict(context, sourceChildGroup, targetChildGroup);
|
||||
}
|
||||
@@ -253,10 +134,6 @@ Merger::ChangeList Merger::mergeGroup(const MergeContext& context)
|
||||
sourceChildGroup,
|
||||
targetChildGroup};
|
||||
changes << mergeGroup(subcontext);
|
||||
// Cleanup the temporary target group structure
|
||||
if (m_dryRun && groupCreated) {
|
||||
delete subcontext.m_targetGroup;
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
@@ -272,68 +149,24 @@ Merger::resolveGroupConflict(const MergeContext& context, const Group* sourceChi
|
||||
|
||||
// only if the other group is newer, update the existing one.
|
||||
if (timeExisting < timeOther) {
|
||||
QStringList modifications;
|
||||
auto updateIfNecessary = [&modifications, this](const auto& targetValue,
|
||||
const auto& sourceValue,
|
||||
auto&& updateFunction,
|
||||
const QString& modification) {
|
||||
if (targetValue != sourceValue) {
|
||||
modifications << modification;
|
||||
if (!m_dryRun) {
|
||||
updateFunction(sourceValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
updateIfNecessary(
|
||||
targetChildGroup->name(),
|
||||
sourceChildGroup->name(),
|
||||
[&](auto&& newValue) { targetChildGroup->setName(newValue); },
|
||||
tr("Group name"));
|
||||
updateIfNecessary(
|
||||
targetChildGroup->notes(),
|
||||
sourceChildGroup->notes(),
|
||||
[&](auto&& newValue) { targetChildGroup->setNotes(newValue); },
|
||||
tr("Notes"));
|
||||
changes << tr("Overwriting %1 [%2]").arg(sourceChildGroup->name(), sourceChildGroup->uuidToHex());
|
||||
targetChildGroup->setName(sourceChildGroup->name());
|
||||
targetChildGroup->setNotes(sourceChildGroup->notes());
|
||||
if (sourceChildGroup->iconNumber() == 0) {
|
||||
updateIfNecessary(
|
||||
targetChildGroup->iconUuid(),
|
||||
sourceChildGroup->iconUuid(),
|
||||
[&](auto&& newValue) { targetChildGroup->setIcon(newValue); },
|
||||
tr("Icon (UUID)"));
|
||||
targetChildGroup->setIcon(sourceChildGroup->iconUuid());
|
||||
} else {
|
||||
updateIfNecessary(
|
||||
targetChildGroup->iconNumber(),
|
||||
sourceChildGroup->iconNumber(),
|
||||
[&](auto&& newValue) { targetChildGroup->setIcon(newValue); },
|
||||
tr("Icon (Number)"));
|
||||
targetChildGroup->setIcon(sourceChildGroup->iconNumber());
|
||||
}
|
||||
updateIfNecessary(
|
||||
targetChildGroup->timeInfo().expiryTime(),
|
||||
sourceChildGroup->timeInfo().expiryTime(),
|
||||
[&](auto&& newValue) { targetChildGroup->setExpiryTime(newValue); },
|
||||
tr("Expiry time"));
|
||||
updateIfNecessary(
|
||||
timeExisting,
|
||||
timeOther,
|
||||
[&](auto&& newValue) {
|
||||
TimeInfo timeInfo = targetChildGroup->timeInfo();
|
||||
timeInfo.setLastModificationTime(newValue);
|
||||
targetChildGroup->setTimeInfo(timeInfo);
|
||||
},
|
||||
tr("Modification time"));
|
||||
changes << Change(Change::Type::Modified, *sourceChildGroup, modifications.join(", "));
|
||||
targetChildGroup->setExpiryTime(sourceChildGroup->timeInfo().expiryTime());
|
||||
TimeInfo timeInfo = targetChildGroup->timeInfo();
|
||||
timeInfo.setLastModificationTime(timeOther);
|
||||
targetChildGroup->setTimeInfo(timeInfo);
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
void Merger::moveEntry(Entry* entry, Group* targetGroup)
|
||||
{
|
||||
if (m_dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(entry);
|
||||
Group* sourceGroup = entry->group();
|
||||
if (sourceGroup == targetGroup) {
|
||||
@@ -363,10 +196,6 @@ void Merger::moveEntry(Entry* entry, Group* targetGroup)
|
||||
|
||||
void Merger::moveGroup(Group* group, Group* targetGroup)
|
||||
{
|
||||
if (m_dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(group);
|
||||
Group* sourceGroup = group->parentGroup();
|
||||
if (sourceGroup == targetGroup) {
|
||||
@@ -396,10 +225,6 @@ void Merger::moveGroup(Group* group, Group* targetGroup)
|
||||
|
||||
void Merger::eraseEntry(Entry* entry)
|
||||
{
|
||||
if (m_dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
Database* database = entry->database();
|
||||
// most simple method to remove an item from DeletedObjects :(
|
||||
const QList<DeletedObject> deletions = database->deletedObjects();
|
||||
@@ -417,10 +242,6 @@ void Merger::eraseEntry(Entry* entry)
|
||||
|
||||
void Merger::eraseGroup(Group* group)
|
||||
{
|
||||
if (m_dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
Database* database = group->database();
|
||||
// most simple method to remove an item from DeletedObjects :(
|
||||
const QList<DeletedObject> deletions = database->deletedObjects();
|
||||
@@ -447,8 +268,6 @@ Merger::ChangeList Merger::resolveEntryConflict_MergeHistories(const MergeContex
|
||||
const int comparison = compare(targetEntry->timeInfo().lastModificationTime(),
|
||||
sourceEntry->timeInfo().lastModificationTime(),
|
||||
CompareItemIgnoreMilliseconds);
|
||||
auto differences = targetEntry->calculateDifference(sourceEntry);
|
||||
differences += "History";
|
||||
const int maxItems = targetEntry->database()->metadata()->historyMaxItems();
|
||||
if (comparison < 0) {
|
||||
Group* currentGroup = targetEntry->group();
|
||||
@@ -457,9 +276,7 @@ Merger::ChangeList Merger::resolveEntryConflict_MergeHistories(const MergeContex
|
||||
qPrintable(targetEntry->title()),
|
||||
qPrintable(sourceEntry->title()),
|
||||
qPrintable(currentGroup->name()));
|
||||
changes << Change(Change::Type::Modified,
|
||||
*targetEntry,
|
||||
tr("%1 (Add local modifications to new entry)").arg(differences.join(", ")));
|
||||
changes << tr("Synchronizing from newer source %1 [%2]").arg(targetEntry->title(), targetEntry->uuidToHex());
|
||||
mergeHistory(targetEntry, clonedEntry, mergeMethod, maxItems);
|
||||
eraseEntry(targetEntry);
|
||||
moveEntry(clonedEntry, currentGroup);
|
||||
@@ -470,9 +287,8 @@ Merger::ChangeList Merger::resolveEntryConflict_MergeHistories(const MergeContex
|
||||
qPrintable(targetEntry->group()->name()));
|
||||
const bool changed = mergeHistory(sourceEntry, targetEntry, mergeMethod, maxItems);
|
||||
if (changed) {
|
||||
changes << Change(Change::Type::Modified,
|
||||
*targetEntry,
|
||||
tr("%1 (Add new modifications to existing entry)").arg(differences.join(", ")));
|
||||
changes
|
||||
<< tr("Synchronizing from older source %1 [%2]").arg(targetEntry->title(), targetEntry->uuidToHex());
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
@@ -484,10 +300,8 @@ Merger::resolveEntryConflict(const MergeContext& context, const Entry* sourceEnt
|
||||
// We need to cut off the milliseconds since the persistent format only supports times down to seconds
|
||||
// so when we import data from a remote source, it may represent the (or even some msec newer) data
|
||||
// which may be discarded due to higher runtime precision
|
||||
Group::MergeMode mergeMode = m_mode;
|
||||
if (mergeMode == Group::Default && context.m_targetGroup) {
|
||||
mergeMode = context.m_targetGroup->mergeMode();
|
||||
}
|
||||
|
||||
Group::MergeMode mergeMode = m_mode == Group::Default ? context.m_targetGroup->mergeMode() : m_mode;
|
||||
return resolveEntryConflict_MergeHistories(context, sourceEntry, targetEntry, mergeMode);
|
||||
}
|
||||
|
||||
@@ -510,11 +324,11 @@ bool Merger::mergeHistory(const Entry* sourceEntry,
|
||||
const QDateTime modificationTime = Clock::serialized(historyItem->timeInfo().lastModificationTime());
|
||||
if (merged.contains(modificationTime)
|
||||
&& !merged[modificationTime]->equals(historyItem, CompareItemIgnoreMilliseconds)) {
|
||||
qWarning("Inconsistent history entry of %s[%s] at %s contains conflicting changes - conflict resolution "
|
||||
"may lose data!",
|
||||
qPrintable(sourceEntry->title()),
|
||||
qPrintable(sourceEntry->uuidToHex()),
|
||||
qPrintable(modificationTime.toString("yyyy-MM-dd HH-mm-ss-zzz")));
|
||||
::qWarning("Inconsistent history entry of %s[%s] at %s contains conflicting changes - conflict resolution "
|
||||
"may lose data!",
|
||||
qPrintable(sourceEntry->title()),
|
||||
qPrintable(sourceEntry->uuidToHex()),
|
||||
qPrintable(modificationTime.toString("yyyy-MM-dd HH-mm-ss-zzz")));
|
||||
}
|
||||
merged[modificationTime] = historyItem->clone(Entry::CloneNoFlags);
|
||||
}
|
||||
@@ -523,10 +337,11 @@ bool Merger::mergeHistory(const Entry* sourceEntry,
|
||||
const QDateTime modificationTime = Clock::serialized(historyItem->timeInfo().lastModificationTime());
|
||||
if (merged.contains(modificationTime)
|
||||
&& !merged[modificationTime]->equals(historyItem, CompareItemIgnoreMilliseconds)) {
|
||||
qWarning("History entry of %s[%s] at %s contains conflicting changes - conflict resolution may lose data!",
|
||||
qPrintable(sourceEntry->title()),
|
||||
qPrintable(sourceEntry->uuidToHex()),
|
||||
qPrintable(modificationTime.toString("yyyy-MM-dd HH-mm-ss-zzz")));
|
||||
::qWarning(
|
||||
"History entry of %s[%s] at %s contains conflicting changes - conflict resolution may lose data!",
|
||||
qPrintable(sourceEntry->title()),
|
||||
qPrintable(sourceEntry->uuidToHex()),
|
||||
qPrintable(modificationTime.toString("yyyy-MM-dd HH-mm-ss-zzz")));
|
||||
}
|
||||
if (preferRemote && merged.contains(modificationTime)) {
|
||||
// forcefully apply the remote history item
|
||||
@@ -542,9 +357,9 @@ bool Merger::mergeHistory(const Entry* sourceEntry,
|
||||
if (targetModificationTime == sourceModificationTime
|
||||
&& !targetEntry->equals(sourceEntry,
|
||||
CompareItemIgnoreMilliseconds | CompareItemIgnoreHistory | CompareItemIgnoreLocation)) {
|
||||
qWarning("Entry of %s[%s] contains conflicting changes - conflict resolution may lose data!",
|
||||
qPrintable(sourceEntry->title()),
|
||||
qPrintable(sourceEntry->uuidToHex()));
|
||||
::qWarning("Entry of %s[%s] contains conflicting changes - conflict resolution may lose data!",
|
||||
qPrintable(sourceEntry->title()),
|
||||
qPrintable(sourceEntry->uuidToHex()));
|
||||
}
|
||||
|
||||
if (targetModificationTime < sourceModificationTime) {
|
||||
@@ -583,24 +398,22 @@ bool Merger::mergeHistory(const Entry* sourceEntry,
|
||||
qDeleteAll(updatedHistoryItems);
|
||||
return false;
|
||||
}
|
||||
if (!m_dryRun) {
|
||||
// We need to prevent any modification to the database since every change should be tracked either
|
||||
// in a clone history item or in the Entry itself
|
||||
const TimeInfo timeInfo = targetEntry->timeInfo();
|
||||
const bool blockedSignals = targetEntry->blockSignals(true);
|
||||
bool updateTimeInfo = targetEntry->canUpdateTimeinfo();
|
||||
targetEntry->setUpdateTimeinfo(false);
|
||||
targetEntry->removeHistoryItems(targetHistoryItems);
|
||||
for (Entry* historyItem : merged) {
|
||||
Q_ASSERT(!historyItem->parent());
|
||||
targetEntry->addHistoryItem(historyItem);
|
||||
}
|
||||
targetEntry->truncateHistory();
|
||||
targetEntry->blockSignals(blockedSignals);
|
||||
targetEntry->setUpdateTimeinfo(updateTimeInfo);
|
||||
Q_ASSERT(timeInfo == targetEntry->timeInfo());
|
||||
Q_UNUSED(timeInfo);
|
||||
// We need to prevent any modification to the database since every change should be tracked either
|
||||
// in a clone history item or in the Entry itself
|
||||
const TimeInfo timeInfo = targetEntry->timeInfo();
|
||||
const bool blockedSignals = targetEntry->blockSignals(true);
|
||||
bool updateTimeInfo = targetEntry->canUpdateTimeinfo();
|
||||
targetEntry->setUpdateTimeinfo(false);
|
||||
targetEntry->removeHistoryItems(targetHistoryItems);
|
||||
for (Entry* historyItem : merged) {
|
||||
Q_ASSERT(!historyItem->parent());
|
||||
targetEntry->addHistoryItem(historyItem);
|
||||
}
|
||||
targetEntry->truncateHistory();
|
||||
targetEntry->blockSignals(blockedSignals);
|
||||
targetEntry->setUpdateTimeinfo(updateTimeInfo);
|
||||
Q_ASSERT(timeInfo == targetEntry->timeInfo());
|
||||
Q_UNUSED(timeInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -624,6 +437,7 @@ Merger::ChangeList Merger::mergeDeletions(const MergeContext& context)
|
||||
for (const auto& object : (targetDeletions + sourceDeletions)) {
|
||||
if (!mergedDeletions.contains(object.uuid)) {
|
||||
mergedDeletions[object.uuid] = object;
|
||||
|
||||
auto* entry = context.m_targetRootGroup->findEntryByUuid(object.uuid);
|
||||
if (entry) {
|
||||
entries << entry;
|
||||
@@ -651,18 +465,17 @@ Merger::ChangeList Merger::mergeDeletions(const MergeContext& context)
|
||||
}
|
||||
deletions << object;
|
||||
if (entry->group()) {
|
||||
changes << Change(Change::Type::Deleted, *entry, tr("Explicit deletion"));
|
||||
changes << tr("Deleting child %1 [%2]").arg(entry->title(), entry->uuidToHex());
|
||||
} else {
|
||||
changes << Change(Change::Type::Deleted, *entry, tr("Implicit deletion (e.g. removal of parent group)"));
|
||||
}
|
||||
if (!m_dryRun) {
|
||||
eraseEntry(entry);
|
||||
changes << tr("Deleting orphan %1 [%2]").arg(entry->title(), entry->uuidToHex());
|
||||
}
|
||||
// Entry is inserted into deletedObjects after deletions are processed
|
||||
eraseEntry(entry);
|
||||
}
|
||||
|
||||
while (!groups.isEmpty()) {
|
||||
auto* group = groups.takeFirst();
|
||||
if (!(group->children().toSet() & groups.toSet()).isEmpty()) {
|
||||
if (Tools::asSet(group->children()).intersects(Tools::asSet(groups))) {
|
||||
// we need to finish all children before we are able to determine if the group can be removed
|
||||
groups << group;
|
||||
continue;
|
||||
@@ -678,22 +491,17 @@ Merger::ChangeList Merger::mergeDeletions(const MergeContext& context)
|
||||
}
|
||||
deletions << object;
|
||||
if (group->parentGroup()) {
|
||||
changes << Change(Change::Type::Deleted, *group, tr("Explicit deletion"));
|
||||
changes << tr("Deleting child %1 [%2]").arg(group->name(), group->uuidToHex());
|
||||
} else {
|
||||
changes << Change(Change::Type::Deleted, *group, tr("Implicit deletion (e.g. removal of parent group)"));
|
||||
}
|
||||
if (!m_dryRun) {
|
||||
eraseGroup(group);
|
||||
changes << tr("Deleting orphan %1 [%2]").arg(group->name(), group->uuidToHex());
|
||||
}
|
||||
eraseGroup(group);
|
||||
}
|
||||
// Put every deletion to the earliest date of deletion
|
||||
if (deletions != context.m_targetDb->deletedObjects()) {
|
||||
changes << Change(Change::Type::Metadata, tr("Changed deleted objects"));
|
||||
if (!m_dryRun) {
|
||||
context.m_targetDb->setDeletedObjects(deletions);
|
||||
}
|
||||
changes << tr("Changed deleted objects");
|
||||
}
|
||||
|
||||
context.m_targetDb->setDeletedObjects(deletions);
|
||||
return changes;
|
||||
}
|
||||
|
||||
@@ -708,11 +516,8 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context)
|
||||
|
||||
for (const auto& iconUuid : sourceMetadata->customIconsOrder()) {
|
||||
if (!targetMetadata->hasCustomIcon(iconUuid)) {
|
||||
changes << Change(Change::Type::Metadata,
|
||||
tr("Adding new icon %1").arg(QString::fromLatin1(iconUuid.toRfc4122().toHex())));
|
||||
if (!m_dryRun) {
|
||||
targetMetadata->addCustomIcon(iconUuid, sourceMetadata->customIcon(iconUuid));
|
||||
}
|
||||
targetMetadata->addCustomIcon(iconUuid, sourceMetadata->customIcon(iconUuid));
|
||||
changes << tr("Adding missing icon %1").arg(QString::fromLatin1(iconUuid.toRfc4122().toHex()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,10 +540,8 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context)
|
||||
// Do not remove protected custom data
|
||||
if (!sourceMetadata->customData()->contains(key) && !sourceMetadata->customData()->isProtected(key)) {
|
||||
auto value = targetMetadata->customData()->value(key);
|
||||
changes << Change(Change::Type::Metadata, tr("Removed custom data %1 [%2]").arg(key, value));
|
||||
if (!m_dryRun) {
|
||||
targetMetadata->customData()->remove(key);
|
||||
}
|
||||
targetMetadata->customData()->remove(key);
|
||||
changes << tr("Removed custom data %1 [%2]").arg(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,10 +556,8 @@ Merger::ChangeList Merger::mergeMetadata(const MergeContext& context)
|
||||
auto targetValue = targetMetadata->customData()->value(key);
|
||||
// Merge only if the values are not the same.
|
||||
if (sourceValue != targetValue) {
|
||||
changes << Change(Change::Type::Metadata, tr("Adding custom data %1 [%2]").arg(key, sourceValue));
|
||||
if (!m_dryRun) {
|
||||
targetMetadata->customData()->set(key, sourceValue);
|
||||
}
|
||||
targetMetadata->customData()->set(key, sourceValue);
|
||||
changes << tr("Adding custom data %1 [%2]").arg(key, sourceValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,51 +32,12 @@ public:
|
||||
void setForcedMergeMode(Group::MergeMode mode);
|
||||
void resetForcedMergeMode();
|
||||
void setSkipDatabaseCustomData(bool state);
|
||||
|
||||
class Change
|
||||
{
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
Unspecified,
|
||||
Added,
|
||||
Modified,
|
||||
Moved,
|
||||
Deleted,
|
||||
Metadata,
|
||||
};
|
||||
|
||||
Change(Type type, QString details);
|
||||
Change(Type type, const Group& group, QString details = "");
|
||||
Change(Type type, const Entry& entry, QString details = "");
|
||||
explicit Change(QString details = "");
|
||||
|
||||
[[nodiscard]] Type type() const;
|
||||
[[nodiscard]] QString typeString() const;
|
||||
[[nodiscard]] const QString& title() const;
|
||||
[[nodiscard]] const QString& group() const;
|
||||
[[nodiscard]] const QUuid& uuid() const;
|
||||
[[nodiscard]] const QString& details() const;
|
||||
|
||||
[[nodiscard]] QString toString() const;
|
||||
void merge();
|
||||
|
||||
bool operator==(const Change& other) const;
|
||||
bool operator!=(const Change& other) const;
|
||||
|
||||
private:
|
||||
Type m_type{Type::Unspecified};
|
||||
QString m_title;
|
||||
QString m_group;
|
||||
QUuid m_uuid;
|
||||
QString m_details;
|
||||
};
|
||||
|
||||
using ChangeList = QList<Change>;
|
||||
|
||||
ChangeList merge(bool dryRun = false);
|
||||
QStringList merge();
|
||||
|
||||
private:
|
||||
typedef QString Change;
|
||||
typedef QStringList ChangeList;
|
||||
|
||||
struct MergeContext
|
||||
{
|
||||
QPointer<const Database> m_sourceDb;
|
||||
@@ -86,7 +47,6 @@ private:
|
||||
QPointer<const Group> m_sourceGroup;
|
||||
QPointer<Group> m_targetGroup;
|
||||
};
|
||||
|
||||
ChangeList mergeGroup(const MergeContext& context);
|
||||
ChangeList mergeDeletions(const MergeContext& context);
|
||||
ChangeList mergeMetadata(const MergeContext& context);
|
||||
@@ -108,7 +68,6 @@ private:
|
||||
MergeContext m_context;
|
||||
Group::MergeMode m_mode;
|
||||
bool m_skipCustomData = false;
|
||||
bool m_dryRun = false;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_MERGER_H
|
||||
|
||||
@@ -99,7 +99,7 @@ void PassphraseGenerator::setWordList(const QString& path)
|
||||
|
||||
m_wordlist = wordset.toList();
|
||||
|
||||
if (!isWordListValid()) {
|
||||
if (m_wordlist.size() < m_minimum_wordlist_length) {
|
||||
qWarning("Wordlist is less than minimum acceptable size: %s", qPrintable(path));
|
||||
}
|
||||
}
|
||||
@@ -117,7 +117,8 @@ void PassphraseGenerator::setWordSeparator(const QString& separator)
|
||||
|
||||
QString PassphraseGenerator::generatePassphrase() const
|
||||
{
|
||||
if (m_wordlist.isEmpty()) {
|
||||
// In case there was an error loading the wordlist
|
||||
if (!isValid() || m_wordlist.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -148,7 +149,7 @@ QString PassphraseGenerator::generatePassphrase() const
|
||||
return words.join(m_separator);
|
||||
}
|
||||
|
||||
bool PassphraseGenerator::isWordListValid() const
|
||||
bool PassphraseGenerator::isValid() const
|
||||
{
|
||||
return m_wordlist.size() >= m_minWordListSize;
|
||||
return m_wordCount > 0 && m_wordlist.size() >= m_minimum_wordlist_length;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
void setWordCase(PassphraseWordCase wordCase);
|
||||
void setDefaultWordList();
|
||||
void setWordSeparator(const QString& separator);
|
||||
bool isWordListValid() const;
|
||||
bool isValid() const;
|
||||
|
||||
QString generatePassphrase() const;
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
|
||||
private:
|
||||
int m_wordCount;
|
||||
int m_minWordListSize = 1296;
|
||||
int m_minimum_wordlist_length = 4000;
|
||||
PassphraseWordCase m_wordCase;
|
||||
QString m_separator;
|
||||
QList<QString> m_wordlist;
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#include <QIODevice>
|
||||
#include <QLocale>
|
||||
#include <QMetaProperty>
|
||||
#include <QMimeDatabase>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
@@ -52,9 +51,7 @@ namespace Tools
|
||||
{
|
||||
QString debugInfo = "KeePassXC - ";
|
||||
debugInfo.append(QObject::tr("Version %1").arg(KEEPASSXC_VERSION).append("\n"));
|
||||
#ifndef KEEPASSXC_BUILD_TYPE_RELEASE
|
||||
debugInfo.append(QObject::tr("Build Type: %1").arg(KEEPASSXC_BUILD_TYPE).append("\n"));
|
||||
#endif
|
||||
|
||||
QString commitHash;
|
||||
if (!QString(GIT_HEAD).isEmpty()) {
|
||||
@@ -64,9 +61,7 @@ namespace Tools
|
||||
debugInfo.append(QObject::tr("Revision: %1").arg(commitHash.left(7)).append("\n"));
|
||||
}
|
||||
|
||||
#ifdef KEEPASSXC_DIST
|
||||
debugInfo.append(QObject::tr("Distribution: %1").arg(KEEPASSXC_DIST_TYPE).append("\n"));
|
||||
#endif
|
||||
|
||||
// Qt related debugging information.
|
||||
debugInfo.append("\n");
|
||||
@@ -87,35 +82,23 @@ namespace Tools
|
||||
debugInfo.append("\n\n");
|
||||
|
||||
QString extensions;
|
||||
#ifdef WITH_XC_AUTOTYPE
|
||||
extensions += "\n- " + QObject::tr("Auto-Type");
|
||||
#endif
|
||||
#ifdef WITH_XC_BROWSER
|
||||
extensions += "\n- " + QObject::tr("Browser Integration");
|
||||
#endif
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
extensions += "\n- " + QObject::tr("Passkeys");
|
||||
#endif
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
extensions += "\n- " + QObject::tr("SSH Agent");
|
||||
#endif
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
extensions += "\n- " + QObject::tr("KeeShare");
|
||||
#endif
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
extensions += "\n- " + QObject::tr("YubiKey");
|
||||
#endif
|
||||
extensions += "\n- " + QObject::tr("Hardware Keys");
|
||||
#if defined(Q_OS_MACOS) || defined(Q_CC_MSVC)
|
||||
extensions += "\n- " + QObject::tr("Quick Unlock");
|
||||
#endif
|
||||
#ifdef WITH_XC_FDOSECRETS
|
||||
#ifdef KPXC_FEATURE_BROWSER
|
||||
extensions += "\n- " + QObject::tr("Browser Integration");
|
||||
extensions += "\n- " + QObject::tr("Passkeys");
|
||||
#endif
|
||||
#ifdef KPXC_FEATURE_SSHAGENT
|
||||
extensions += "\n- " + QObject::tr("SSH Agent");
|
||||
#endif
|
||||
#ifdef KPXC_FEATURE_FDOSECRETS
|
||||
extensions += "\n- " + QObject::tr("Secret Service Integration");
|
||||
#endif
|
||||
|
||||
if (extensions.isEmpty()) {
|
||||
extensions = " " + QObject::tr("None");
|
||||
}
|
||||
|
||||
debugInfo.append(QObject::tr("Enabled extensions:").append(extensions).append("\n"));
|
||||
return debugInfo;
|
||||
}
|
||||
@@ -151,23 +134,23 @@ namespace Tools
|
||||
|
||||
if (seconds >= secondsInYear) {
|
||||
auto years = std::floor(seconds / secondsInYear);
|
||||
return QObject::tr("over %1 year(s)", "", years).arg(years);
|
||||
return QObject::tr("over %1 year(s)", nullptr, years).arg(years);
|
||||
} else if (seconds >= secondsInMonth) {
|
||||
auto months = std::round(seconds / secondsInMonth);
|
||||
return QObject::tr("about %1 month(s)", "", months).arg(months);
|
||||
return QObject::tr("about %1 month(s)", nullptr, months).arg(months);
|
||||
} else if (seconds >= secondsInWeek) {
|
||||
auto weeks = std::round(seconds / secondsInWeek);
|
||||
return QObject::tr("%1 week(s)", "", weeks).arg(weeks);
|
||||
return QObject::tr("%1 week(s)", nullptr, weeks).arg(weeks);
|
||||
} else if (seconds >= secondsInDay) {
|
||||
auto days = std::floor(seconds / secondsInDay);
|
||||
return QObject::tr("%1 day(s)", "", days).arg(days);
|
||||
return QObject::tr("%1 day(s)", nullptr, days).arg(days);
|
||||
} else if (seconds >= secondsInHour) {
|
||||
auto hours = std::floor(seconds / secondsInHour);
|
||||
return QObject::tr("%1 hour(s)", "", hours).arg(hours);
|
||||
return QObject::tr("%1 hour(s)", nullptr, hours).arg(hours);
|
||||
}
|
||||
|
||||
auto minutes = std::floor(seconds / 60);
|
||||
return QObject::tr("%1 minute(s)", "", minutes).arg(minutes);
|
||||
return QObject::tr("%1 minute(s)", nullptr, minutes).arg(minutes);
|
||||
}
|
||||
|
||||
bool readFromDevice(QIODevice* device, QByteArray& data, int size)
|
||||
@@ -479,57 +462,29 @@ namespace Tools
|
||||
|
||||
MimeType toMimeType(const QString& mimeName)
|
||||
{
|
||||
const static QStringList TextFormats = {"text/",
|
||||
"application/json",
|
||||
"application/xml",
|
||||
"application/soap+xml",
|
||||
"application/x-yaml",
|
||||
"application/protobuf",
|
||||
"application/x-zerosize"};
|
||||
const static QStringList HtmlFormats = {"text/html"};
|
||||
const static QStringList MarkdownFormats = {"text/markdown"};
|
||||
const static QStringList ImageFormats = {"image/"};
|
||||
static QStringList textFormats = {
|
||||
"text/",
|
||||
"application/json",
|
||||
"application/xml",
|
||||
"application/soap+xml",
|
||||
"application/x-yaml",
|
||||
"application/protobuf",
|
||||
};
|
||||
static QStringList imageFormats = {"image/"};
|
||||
|
||||
static auto isCompatible = [](const QString& format, const QStringList& list) {
|
||||
return std::any_of(
|
||||
list.cbegin(), list.cend(), [&format](const auto& item) { return format.startsWith(item); });
|
||||
};
|
||||
|
||||
if (isCompatible(mimeName, ImageFormats)) {
|
||||
if (isCompatible(mimeName, imageFormats)) {
|
||||
return MimeType::Image;
|
||||
}
|
||||
|
||||
if (isCompatible(mimeName, TextFormats)) {
|
||||
if (isCompatible(mimeName, HtmlFormats)) {
|
||||
return MimeType::Html;
|
||||
} else if (isCompatible(mimeName, MarkdownFormats)) {
|
||||
return MimeType::Markdown;
|
||||
}
|
||||
|
||||
if (isCompatible(mimeName, textFormats)) {
|
||||
return MimeType::PlainText;
|
||||
}
|
||||
|
||||
return MimeType::Unknown;
|
||||
}
|
||||
|
||||
MimeType getMimeType(const QByteArray& data)
|
||||
{
|
||||
QMimeDatabase mimeDb;
|
||||
const auto mime = mimeDb.mimeTypeForData(data);
|
||||
return toMimeType(mime.name());
|
||||
}
|
||||
|
||||
MimeType getMimeType(const QFileInfo& fileInfo)
|
||||
{
|
||||
QMimeDatabase mimeDb;
|
||||
const auto mime = mimeDb.mimeTypeForFile(fileInfo);
|
||||
return toMimeType(mime.name());
|
||||
}
|
||||
|
||||
bool isTextMimeType(MimeType mimeType)
|
||||
{
|
||||
return mimeType == Tools::MimeType::PlainText || mimeType == Tools::MimeType::Html
|
||||
|| mimeType == Tools::MimeType::Markdown;
|
||||
}
|
||||
|
||||
} // namespace Tools
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "core/Global.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
#include <QList>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QSet>
|
||||
@@ -120,16 +119,10 @@ namespace Tools
|
||||
{
|
||||
Image,
|
||||
PlainText,
|
||||
Html,
|
||||
Markdown,
|
||||
Unknown
|
||||
};
|
||||
|
||||
MimeType toMimeType(const QString& mimeName);
|
||||
MimeType getMimeType(const QByteArray& data);
|
||||
MimeType getMimeType(const QFileInfo& fileInfo);
|
||||
bool isTextMimeType(MimeType mimeType);
|
||||
|
||||
} // namespace Tools
|
||||
|
||||
#endif // KEEPASSX_TOOLS_H
|
||||
|
||||
@@ -210,33 +210,12 @@ QString Totp::writeSettings(const QSharedPointer<Totp::Settings>& settings,
|
||||
}
|
||||
}
|
||||
|
||||
QString Totp::checkValidSettings(const QSharedPointer<Totp::Settings>& settings)
|
||||
QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, const quint64 time)
|
||||
{
|
||||
Q_ASSERT(!settings.isNull());
|
||||
if (settings.isNull()) {
|
||||
return QObject::tr("Invalid Settings", "TOTP");
|
||||
}
|
||||
QVariant secret = Base32::decode(Base32::sanitizeInput(settings->key.toLatin1()));
|
||||
if (secret.isNull()) {
|
||||
return QObject::tr("Invalid Key", "TOTP");
|
||||
}
|
||||
if (settings->step == 0) {
|
||||
return QObject::tr("Invalid Step", "TOTP");
|
||||
}
|
||||
if (settings->digits == 0) {
|
||||
return QObject::tr("Invalid Digits", "TOTP");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, bool* isValid, const quint64 time)
|
||||
{
|
||||
auto error = checkValidSettings(settings);
|
||||
if (!error.isEmpty()) {
|
||||
if (isValid) {
|
||||
*isValid = false;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
const Encoder& encoder = settings->encoder;
|
||||
uint step = settings->step;
|
||||
@@ -250,6 +229,9 @@ QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, bool*
|
||||
}
|
||||
|
||||
QVariant secret = Base32::decode(Base32::sanitizeInput(settings->key.toLatin1()));
|
||||
if (secret.isNull()) {
|
||||
return QObject::tr("Invalid Key", "TOTP");
|
||||
}
|
||||
|
||||
QCryptographicHash::Algorithm cryptoHash;
|
||||
switch (settings->algorithm) {
|
||||
@@ -292,9 +274,6 @@ QString Totp::generateTotp(const QSharedPointer<Totp::Settings>& settings, bool*
|
||||
retval[pos] = encoder.alphabet[int(password % encoder.alphabet.size())];
|
||||
password /= encoder.alphabet.size();
|
||||
}
|
||||
if (isValid) {
|
||||
*isValid = true;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,10 +91,8 @@ namespace Totp
|
||||
const QString& title = {},
|
||||
const QString& username = {},
|
||||
bool forceOtp = false);
|
||||
// Returns an empty string if settings are valid, otherwise an error message is supplied
|
||||
QString checkValidSettings(const QSharedPointer<Totp::Settings>& settings);
|
||||
QString
|
||||
generateTotp(const QSharedPointer<Totp::Settings>& settings, bool* isValid = nullptr, const quint64 time = 0ull);
|
||||
|
||||
QString generateTotp(const QSharedPointer<Totp::Settings>& settings, const quint64 time = 0ull);
|
||||
|
||||
bool hasCustomSettings(const QSharedPointer<Totp::Settings>& settings);
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ namespace Crypto
|
||||
{
|
||||
bool init()
|
||||
{
|
||||
#ifdef WITH_XC_BOTAN3
|
||||
#ifdef WITH_BOTAN3
|
||||
unsigned int version_major = 3, min_version_minor = 0;
|
||||
QString versionString = "3.x";
|
||||
#else
|
||||
|
||||
@@ -34,7 +34,7 @@ bool SymmetricCipher::init(Mode mode, Direction direction, const QByteArray& key
|
||||
try {
|
||||
auto botanMode = modeToString(mode);
|
||||
auto botanDirection =
|
||||
#ifdef WITH_XC_BOTAN3
|
||||
#ifdef WITH_BOTAN3
|
||||
(direction == SymmetricCipher::Encrypt ? Botan::Cipher_Dir::Encryption : Botan::Cipher_Dir::Decryption);
|
||||
#else
|
||||
(direction == SymmetricCipher::Encrypt ? Botan::Cipher_Dir::ENCRYPTION : Botan::Cipher_Dir::DECRYPTION);
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
*/
|
||||
Argon2Kdf::Argon2Kdf(Type type)
|
||||
: Kdf::Kdf(type == Type::Argon2d ? KeePass2::KDF_ARGON2D : KeePass2::KDF_ARGON2ID)
|
||||
, m_version(ARGON2_DEFAULT_VERSION)
|
||||
, m_memory(ARGON2_DEFAULT_MEMORY)
|
||||
, m_parallelism(qMin<quint32>(QThread::idealThreadCount(), ARGON2_DEFAULT_PARALLELISM))
|
||||
, m_version(0x13)
|
||||
, m_memory(1 << 16)
|
||||
, m_parallelism(static_cast<quint32>(QThread::idealThreadCount()))
|
||||
{
|
||||
m_rounds = ARGON2_DEFAULT_ROUNDS;
|
||||
m_rounds = 10;
|
||||
}
|
||||
|
||||
quint32 Argon2Kdf::version() const
|
||||
@@ -52,7 +52,7 @@ bool Argon2Kdf::setVersion(quint32 version)
|
||||
m_version = version;
|
||||
return true;
|
||||
}
|
||||
m_version = ARGON2_DEFAULT_VERSION;
|
||||
m_version = 0x13;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ bool Argon2Kdf::setMemory(quint64 kibibytes)
|
||||
m_memory = kibibytes;
|
||||
return true;
|
||||
}
|
||||
m_memory = ARGON2_DEFAULT_MEMORY;
|
||||
m_memory = 16;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ bool Argon2Kdf::setParallelism(quint32 threads)
|
||||
m_parallelism = threads;
|
||||
return true;
|
||||
}
|
||||
m_parallelism = ARGON2_DEFAULT_PARALLELISM;
|
||||
m_parallelism = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,6 @@
|
||||
|
||||
#include "Kdf.h"
|
||||
|
||||
constexpr auto ARGON2_DEFAULT_VERSION = 0x13;
|
||||
constexpr auto ARGON2_DEFAULT_ROUNDS = 10;
|
||||
constexpr auto ARGON2_DEFAULT_MEMORY = 1 << 16;
|
||||
constexpr auto ARGON2_DEFAULT_PARALLELISM = 4;
|
||||
|
||||
class Argon2Kdf : public Kdf
|
||||
{
|
||||
public:
|
||||
@@ -52,15 +47,6 @@ public:
|
||||
|
||||
int benchmark(int msec) const override;
|
||||
|
||||
static quint64 toMebibytes(quint64 kibibytes)
|
||||
{
|
||||
return kibibytes >> 10;
|
||||
}
|
||||
static quint64 toKibibytes(quint64 mebibits)
|
||||
{
|
||||
return mebibits << 10;
|
||||
}
|
||||
|
||||
quint32 m_version;
|
||||
quint64 m_memory;
|
||||
quint32 m_parallelism;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
if(WITH_XC_FDOSECRETS)
|
||||
if(KPXC_FEATURE_FDOSECRETS)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
add_library(fdosecrets STATIC
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace FdoSecrets
|
||||
// add some informative and readonly attributes
|
||||
attrs[ItemAttributes::UuidKey] = m_backend->uuidToHex();
|
||||
attrs[ItemAttributes::PathKey] = path();
|
||||
if (m_backend->hasValidTotp()) {
|
||||
if (m_backend->hasTotp()) {
|
||||
attrs[ItemAttributes::TotpKey] = m_backend->totp();
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -225,7 +225,6 @@ KdbxXmlWriter::BinaryIdxMap Kdbx4Writer::writeAttachments(QIODevice* device, Dat
|
||||
data.append(entry->attachments()->value(key));
|
||||
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
// Namespace KeeShare attachments so they don't get deduplicated together with attachments
|
||||
// from other databases. Prevents potential filesize side channels.
|
||||
auto group = entry->group();
|
||||
@@ -237,7 +236,6 @@ KdbxXmlWriter::BinaryIdxMap Kdbx4Writer::writeAttachments(QIODevice* device, Dat
|
||||
} else {
|
||||
hash.addData(db->uuid().toByteArray());
|
||||
}
|
||||
#endif
|
||||
hash.addData(data);
|
||||
|
||||
// Deduplicate attachments with the same hash
|
||||
|
||||
@@ -109,7 +109,6 @@ void KdbxXmlWriter::fillBinaryIdxMap()
|
||||
for (const QString& key : attachmentKeys) {
|
||||
QByteArray data = entry->attachments()->value(key);
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
// Namespace KeeShare attachments so they don't get deduplicated together with attachments
|
||||
// from other databases. Prevents potential filesize side channels.
|
||||
auto group = entry->group();
|
||||
@@ -121,7 +120,6 @@ void KdbxXmlWriter::fillBinaryIdxMap()
|
||||
} else {
|
||||
hash.addData(m_db->uuid().toByteArray());
|
||||
}
|
||||
#endif
|
||||
hash.addData(data);
|
||||
|
||||
const auto hashResult = hash.result();
|
||||
|
||||
@@ -73,13 +73,7 @@ namespace
|
||||
}
|
||||
|
||||
if (loginMap.contains("itemEmail")) {
|
||||
// Place the email value as the username if empty, otherwise set it as an attribute
|
||||
const auto email = loginMap.value("itemEmail").toString();
|
||||
if (entry->username().isEmpty()) {
|
||||
entry->setUsername(email);
|
||||
} else if (!email.isEmpty()) {
|
||||
entry->attributes()->set("login_email", email);
|
||||
}
|
||||
entry->attributes()->set("login_email", loginMap.value("itemEmail").toString());
|
||||
}
|
||||
|
||||
// Set the entry url(s)
|
||||
|
||||
@@ -113,7 +113,7 @@ Application::Application(int& argc, char** argv)
|
||||
.toUtf8()
|
||||
.constData();
|
||||
|
||||
// forcibly reset the lock file
|
||||
// forceably reset the lock file
|
||||
m_lockFile->removeStaleLockFile();
|
||||
m_lockFile->tryLock();
|
||||
// start the listen server
|
||||
|
||||
@@ -27,16 +27,14 @@
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
#include "core/Translator.h"
|
||||
#include "gui/GuiTools.h"
|
||||
#include "gui/Icons.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/osutils/OSUtils.h"
|
||||
#include "gui/styles/StateColorPalette.h"
|
||||
#include "quickunlock/QuickUnlockInterface.h"
|
||||
|
||||
#include "FileDialog.h"
|
||||
#include "MessageBox.h"
|
||||
#ifdef WITH_XC_BROWSER
|
||||
#ifdef KPXC_FEATURE_BROWSER
|
||||
#include "browser/BrowserSettingsPage.h"
|
||||
#endif
|
||||
|
||||
@@ -64,6 +62,28 @@ private:
|
||||
QWidget* widget;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper class to ignore mouse wheel events on non-focused widgets
|
||||
* NOTE: The widget must NOT have a focus policy of "WHEEL"
|
||||
*/
|
||||
class MouseWheelEventFilter : public QObject
|
||||
{
|
||||
public:
|
||||
explicit MouseWheelEventFilter(QObject* parent)
|
||||
: QObject(parent){};
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* obj, QEvent* event) override
|
||||
{
|
||||
const auto* widget = qobject_cast<QWidget*>(obj);
|
||||
if (event->type() == QEvent::Wheel && widget && !widget->hasFocus()) {
|
||||
event->ignore();
|
||||
return true;
|
||||
}
|
||||
return QObject::eventFilter(obj, event);
|
||||
}
|
||||
};
|
||||
|
||||
ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
||||
: EditWidget(parent)
|
||||
, m_secWidget(new QWidget())
|
||||
@@ -80,7 +100,7 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
||||
m_generalUi->setupUi(m_generalWidget);
|
||||
addPage(tr("General"), icons()->icon("preferences-other"), m_generalWidget);
|
||||
addPage(tr("Security"), icons()->icon("security-high"), m_secWidget);
|
||||
#ifdef WITH_XC_BROWSER
|
||||
#ifdef KPXC_FEATURE_BROWSER
|
||||
addSettingsPage(new BrowserSettingsPage());
|
||||
#endif
|
||||
|
||||
@@ -109,8 +129,6 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
||||
connect(m_generalUi->backupFilePathPicker, SIGNAL(pressed()), SLOT(selectBackupDirectory()));
|
||||
connect(m_generalUi->showExpiredEntriesOnDatabaseUnlockCheckBox, SIGNAL(toggled(bool)),
|
||||
SLOT(showExpiredEntriesOnDatabaseUnlockToggled(bool)));
|
||||
connect(m_generalUi->autoTypeAskCheckBox, SIGNAL(toggled(bool)),
|
||||
SLOT(autoTypeAskToggled(bool)));
|
||||
|
||||
connect(m_secUi->clearClipboardCheckBox, SIGNAL(toggled(bool)),
|
||||
m_secUi->clearClipboardSpinBox, SLOT(setEnabled(bool)));
|
||||
@@ -137,10 +155,7 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
||||
m_generalUi->autoTypeShortcutWidget->setStyleSheet("");
|
||||
} else {
|
||||
QToolTip::showText(mapToGlobal(rect().bottomLeft()), error);
|
||||
StateColorPalette statePalette;
|
||||
auto color = statePalette.color(StateColorPalette::ColorRole::Error);
|
||||
m_generalUi->autoTypeShortcutWidget->setStyleSheet(
|
||||
QString("QLineEdit { background: %1; }").arg(color.name()));
|
||||
m_generalUi->autoTypeShortcutWidget->setStyleSheet("background-color: #FF9696;");
|
||||
}
|
||||
});
|
||||
connect(m_generalUi->autoTypeShortcutWidget, &ShortcutWidget::shortcutReset, this, [this] {
|
||||
@@ -157,7 +172,7 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
||||
m_generalUi->trayIconAppearance->installEventFilter(mouseWheelFilter);
|
||||
m_generalUi->fontSizeComboBox->installEventFilter(mouseWheelFilter);
|
||||
|
||||
#ifdef WITH_XC_UPDATECHECK
|
||||
#ifdef KPXC_FEATURE_UPDATES
|
||||
connect(m_generalUi->checkForUpdatesOnStartupCheckBox, SIGNAL(toggled(bool)), SLOT(checkUpdatesToggled(bool)));
|
||||
#else
|
||||
m_generalUi->checkForUpdatesOnStartupCheckBox->setVisible(false);
|
||||
@@ -165,7 +180,7 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
||||
m_generalUi->checkUpdatesSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
#endif
|
||||
|
||||
#ifndef WITH_XC_NETWORKING
|
||||
#ifndef KPXC_FEATURE_NETWORK
|
||||
m_secUi->privacy->setVisible(false);
|
||||
m_generalUi->faviconTimeoutLabel->setVisible(false);
|
||||
m_generalUi->faviconTimeoutSpinBox->setVisible(false);
|
||||
@@ -287,9 +302,6 @@ void ApplicationSettingsWidget::loadSettings()
|
||||
showExpiredEntriesOnDatabaseUnlockToggled(m_generalUi->showExpiredEntriesOnDatabaseUnlockCheckBox->isChecked());
|
||||
|
||||
m_generalUi->autoTypeAskCheckBox->setChecked(config()->get(Config::Security_AutoTypeAsk).toBool());
|
||||
m_generalUi->autoTypeSkipMainWindowConfirmationCheckBox->setChecked(
|
||||
config()->get(Config::Security_AutoTypeSkipMainWindowConfirmation).toBool());
|
||||
autoTypeAskToggled(m_generalUi->autoTypeAskCheckBox->isChecked());
|
||||
m_generalUi->autoTypeRelockDatabaseCheckBox->setChecked(config()->get(Config::Security_RelockAutoType).toBool());
|
||||
|
||||
if (autoType()->isAvailable()) {
|
||||
@@ -433,8 +445,6 @@ void ApplicationSettingsWidget::saveSettings()
|
||||
m_generalUi->showExpiredEntriesOnDatabaseUnlockOffsetSpinBox->value());
|
||||
|
||||
config()->set(Config::Security_AutoTypeAsk, m_generalUi->autoTypeAskCheckBox->isChecked());
|
||||
config()->set(Config::Security_AutoTypeSkipMainWindowConfirmation,
|
||||
m_generalUi->autoTypeSkipMainWindowConfirmationCheckBox->isChecked());
|
||||
config()->set(Config::Security_RelockAutoType, m_generalUi->autoTypeRelockDatabaseCheckBox->isChecked());
|
||||
|
||||
if (autoType()->isAvailable()) {
|
||||
@@ -608,11 +618,6 @@ void ApplicationSettingsWidget::showExpiredEntriesOnDatabaseUnlockToggled(bool c
|
||||
m_generalUi->showExpiredEntriesOnDatabaseUnlockOffsetSpinBox->setEnabled(checked);
|
||||
}
|
||||
|
||||
void ApplicationSettingsWidget::autoTypeAskToggled(bool checked)
|
||||
{
|
||||
m_generalUi->autoTypeSkipMainWindowConfirmationCheckBox->setEnabled(checked);
|
||||
}
|
||||
|
||||
void ApplicationSettingsWidget::selectBackupDirectory()
|
||||
{
|
||||
auto backupDirectory =
|
||||
@@ -621,4 +626,4 @@ void ApplicationSettingsWidget::selectBackupDirectory()
|
||||
m_generalUi->backupFilePath->setText(
|
||||
QDir(backupDirectory).filePath(config()->getDefault(Config::BackupFilePathPattern).toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,6 @@ private slots:
|
||||
void rememberDatabasesToggled(bool checked);
|
||||
void checkUpdatesToggled(bool checked);
|
||||
void showExpiredEntriesOnDatabaseUnlockToggled(bool checked);
|
||||
void autoTypeAskToggled(bool checked);
|
||||
void selectBackupDirectory();
|
||||
|
||||
private:
|
||||
|
||||
@@ -1157,42 +1157,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="autoTypeSkipMainWindowLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="autoTypeSkipMainWindowSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="autoTypeSkipMainWindowConfirmationCheckBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Skip confirmation for main window Auto-Type actions</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="autoTypeHideExpiredEntryCheckBox">
|
||||
<property name="text">
|
||||
@@ -1357,9 +1321,6 @@
|
||||
<property name="text">
|
||||
<string>Remember last typed entry for:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
|
||||
@@ -25,9 +25,7 @@
|
||||
#include "gui/MessageBox.h"
|
||||
#include "keys/ChallengeResponseKey.h"
|
||||
#include "keys/FileKey.h"
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
#include "keys/drivers/YubiKeyInterfaceUSB.h"
|
||||
#endif
|
||||
#include "quickunlock/QuickUnlockInterface.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
@@ -52,9 +50,7 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
||||
: DialogyWidget(parent)
|
||||
, m_ui(new Ui::DatabaseOpenWidget())
|
||||
, m_db(nullptr)
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
, m_deviceListener(new DeviceListener(this))
|
||||
#endif
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
@@ -101,7 +97,6 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
||||
sp.setRetainSizeWhenHidden(true);
|
||||
m_ui->hardwareKeyProgress->setSizePolicy(sp);
|
||||
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
connect(m_deviceListener, &DeviceListener::devicePlugged, this, [this] { pollHardwareKey(false, 500); });
|
||||
connect(YubiKey::instance(), SIGNAL(detectComplete(bool)), SLOT(hardwareKeyResponse(bool)), Qt::QueuedConnection);
|
||||
|
||||
@@ -122,10 +117,6 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
||||
connect(&m_hideNoHardwareKeysFoundTimer, &QTimer::timeout, this, [this] {
|
||||
m_ui->noHardwareKeysFoundLabel->setVisible(false);
|
||||
});
|
||||
#else
|
||||
m_ui->noHardwareKeysFoundLabel->setVisible(false);
|
||||
m_ui->refreshHardwareKeys->setVisible(false);
|
||||
#endif
|
||||
|
||||
// QuickUnlock actions
|
||||
connect(m_ui->quickUnlockButton, &QPushButton::pressed, this, [this] { openDatabase(); });
|
||||
@@ -195,7 +186,6 @@ bool DatabaseOpenWidget::event(QEvent* event)
|
||||
toggleQuickUnlockScreen();
|
||||
|
||||
if (type == QEvent::Show) {
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
#ifdef Q_OS_WIN
|
||||
m_deviceListener->registerHotplugCallback(true,
|
||||
true,
|
||||
@@ -210,7 +200,6 @@ bool DatabaseOpenWidget::event(QEvent* event)
|
||||
#else
|
||||
m_deviceListener->registerHotplugCallback(true, true, YubiKeyInterfaceUSB::YUBICO_USB_VID);
|
||||
m_deviceListener->registerHotplugCallback(true, true, YubiKeyInterfaceUSB::ONLYKEY_USB_VID);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -226,11 +215,9 @@ bool DatabaseOpenWidget::event(QEvent* event)
|
||||
m_hideTimer.start();
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
if (type == QEvent::Hide) {
|
||||
m_deviceListener->deregisterAllHotplugCallbacks();
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = true;
|
||||
}
|
||||
@@ -295,10 +282,8 @@ void DatabaseOpenWidget::load(const QString& filename)
|
||||
|
||||
toggleQuickUnlockScreen();
|
||||
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
// Do initial auto-poll
|
||||
pollHardwareKey();
|
||||
#endif
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::clearForms()
|
||||
@@ -489,7 +474,6 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::buildDatabaseKey()
|
||||
config()->set(Config::LastKeyFiles, lastKeyFiles);
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
auto lastChallengeResponse = config()->get(Config::LastChallengeResponse).toHash();
|
||||
lastChallengeResponse.remove(m_filename);
|
||||
|
||||
@@ -506,7 +490,6 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::buildDatabaseKey()
|
||||
if (config()->get(Config::RememberLastKeyFiles).toBool()) {
|
||||
config()->set(Config::LastChallengeResponse, lastChallengeResponse);
|
||||
}
|
||||
#endif
|
||||
|
||||
return databaseKey;
|
||||
}
|
||||
|
||||
@@ -23,12 +23,9 @@
|
||||
#include <QScopedPointer>
|
||||
#include <QTimer>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "gui/DialogyWidget.h"
|
||||
#include "gui/MessageWidget.h"
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
#include "osutils/DeviceListener.h"
|
||||
#endif
|
||||
|
||||
class CompositeKey;
|
||||
class Database;
|
||||
@@ -87,9 +84,7 @@ private slots:
|
||||
void hardwareKeyResponse(bool found);
|
||||
|
||||
private:
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
QPointer<DeviceListener> m_deviceListener;
|
||||
#endif
|
||||
bool m_pollingHardwareKey = false;
|
||||
bool m_manualHardwareKeyRefresh = false;
|
||||
bool m_blockQuickUnlock = false;
|
||||
|
||||
@@ -576,7 +576,6 @@ void DatabaseTabWidget::showDatabaseSecurity()
|
||||
currentDatabaseWidget()->switchToDatabaseSecurity();
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
void DatabaseTabWidget::showPasskeys()
|
||||
{
|
||||
currentDatabaseWidget()->switchToPasskeys();
|
||||
@@ -596,7 +595,6 @@ void DatabaseTabWidget::removePasskeyFromEntry()
|
||||
{
|
||||
currentDatabaseWidget()->removePasskeyFromEntry();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool DatabaseTabWidget::isModified(int index) const
|
||||
{
|
||||
|
||||
@@ -86,12 +86,10 @@ public slots:
|
||||
void showDatabaseReports(bool state);
|
||||
void showDatabaseSettings(bool state);
|
||||
void showDatabaseSecurity();
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
void showPasskeys();
|
||||
void importPasskey();
|
||||
void importPasskeyToEntry();
|
||||
void removePasskeyFromEntry();
|
||||
#endif
|
||||
void performGlobalAutoType(const QString& search);
|
||||
void performBrowserUnlock();
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
#include "gui/FileDialog.h"
|
||||
#include "gui/GuiTools.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/MergeDialog.h"
|
||||
#include "gui/MessageBox.h"
|
||||
#include "gui/TotpDialog.h"
|
||||
#include "gui/TotpExportSettingsDialog.h"
|
||||
@@ -60,15 +59,15 @@
|
||||
#include "remote/RemoteHandler.h"
|
||||
#include "remote/RemoteSettings.h"
|
||||
|
||||
#ifdef WITH_XC_NETWORKING
|
||||
#ifdef KPXC_FEATURE_NETWORK
|
||||
#include "gui/IconDownloaderDialog.h"
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
#ifdef KPXC_FEATURE_SSHAGENT
|
||||
#include "sshagent/SSHAgent.h"
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
#ifdef KPXC_FEATURE_BROWSER
|
||||
#include "gui/passkeys/PasskeyImporter.h"
|
||||
#endif
|
||||
|
||||
@@ -141,9 +140,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
||||
auto rightHandSideVBox = new QVBoxLayout();
|
||||
rightHandSideVBox->setMargin(0);
|
||||
rightHandSideVBox->addWidget(m_searchingLabel);
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
rightHandSideVBox->addWidget(m_shareLabel);
|
||||
#endif
|
||||
rightHandSideVBox->addWidget(m_previewSplitter);
|
||||
rightHandSideWidget->setLayout(rightHandSideVBox);
|
||||
m_entryView = new EntryView(rightHandSideWidget);
|
||||
@@ -173,12 +170,10 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
||||
m_searchingLabel->setAlignment(Qt::AlignCenter);
|
||||
m_searchingLabel->setVisible(false);
|
||||
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
m_shareLabel->setObjectName("KeeShareBanner");
|
||||
m_shareLabel->setRawText(tr("Shared group…"));
|
||||
m_shareLabel->setAlignment(Qt::AlignCenter);
|
||||
m_shareLabel->setVisible(false);
|
||||
#endif
|
||||
|
||||
m_previewView->setObjectName("previewWidget");
|
||||
m_previewView->hide();
|
||||
@@ -239,11 +234,9 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
||||
|
||||
m_searchLimitGroup = config()->get(Config::SearchLimitGroup).toBool();
|
||||
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
// We need to reregister the database to allow exports
|
||||
// from a newly created database
|
||||
KeeShare::instance()->connectDatabase(m_db, {});
|
||||
#endif
|
||||
|
||||
if (m_db->isInitialized()) {
|
||||
switchToMainView();
|
||||
@@ -508,12 +501,7 @@ void DatabaseWidget::replaceDatabase(QSharedPointer<Database> db)
|
||||
|
||||
emit databaseReplaced(oldDb, m_db);
|
||||
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
KeeShare::instance()->connectDatabase(m_db, oldDb);
|
||||
#else
|
||||
// Keep the instance active till the end of this function
|
||||
Q_UNUSED(oldDb);
|
||||
#endif
|
||||
|
||||
oldDb->releaseData();
|
||||
}
|
||||
@@ -824,7 +812,7 @@ void DatabaseWidget::setClipboardTextAndMinimize(const QString& text)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
#ifdef KPXC_FEATURE_SSHAGENT
|
||||
void DatabaseWidget::addToAgent()
|
||||
{
|
||||
Entry* currentEntry = m_entryView->currentEntry();
|
||||
@@ -878,18 +866,12 @@ void DatabaseWidget::performAutoType(const QString& sequence)
|
||||
{
|
||||
auto currentEntry = currentSelectedEntry();
|
||||
if (currentEntry) {
|
||||
// Check if we need to ask for confirmation
|
||||
bool shouldAsk = config()->get(Config::Security_AutoTypeAsk).toBool();
|
||||
bool skipMainWindowConfirmation = config()->get(Config::Security_AutoTypeSkipMainWindowConfirmation).toBool();
|
||||
|
||||
// Show confirmation if Security_AutoTypeAsk is true AND Security_AutoTypeSkipMainWindowConfirmation is false
|
||||
if (shouldAsk && !skipMainWindowConfirmation) {
|
||||
// TODO: Include name of previously active window in confirmation question
|
||||
if (MessageBox::question(
|
||||
this, tr("Confirm Auto-Type"), tr("Perform Auto-Type into the previously active window?"))
|
||||
!= MessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
// TODO: Include name of previously active window in confirmation question
|
||||
if (config()->get(Config::Security_AutoTypeAsk).toBool()
|
||||
&& MessageBox::question(
|
||||
this, tr("Confirm Auto-Type"), tr("Perform Auto-Type into the previously active window?"))
|
||||
!= MessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sequence.isEmpty()) {
|
||||
@@ -925,16 +907,6 @@ void DatabaseWidget::performAutoTypeTOTP()
|
||||
performAutoType(QStringLiteral("{TOTP}"));
|
||||
}
|
||||
|
||||
void DatabaseWidget::performAutoTypeURL()
|
||||
{
|
||||
performAutoType(QStringLiteral("{URL}"));
|
||||
}
|
||||
|
||||
void DatabaseWidget::performAutoTypeURLEnter()
|
||||
{
|
||||
performAutoType(QStringLiteral("{URL}{ENTER}"));
|
||||
}
|
||||
|
||||
void DatabaseWidget::openUrl()
|
||||
{
|
||||
auto currentEntry = currentSelectedEntry();
|
||||
@@ -945,7 +917,7 @@ void DatabaseWidget::openUrl()
|
||||
|
||||
void DatabaseWidget::downloadSelectedFavicons()
|
||||
{
|
||||
#ifdef WITH_XC_NETWORKING
|
||||
#ifdef KPXC_FEATURE_NETWORK
|
||||
QList<Entry*> selectedEntries;
|
||||
for (const auto& index : m_entryView->selectionModel()->selectedRows()) {
|
||||
selectedEntries.append(m_entryView->entryFromIndex(index));
|
||||
@@ -958,7 +930,7 @@ void DatabaseWidget::downloadSelectedFavicons()
|
||||
|
||||
void DatabaseWidget::downloadAllFavicons()
|
||||
{
|
||||
#ifdef WITH_XC_NETWORKING
|
||||
#ifdef KPXC_FEATURE_NETWORK
|
||||
auto currentGroup = m_groupView->currentGroup();
|
||||
if (currentGroup) {
|
||||
performIconDownloads(currentGroup->entries());
|
||||
@@ -968,7 +940,7 @@ void DatabaseWidget::downloadAllFavicons()
|
||||
|
||||
void DatabaseWidget::downloadFaviconInBackground(Entry* entry)
|
||||
{
|
||||
#ifdef WITH_XC_NETWORKING
|
||||
#ifdef KPXC_FEATURE_NETWORK
|
||||
performIconDownloads({entry}, true, true);
|
||||
#else
|
||||
Q_UNUSED(entry);
|
||||
@@ -977,7 +949,7 @@ void DatabaseWidget::downloadFaviconInBackground(Entry* entry)
|
||||
|
||||
void DatabaseWidget::performIconDownloads(const QList<Entry*>& entries, bool force, bool downloadInBackground)
|
||||
{
|
||||
#ifdef WITH_XC_NETWORKING
|
||||
#ifdef KPXC_FEATURE_NETWORK
|
||||
auto* iconDownloaderDialog = new IconDownloaderDialog(this);
|
||||
connect(this, SIGNAL(databaseLockRequested()), iconDownloaderDialog, SLOT(close()));
|
||||
|
||||
@@ -1121,8 +1093,8 @@ void DatabaseWidget::deleteGroup()
|
||||
if (inRecycleBin || isRecycleBin || isRecycleBinSubgroup || !m_db->metadata()->recycleBinEnabled()) {
|
||||
auto result = MessageBox::question(
|
||||
this,
|
||||
tr("Confirm Delete Group"),
|
||||
tr("Do you really want to permanently delete the group \"%1\"?").arg(currentGroup->name().toHtmlEscaped()),
|
||||
tr("Delete group"),
|
||||
tr("Do you really want to delete the group \"%1\" for good?").arg(currentGroup->name().toHtmlEscaped()),
|
||||
MessageBox::Delete | MessageBox::Cancel,
|
||||
MessageBox::Cancel);
|
||||
|
||||
@@ -1131,7 +1103,7 @@ void DatabaseWidget::deleteGroup()
|
||||
}
|
||||
} else {
|
||||
auto result = MessageBox::question(this,
|
||||
tr("Confirm Recycle Group"),
|
||||
tr("Move group to recycle bin?"),
|
||||
tr("Do you really want to move the group "
|
||||
"\"%1\" to the recycle bin?")
|
||||
.arg(currentGroup->name().toHtmlEscaped()),
|
||||
@@ -1353,7 +1325,7 @@ void DatabaseWidget::loadDatabase(bool accepted)
|
||||
m_entryBeforeLock = QUuid();
|
||||
m_saveAttempts = 0;
|
||||
emit databaseUnlocked();
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
#ifdef KPXC_FEATURE_SSHAGENT
|
||||
sshAgent()->databaseUnlocked(m_db);
|
||||
#endif
|
||||
if (config()->get(Config::MinimizeAfterUnlock).toBool()) {
|
||||
@@ -1388,30 +1360,18 @@ void DatabaseWidget::mergeDatabase(bool accepted)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
// Disable KeeShare while merging to avoid conflicts with incoming changes
|
||||
KeeShare::instance()->setSharingEnabled(m_db, false);
|
||||
#endif
|
||||
Merger merger(srcDb.data(), m_db.data());
|
||||
QStringList changeList = merger.merge();
|
||||
|
||||
auto* mergeDialog = new MergeDialog(srcDb, m_db, this);
|
||||
connect(mergeDialog, &MergeDialog::databaseMerged, [this](bool changed) {
|
||||
if (changed) {
|
||||
showMessage(tr("Successfully merged the selected database."), MessageWidget::Positive);
|
||||
emit databaseMerged(m_db);
|
||||
} else {
|
||||
showMessage(tr("No changes were made by the merge operation."), MessageWidget::Information);
|
||||
}
|
||||
});
|
||||
connect(mergeDialog, &MergeDialog::finished, [this](int result) {
|
||||
if (result == QDialog::Rejected) {
|
||||
showMessage(tr("Merge canceled, no changes were made."), MessageWidget::Information);
|
||||
}
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
KeeShare::instance()->setSharingEnabled(m_db, true);
|
||||
#endif
|
||||
});
|
||||
mergeDialog->open();
|
||||
if (!changeList.isEmpty()) {
|
||||
showMessage(tr("Successfully merged the database files."), MessageWidget::Information);
|
||||
} else {
|
||||
showMessage(tr("Database was not modified by merge operation."), MessageWidget::Information);
|
||||
}
|
||||
}
|
||||
|
||||
switchToMainView();
|
||||
emit databaseMerged(m_db);
|
||||
}
|
||||
|
||||
void DatabaseWidget::syncUnlockedDatabase(bool accepted)
|
||||
@@ -1451,7 +1411,7 @@ bool DatabaseWidget::syncWithDatabase(const QSharedPointer<Database>& otherDb, Q
|
||||
emit updateSyncProgress(50, tr("Syncing..."));
|
||||
Merger firstMerge(m_db.data(), otherDb.data());
|
||||
Merger secondMerge(otherDb.data(), m_db.data());
|
||||
auto changeList = firstMerge.merge() + secondMerge.merge();
|
||||
QStringList changeList = firstMerge.merge() + secondMerge.merge();
|
||||
|
||||
if (!changeList.isEmpty()) {
|
||||
// Save synced databases
|
||||
@@ -1516,7 +1476,7 @@ void DatabaseWidget::unlockDatabase(bool accepted)
|
||||
processAutoOpen();
|
||||
emit databaseUnlocked();
|
||||
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
#ifdef KPXC_FEATURE_SSHAGENT
|
||||
sshAgent()->databaseUnlocked(m_db);
|
||||
#endif
|
||||
|
||||
@@ -1556,7 +1516,7 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod
|
||||
}
|
||||
break;
|
||||
case EntryModel::Totp:
|
||||
if (entry->hasValidTotp()) {
|
||||
if (entry->hasTotp()) {
|
||||
setClipboardTextAndMinimize(entry->totp());
|
||||
} else {
|
||||
setupTotp();
|
||||
@@ -1664,7 +1624,7 @@ void DatabaseWidget::switchToRemoteSettings()
|
||||
m_databaseSettingDialog->showRemoteSettings();
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
#ifdef KPXC_FEATURE_BROWSER
|
||||
void DatabaseWidget::switchToPasskeys()
|
||||
{
|
||||
switchToDatabaseReports();
|
||||
@@ -1761,9 +1721,7 @@ void DatabaseWidget::search(const QString& searchtext)
|
||||
m_lastSearchText = searchtext;
|
||||
|
||||
m_searchingLabel->setVisible(true);
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
m_shareLabel->setVisible(false);
|
||||
#endif
|
||||
|
||||
emit searchModeActivated();
|
||||
}
|
||||
@@ -1828,7 +1786,6 @@ void DatabaseWidget::onGroupChanged()
|
||||
|
||||
m_previewView->setGroup(group);
|
||||
|
||||
#ifdef WITH_XC_KEESHARE
|
||||
auto shareLabel = KeeShare::sharingLabel(group);
|
||||
if (!shareLabel.isEmpty()) {
|
||||
m_shareLabel->setRawText(shareLabel);
|
||||
@@ -1836,7 +1793,6 @@ void DatabaseWidget::onGroupChanged()
|
||||
} else {
|
||||
m_shareLabel->setVisible(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
emit groupChanged();
|
||||
}
|
||||
@@ -2120,7 +2076,7 @@ bool DatabaseWidget::lock()
|
||||
m_entryBeforeLock = currentEntry->uuid();
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
#ifdef KPXC_FEATURE_SSHAGENT
|
||||
sshAgent()->databaseLocked(m_db);
|
||||
#endif
|
||||
|
||||
@@ -2415,10 +2371,10 @@ bool DatabaseWidget::currentEntryHasTotp()
|
||||
if (!currentEntry) {
|
||||
return false;
|
||||
}
|
||||
return currentEntry->hasValidTotp();
|
||||
return currentEntry->hasTotp();
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
#ifdef KPXC_FEATURE_SSHAGENT
|
||||
bool DatabaseWidget::currentEntryHasSshKey()
|
||||
{
|
||||
Entry* currentEntry = m_entryView->currentEntry();
|
||||
@@ -2431,7 +2387,7 @@ bool DatabaseWidget::currentEntryHasSshKey()
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_BROWSER_PASSKEYS
|
||||
#ifdef KPXC_FEATURE_BROWSER
|
||||
bool DatabaseWidget::currentEntryHasPasskey()
|
||||
{
|
||||
auto currentEntry = m_entryView->currentEntry();
|
||||
@@ -2653,7 +2609,7 @@ bool DatabaseWidget::saveBackup()
|
||||
}
|
||||
|
||||
const QString newFilePath = fileDialog()->getSaveFileName(this,
|
||||
tr("Save Database Backup"),
|
||||
tr("Save database backup"),
|
||||
FileDialog::getLastDir("backup", oldFilePath),
|
||||
tr("KeePass 2 Database").append(" (*.kdbx)"));
|
||||
|
||||
@@ -2713,22 +2669,6 @@ bool DatabaseWidget::isRecycleBinSelected() const
|
||||
return (group && group->isRecycled()) || (entry && entry->isRecycled());
|
||||
}
|
||||
|
||||
bool DatabaseWidget::hasRecycledSelectedEntries() const
|
||||
{
|
||||
if (!m_entryView) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if any of the selected entries are actually recycled
|
||||
for (auto* entry : m_entryView->selectedEntries()) {
|
||||
if (entry && entry->isRecycled()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DatabaseWidget::emptyRecycleBin()
|
||||
{
|
||||
if (!isRecycleBinSelected()) {
|
||||
|
||||