Release 2.3.0
- Add support for KDBX 4.0, Argon2 and ChaCha20 [#148, #1179, #1230, #1494] - Add SSH Agent feature [#1098, #1450, #1463] - Add preview panel with details of the selected entry [#879, #1338] - Add more and configurable columns to entry table and allow copying of values by double click [#1305] - Add KeePassXC-Browser API as a replacement for KeePassHTTP [#608] - Deprecate KeePassHTTP [#1392] - Add support for Steam one-time passwords [#1206] - Add support for multiple Auto-Type sequences for a single entry [#1390] - Adjust YubiKey HMAC-SHA1 challenge-response key generation for KDBX 4.0 [#1060] - Replace qHttp with cURL for website icon downloads [#1460] - Remove lock file [#1231] - Add option to create backup file before saving [#1385] - Ask to save a generated password before closing the entry password generator [#1499] - Resolve placeholders recursively [#1078] - Add Auto-Type button to the toolbar [#1056] - Improve window focus handling for Auto-Type dialogs [#1204, #1490] - Auto-Type dialog and password generator can now be exited with ESC [#1252, #1412] - Add optional dark tray icon [#1154] - Add new "Unsafe saving" option to work around saving problems with file sync services [#1385] - Add IBus support to AppImage and additional image formats to Windows builds [#1534, #1537] - Add diceware password generator to CLI [#1406] - Add --key-file option to CLI [#816, #824] - Add DBus interface for opening and closing KeePassXC databases [#283] - Add KDBX compression options to database settings [#1419] - Discourage use of old fixed-length key files in favor of arbitrary files [#1326, #1327] - Correct reference resolution in entry fields [#1486] - Fix window state and recent databases not being remembered on exit [#1453] - Correct history item generation when configuring TOTP for an entry [#1446] - Correct multiple TOTP bugs [#1414] - Automatic saving after every change is now a default [#279] - Allow creation of new entries during search [#1398] - Correct menu issues on macOS [#1335] - Allow compilation on OpenBSD [#1328] - Improve entry attachments view [#1139, #1298] - Fix auto lock for Gnome and Xfce [#910, #1249] - Don't remember key files in file dialogs when the setting is disabled [#1188] - Improve database merging and conflict resolution [#807, #1165] - Fix macOS pasteboard issues [#1202] - Improve startup times on some platforms [#1205] - Hide the notes field by default [#1124] - Toggle main window by clicking tray icon with the middle mouse button [#992] - Fix custom icons not copied over when databases are merged [#1008] - Allow use of DEL key to delete entries [#914] - Correct intermittent crash due to stale history items [#1527] - Sanitize newline characters in title, username and URL fields [#1502] - Reopen previously opened databases in correct order [#774] - Use system's zxcvbn library if available [#701] - Implement various i18n improvements [#690, #875, #1436]
@ -23,11 +23,10 @@ BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterFunction: true
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterEnum: true
|
||||
AfterNamespace: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
|
17
.github/CONTRIBUTING.md
vendored
@ -53,7 +53,7 @@ If we reject your contribution, it means only that we do not consider it suitabl
|
||||
## How can I contribute?
|
||||
### Feature requests
|
||||
|
||||
We're always looking for suggestions to improve our application. If you have a suggestion to improve an existing feature, or would like to suggest a completely new feature for KeePassXC, please use the [issue tracker on GitHub][issues-section]. For more general discussion, try using our [Google Groups][google-groups] forum.
|
||||
We're always looking for suggestions to improve our application. If you have a suggestion to improve an existing feature, or would like to suggest a completely new feature for KeePassXC, please use the [issue tracker on GitHub][issues-section].
|
||||
|
||||
### Bug reports
|
||||
|
||||
@ -63,7 +63,7 @@ Before submitting a bug report, check if the problem has already been reported.
|
||||
|
||||
### Discuss with the team
|
||||
|
||||
As with feature requests, you can talk to the KeePassXC team about bugs, new features, other issues and pull requests on the dedicated issue tracker, using the [Google Groups][google-groups] forum, or in the IRC channel on Freenode (`#keepassxc-dev` on `irc.freenode.net`, or use a [webchat link](https://webchat.freenode.net/?channels=%23keepassxc-dev)).
|
||||
As with feature requests, you can talk to the KeePassXC team about bugs, new features, other issues and pull requests on the dedicated issue tracker, or in the IRC channel on Freenode (`#keepassxc-dev` on `irc.freenode.net`, or use a [webchat link](https://webchat.freenode.net/?channels=%23keepassxc-dev)).
|
||||
|
||||
### Your first code contribution
|
||||
|
||||
@ -103,18 +103,8 @@ The Branch Strategy is based on [git-flow-lite](http://nvie.com/posts/a-successf
|
||||
* Use the imperative mood ("Move cursor to…" not "Moves cursor to…")
|
||||
* Limit the first line to 72 characters or less
|
||||
* Reference issues and pull requests liberally
|
||||
* If your pull request fixes an existing issue, add "…, resolves #ISSUENUMBER" to your main commit
|
||||
* When only changing documentation, include `[ci skip]` in the commit description
|
||||
* Consider starting the commit message with an applicable emoji:
|
||||
* :memo: `:memo:` when writing docs
|
||||
* :penguin: `:penguin:` when fixing something on Linux
|
||||
* :apple: `:apple:` when fixing something on macOS
|
||||
* :checkered_flag: `:checkered_flag:` when fixing something on Windows
|
||||
* :bug: `:bug:` when fixing a bug
|
||||
* :fire: `:fire:` when removing code or files
|
||||
* :green_heart: `:green_heart:` when fixing the CI build
|
||||
* :white_check_mark: `:white_check_mark:` when adding tests
|
||||
* :lock: `:lock:` when dealing with security
|
||||
|
||||
|
||||
### Coding styleguide
|
||||
|
||||
@ -177,4 +167,3 @@ Example: `<widget class="QCheckBox" name="rememberCheckBox">`
|
||||
[beginner]:https://github.com/keepassxreboot/keepassx/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner+label%3A%22help+wanted%22+sort%3Acomments-desc
|
||||
[help-wanted]:https://github.com/keepassxreboot/keepassx/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+sort%3Acomments-desc
|
||||
[issues-section]:https://github.com/keepassxreboot/keepassxc/issues
|
||||
[google-groups]:https://groups.google.com/forum/#!forum/keepassx-reboot
|
||||
|
20
.github/ISSUE_TEMPLATE.md
vendored
@ -24,9 +24,17 @@
|
||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||
|
||||
## Your Environment
|
||||
<!--- Include relevant details about the environment you experienced the bug in -->
|
||||
* KeePassXC version/commit used: (can be found under Help -> About)
|
||||
* Qt version (e.g. Qt 5.3):
|
||||
* Compiler (e.g. Clang++3.6.0):
|
||||
* Operating System and version:
|
||||
## Debug Info
|
||||
<!--- Paste debug info from Help → About here -->
|
||||
KeePassXC - VERSION
|
||||
Revision: REVISION
|
||||
|
||||
Libraries:
|
||||
- LIBS
|
||||
|
||||
Operating system: OS
|
||||
CPU architecture: ARCH
|
||||
Kernel: KERNEL
|
||||
|
||||
Enabled extensions:
|
||||
- EXTENSIONS
|
||||
|
4
.gitignore
vendored
@ -7,3 +7,7 @@ release*/
|
||||
*.kdev4
|
||||
|
||||
\.vscode/
|
||||
*.swp
|
||||
|
||||
.DS_Store
|
||||
.version
|
47
.travis.yml
@ -1,47 +0,0 @@
|
||||
language: cpp
|
||||
sudo: required
|
||||
dist: trusty
|
||||
# FIXME : remove when (https://github.com/google/sanitizers/issues/837) is resolved.
|
||||
group: deprecated-2017Q3
|
||||
services: [docker]
|
||||
|
||||
os:
|
||||
- linux
|
||||
# - osx
|
||||
|
||||
# Define clang compiler without any frills
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
|
||||
env:
|
||||
- CONFIG=Release ASAN_OPTIONS=detect_odr_violation=1
|
||||
- CONFIG=Debug ASAN_OPTIONS=detect_odr_violation=1
|
||||
|
||||
git:
|
||||
depth: 3
|
||||
|
||||
before_install:
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq update; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install cmake libclang-common-3.5-dev libxi-dev qtbase5-dev libqt5x11extras5-dev qttools5-dev qttools5-dev-tools libgcrypt20-dev zlib1g-dev libxtst-dev xvfb libyubikey-dev libykpers-1-dev; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq cmake || brew install cmake; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq qt5 || brew install qt5; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew ls | grep -wq libgcrypt || brew install libgcrypt; fi
|
||||
|
||||
before_script:
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then CMAKE_ARGS="-DCMAKE_PREFIX_PATH=/usr/local/opt/qt5"; fi
|
||||
- mkdir build && pushd build
|
||||
|
||||
script:
|
||||
- cmake -DCMAKE_BUILD_TYPE=${CONFIG} -DWITH_GUI_TESTS=ON -DWITH_ASAN=ON -DWITH_XC_HTTP=ON -DWITH_XC_AUTOTYPE=ON -DWITH_XC_YUBIKEY=ON $CMAKE_ARGS ..
|
||||
- make -j2
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui --output-on-failure"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then ASAN_OPTIONS=${ASAN_OPTIONS}:leak_check_at_exit=0 xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui --output-on-failure"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test ARGS+="--output-on-failure"; fi
|
||||
|
||||
# Generate snapcraft build when merging into master/develop branches
|
||||
#after_success:
|
||||
# - popd
|
||||
# - "[[ $DEPLOY = 1 ]] && [[ $CONFIG = Release ]] && [[ $TRAVIS_BRANCH =~ (master|develop) ]] && [[ $TRAVIS_PULL_REQUEST = false ]] \
|
||||
# && docker run -v $(pwd):/cwd snapcore/snapcraft sh -c 'cd /cwd && apt update && snapcraft'"
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# KeePassXC AppImage Recipe
|
||||
# Copyright (C) 2017 KeePassXC team <https://keepassxc.org/>
|
||||
# Copyright (C) 2017-2018 KeePassXC team <https://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
|
||||
@ -37,12 +37,14 @@ VERSION="$2"
|
||||
export ARCH=x86_64
|
||||
|
||||
mkdir -p $APP.AppDir
|
||||
wget -q https://github.com/probonopd/AppImages/raw/master/functions.sh -O ./functions.sh
|
||||
wget -q https://github.com/AppImage/AppImages/raw/master/functions.sh -O ./functions.sh
|
||||
. ./functions.sh
|
||||
|
||||
LIB_DIR=./usr/lib
|
||||
if [ -d ./usr/lib/x86_64-linux-gnu ]; then
|
||||
LIB_DIR=./usr/lib/x86_64-linux-gnu
|
||||
elif [ -d ./usr/lib/i386-linux-gnu ]; then
|
||||
LIB_DIR=./usr/lib/i386-linux-gnu
|
||||
elif [ -d ./usr/lib64 ]; then
|
||||
LIB_DIR=./usr/lib64
|
||||
fi
|
||||
@ -60,17 +62,25 @@ if [ "$QXCB_PLUGIN" == "" ]; then
|
||||
fi
|
||||
QT_PLUGIN_PATH="$(dirname $(dirname $QXCB_PLUGIN))"
|
||||
mkdir -p ".${QT_PLUGIN_PATH}/platforms"
|
||||
cp "$QXCB_PLUGIN" ".${QT_PLUGIN_PATH}/platforms/"
|
||||
cp -a "$QXCB_PLUGIN" ".${QT_PLUGIN_PATH}/platforms/"
|
||||
cp -a "${QT_PLUGIN_PATH}/platforminputcontexts/" ".${QT_PLUGIN_PATH}/platforminputcontexts/"
|
||||
cp -a "${QT_PLUGIN_PATH}/imageformats/" ".${QT_PLUGIN_PATH}/imageformats/"
|
||||
|
||||
get_apprun
|
||||
copy_deps
|
||||
|
||||
# protect our libgpg-error from being deleted
|
||||
mv ./opt/keepassxc-libs/lib/x86_64-linux-gnu/libgpg-error.so.0 ./protected.so
|
||||
delete_blacklisted
|
||||
mv ./protected.so ./opt/keepassxc-libs/lib/x86_64-linux-gnu/libgpg-error.so.0
|
||||
|
||||
get_desktop
|
||||
get_icon
|
||||
cat << EOF > ./usr/bin/keepassxc_env
|
||||
#!/usr/bin/env bash
|
||||
export LD_LIBRARY_PATH="..$(dirname ${QT_PLUGIN_PATH})/lib:\${LD_LIBRARY_PATH}"
|
||||
export LD_LIBRARY_PATH="../opt/keepassxc-libs/lib/x86_64-linux-gnu:\${LD_LIBRARY_PATH}"
|
||||
|
||||
export QT_PLUGIN_PATH="..${QT_PLUGIN_PATH}:\${KPXC_QT_PLUGIN_PATH}"
|
||||
|
||||
# unset XDG_DATA_DIRS to make tray icon work in Ubuntu Unity
|
||||
@ -80,6 +90,11 @@ unset XDG_DATA_DIRS
|
||||
if [ "\${1}" == "cli" ]; then
|
||||
shift
|
||||
exec keepassxc-cli "\$@"
|
||||
elif [ "\${1}" == "proxy" ]; then
|
||||
shift
|
||||
exec keepassxc-proxy "\$@"
|
||||
elif [ -v CHROME_WRAPPER ] || [ -v MOZ_LAUNCHED_CHILD ]; then
|
||||
exec keepassxc-proxy "\$@"
|
||||
else
|
||||
exec keepassxc "\$@"
|
||||
fi
|
||||
|
52
CHANGELOG
@ -1,3 +1,55 @@
|
||||
2.3.0 (2018-02-27)
|
||||
=========================
|
||||
|
||||
- Add support for KDBX 4.0, Argon2 and ChaCha20 [#148, #1179, #1230, #1494]
|
||||
- Add SSH Agent feature [#1098, #1450, #1463]
|
||||
- Add preview panel with details of the selected entry [#879, #1338]
|
||||
- Add more and configurable columns to entry table and allow copying of values by double click [#1305]
|
||||
- Add KeePassXC-Browser API as a replacement for KeePassHTTP [#608]
|
||||
- Deprecate KeePassHTTP [#1392]
|
||||
- Add support for Steam one-time passwords [#1206]
|
||||
- Add support for multiple Auto-Type sequences for a single entry [#1390]
|
||||
- Adjust YubiKey HMAC-SHA1 challenge-response key generation for KDBX 4.0 [#1060]
|
||||
- Replace qHttp with cURL for website icon downloads [#1460]
|
||||
- Remove lock file [#1231]
|
||||
- Add option to create backup file before saving [#1385]
|
||||
- Ask to save a generated password before closing the entry password generator [#1499]
|
||||
- Resolve placeholders recursively [#1078]
|
||||
- Add Auto-Type button to the toolbar [#1056]
|
||||
- Improve window focus handling for Auto-Type dialogs [#1204, #1490]
|
||||
- Auto-Type dialog and password generator can now be exited with ESC [#1252, #1412]
|
||||
- Add optional dark tray icon [#1154]
|
||||
- Add new "Unsafe saving" option to work around saving problems with file sync services [#1385]
|
||||
- Add IBus support to AppImage and additional image formats to Windows builds [#1534, #1537]
|
||||
- Add diceware password generator to CLI [#1406]
|
||||
- Add --key-file option to CLI [#816, #824]
|
||||
- Add DBus interface for opening and closing KeePassXC databases [#283]
|
||||
- Add KDBX compression options to database settings [#1419]
|
||||
- Discourage use of old fixed-length key files in favor of arbitrary files [#1326, #1327]
|
||||
- Correct reference resolution in entry fields [#1486]
|
||||
- Fix window state and recent databases not being remembered on exit [#1453]
|
||||
- Correct history item generation when configuring TOTP for an entry [#1446]
|
||||
- Correct multiple TOTP bugs [#1414]
|
||||
- Automatic saving after every change is now a default [#279]
|
||||
- Allow creation of new entries during search [#1398]
|
||||
- Correct menu issues on macOS [#1335]
|
||||
- Allow compilation on OpenBSD [#1328]
|
||||
- Improve entry attachments view [#1139, #1298]
|
||||
- Fix auto lock for Gnome and Xfce [#910, #1249]
|
||||
- Don't remember key files in file dialogs when the setting is disabled [#1188]
|
||||
- Improve database merging and conflict resolution [#807, #1165]
|
||||
- Fix macOS pasteboard issues [#1202]
|
||||
- Improve startup times on some platforms [#1205]
|
||||
- Hide the notes field by default [#1124]
|
||||
- Toggle main window by clicking tray icon with the middle mouse button [#992]
|
||||
- Fix custom icons not copied over when databases are merged [#1008]
|
||||
- Allow use of DEL key to delete entries [#914]
|
||||
- Correct intermittent crash due to stale history items [#1527]
|
||||
- Sanitize newline characters in title, username and URL fields [#1502]
|
||||
- Reopen previously opened databases in correct order [#774]
|
||||
- Use system's zxcvbn library if available [#701]
|
||||
- Implement various i18n improvements [#690, #875, #1436]
|
||||
|
||||
2.2.4 (2017-12-13)
|
||||
=========================
|
||||
|
||||
|
166
CMakeLists.txt
@ -1,5 +1,5 @@
|
||||
# Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
# Copyright (C) 2017 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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -14,16 +14,16 @@
|
||||
# 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.1.0)
|
||||
|
||||
project(KeePassXC)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
|
||||
"Choose the type of build, options are: None Debug Release RelWithDebInfo Debug DebugFull Profile MinSizeRel."
|
||||
FORCE)
|
||||
endif()
|
||||
|
||||
project(KeePassXC)
|
||||
|
||||
cmake_minimum_required(VERSION 3.1.0)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
||||
|
||||
# Support Visual Studio Code
|
||||
@ -36,30 +36,89 @@ 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 only)" 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 OS X" ON)
|
||||
option(WITH_APP_BUNDLE "Enable Application Bundle for macOS" ON)
|
||||
|
||||
set(WITH_XC_ALL OFF CACHE BOOLEAN "Build in all available plugins")
|
||||
|
||||
option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
|
||||
option(WITH_XC_HTTP "Include KeePassHTTP and Custom Icon Downloads." OFF)
|
||||
option(WITH_XC_NETWORKING "Include networking code (e.g. for downlading website icons)." OFF)
|
||||
option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF)
|
||||
option(WITH_XC_HTTP "Include KeePassHTTP-compatible browser integration (deprecated, implies WITH_NETWORKING)." OFF)
|
||||
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
|
||||
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
|
||||
|
||||
if(WITH_XC_HTTP)
|
||||
message(WARNING "KeePassHTTP support has been deprecated and will be removed in a future version. Please use WITH_XC_BROWSER instead!\n"
|
||||
"For enabling / disabling network access code, WITH_XC_HTTP has been replaced by WITH_XC_NETWORKING.")
|
||||
set(WITH_XC_NETWORKING ON CACHE BOOL "Include networking code (e.g. for downlading website icons)." FORCE)
|
||||
endif()
|
||||
|
||||
if(WITH_XC_ALL)
|
||||
# Enable all options
|
||||
set(WITH_XC_AUTOTYPE ON)
|
||||
set(WITH_XC_NETWORKING ON)
|
||||
set(WITH_XC_BROWSER ON)
|
||||
set(WITH_XC_HTTP ON) # Deprecated
|
||||
set(WITH_XC_YUBIKEY ON)
|
||||
set(WITH_XC_SSHAGENT ON)
|
||||
endif()
|
||||
|
||||
# Process ui files automatically from source files
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
set(KEEPASSXC_VERSION_MAJOR "2")
|
||||
set(KEEPASSXC_VERSION_MINOR "2")
|
||||
set(KEEPASSXC_VERSION_PATCH "4")
|
||||
set(KEEPASSXC_VERSION_MINOR "3")
|
||||
set(KEEPASSXC_VERSION_PATCH "0")
|
||||
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
|
||||
|
||||
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)
|
||||
|
||||
# Check if on a tag, if so build as a release
|
||||
execute_process(COMMAND git tag --points-at HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_TAG)
|
||||
if(NOT GIT_TAG AND EXISTS ${CMAKE_SOURCE_DIR}/.version)
|
||||
file(READ ${CMAKE_SOURCE_DIR}/.version OVERRIDE_VERSION)
|
||||
endif()
|
||||
|
||||
string(REGEX REPLACE "(\r?\n)+" "" OVERRIDE_VERSION "${OVERRIDE_VERSION}")
|
||||
if(OVERRIDE_VERSION)
|
||||
if(OVERRIDE_VERSION MATCHES "^[\\.0-9]+-(alpha|beta)[0-9]+$")
|
||||
set(KEEPASSXC_BUILD_TYPE PreRelease)
|
||||
set(KEEPASSXC_VERSION ${OVERRIDE_VERSION})
|
||||
elseif(OVERRIDE_VERSION MATCHES "^[\\.0-9]+$")
|
||||
set(KEEPASSXC_BUILD_TYPE Release)
|
||||
set(KEEPASSXC_VERSION ${OVERRIDE_VERSION})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(KEEPASSXC_BUILD_TYPE STREQUAL "PreRelease" AND NOT OVERRIDE_VERSION)
|
||||
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-preview")
|
||||
elseif(KEEPASSXC_BUILD_TYPE STREQUAL "Snapshot")
|
||||
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION}-snapshot")
|
||||
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)
|
||||
endif()
|
||||
|
||||
message(STATUS "Setting up build for KeePassXC v${KEEPASSXC_VERSION}\n")
|
||||
|
||||
# Distribution info
|
||||
set(KEEPASSXC_DIST True)
|
||||
set(KEEPASSXC_DIST_TYPE "Other" CACHE STRING "KeePassXC Distribution type")
|
||||
set(KEEPASSXC_DIST ON)
|
||||
set(KEEPASSXC_DIST_TYPE "Other" CACHE STRING "KeePassXC Distribution Type")
|
||||
set_property(CACHE KEEPASSXC_DIST_TYPE PROPERTY STRINGS Snap AppImage Other)
|
||||
if(KEEPASSXC_DIST_TYPE STREQUAL "Snap")
|
||||
set(KEEPASSXC_DIST_SNAP True)
|
||||
set(KEEPASSXC_DIST_SNAP ON)
|
||||
elseif(KEEPASSXC_DIST_TYPE STREQUAL "AppImage")
|
||||
set(KEEPASSXC_DIST_APPIMAGE True)
|
||||
set(KEEPASSXC_DIST_APPIMAGE ON)
|
||||
elseif(KEEPASSXC_DIST_TYPE STREQUAL "Other")
|
||||
unset(KEEPASSXC_DIST)
|
||||
endif()
|
||||
@ -96,7 +155,7 @@ if(WITH_APP_BUNDLE)
|
||||
endif()
|
||||
|
||||
add_gcc_compiler_flags("-fno-common")
|
||||
add_gcc_compiler_flags("-Wall -Wextra -Wundef -Wpointer-arith -Wno-long-long")
|
||||
add_gcc_compiler_flags("-Wall -Werror -Wextra -Wundef -Wpointer-arith -Wno-long-long")
|
||||
add_gcc_compiler_flags("-Wformat=2 -Wmissing-format-attribute")
|
||||
add_gcc_compiler_flags("-fvisibility=hidden")
|
||||
add_gcc_compiler_cxxflags("-fvisibility-inlines-hidden")
|
||||
@ -110,21 +169,25 @@ endif()
|
||||
add_gcc_compiler_cxxflags("-fno-exceptions -fno-rtti")
|
||||
add_gcc_compiler_cxxflags("-Wnon-virtual-dtor -Wold-style-cast -Woverloaded-virtual")
|
||||
add_gcc_compiler_cflags("-Wchar-subscripts -Wwrite-strings")
|
||||
|
||||
if(WITH_ASAN)
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
message(FATAL_ERROR "WITH_ASAN is only supported on Linux at the moment.")
|
||||
if(NOT (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR APPLE))
|
||||
message(FATAL_ERROR "WITH_ASAN is only supported on Linux / macOS at the moment.")
|
||||
endif()
|
||||
|
||||
add_gcc_compiler_flags("-fsanitize=address -DWITH_ASAN")
|
||||
|
||||
if(NOT (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9))
|
||||
add_gcc_compiler_flags("-fsanitize=leak -DWITH_LSAN")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
if(NOT (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9))
|
||||
add_gcc_compiler_flags("-fsanitize=leak -DWITH_LSAN")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
|
||||
if (CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)")
|
||||
add_gcc_compiler_flags("-D_FORTIFY_SOURCE=2")
|
||||
add_gcc_compiler_flags("-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2")
|
||||
endif()
|
||||
|
||||
check_c_compiler_flag("-Werror=format-security -Werror=implicit-function-declaration" WERROR_C_AVAILABLE)
|
||||
@ -159,14 +222,13 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro,-z,now")
|
||||
endif()
|
||||
|
||||
add_gcc_compiler_cflags("-std=c99")
|
||||
add_gcc_compiler_cxxflags("-std=c++11")
|
||||
|
||||
if(APPLE)
|
||||
add_gcc_compiler_cxxflags("-stdlib=libc++")
|
||||
endif()
|
||||
|
||||
add_gcc_compiler_cflags("-ansi")
|
||||
|
||||
if(WITH_DEV_BUILD)
|
||||
add_definitions(-DQT_DEPRECATED_WARNINGS -DGCRYPT_NO_DEPRECATED)
|
||||
endif()
|
||||
@ -190,15 +252,18 @@ endif()
|
||||
|
||||
if(APPLE AND WITH_APP_BUNDLE AND "${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local")
|
||||
set(CMAKE_INSTALL_PREFIX "/Applications")
|
||||
set(CMAKE_INSTALL_MANDIR "/usr/local/share/man")
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
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(CLI_INSTALL_DIR "/usr/local/bin")
|
||||
set(PROXY_INSTALL_DIR "/usr/local/bin")
|
||||
set(BIN_INSTALL_DIR ".")
|
||||
set(PLUGIN_INSTALL_DIR "${PROGNAME}.app/Contents/PlugIns")
|
||||
set(DATA_INSTALL_DIR "${PROGNAME}.app/Contents/Resources")
|
||||
@ -206,6 +271,7 @@ else()
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(CLI_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
|
||||
set(PROXY_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
|
||||
set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
|
||||
set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/keepassxc")
|
||||
set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/keepassxc")
|
||||
@ -215,41 +281,53 @@ if(WITH_TESTS)
|
||||
enable_testing()
|
||||
endif(WITH_TESTS)
|
||||
|
||||
find_package(Qt5Core 5.2 REQUIRED)
|
||||
find_package(Qt5Network 5.2 REQUIRED)
|
||||
find_package(Qt5Concurrent 5.2 REQUIRED)
|
||||
find_package(Qt5Widgets 5.2 REQUIRED)
|
||||
find_package(Qt5Test 5.2 REQUIRED)
|
||||
find_package(Qt5LinguistTools 5.2 REQUIRED)
|
||||
find_package(Qt5Network 5.2 REQUIRED)
|
||||
if (UNIX AND NOT APPLE)
|
||||
find_package(Qt5DBus 5.2 REQUIRED)
|
||||
if(UNIX AND NOT APPLE)
|
||||
find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Test LinguistTools DBus REQUIRED)
|
||||
elseif(APPLE)
|
||||
find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Test LinguistTools REQUIRED
|
||||
HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH
|
||||
)
|
||||
find_package(Qt5 COMPONENTS MacExtras
|
||||
HINTS /usr/local/Cellar/qt/*/lib/cmake ENV PATH
|
||||
)
|
||||
else()
|
||||
find_package(Qt5 COMPONENTS Core Network Concurrent Widgets Test LinguistTools REQUIRED)
|
||||
endif()
|
||||
|
||||
if(Qt5Core_VERSION VERSION_LESS "5.2.0")
|
||||
message(FATAL_ERROR "Qt version 5.2.0 or higher is required")
|
||||
endif()
|
||||
|
||||
get_filename_component(Qt5_PREFIX ${Qt5_DIR}/../../.. REALPATH)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
if(APPLE)
|
||||
set(CMAKE_MACOSX_RPATH TRUE)
|
||||
find_program(MACDEPLOYQT_EXE macdeployqt HINTS ${Qt5_PREFIX}/bin ENV PATH)
|
||||
if(NOT MACDEPLOYQT_EXE)
|
||||
message(FATAL_ERROR "macdeployqt is required to build in macOS")
|
||||
else()
|
||||
message(STATUS "Using macdeployqt: ${MACDEPLOYQT_EXE}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Debian sets the 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_package(LibGPGError REQUIRED)
|
||||
|
||||
find_package(Gcrypt 1.6.0 REQUIRED)
|
||||
find_package(Gcrypt 1.7.0 REQUIRED)
|
||||
find_package(Argon2 REQUIRED)
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
set(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR})
|
||||
|
||||
check_cxx_source_compiles("
|
||||
#include <zlib.h>
|
||||
|
||||
#if !defined(ZLIB_VERNUM) || (ZLIB_VERNUM < 0x1200)
|
||||
#error zlib 1.2.x or higher is required to use the gzip format
|
||||
#endif
|
||||
|
||||
int main() { return 0; }" ZLIB_SUPPORTS_GZIP)
|
||||
|
||||
if(NOT ZLIB_SUPPORTS_GZIP)
|
||||
message(FATAL_ERROR "zlib 1.2.x or higher is required to use the gzip format")
|
||||
if(ZLIB_VERSION_STRING VERSION_LESS "1.2.0")
|
||||
message(FATAL_ERROR "zlib 1.2.0 or higher is required to use the gzip format")
|
||||
endif()
|
||||
|
||||
# Optional
|
||||
|
16
COPYING
@ -27,7 +27,7 @@ Copyright: 2010-2012, Felix Geyer <debfx@fobos.de>
|
||||
2000-2008, Tom Sato <VEF00200@nifty.ne.jp>
|
||||
2013, Laszlo Papp <lpapp@kde.org>
|
||||
2013, David Faure <faure@kde.org>
|
||||
2016-2017, KeePassXC Team <team@keepassxc.org>
|
||||
2016-2018, KeePassXC Team <team@keepassxc.org>
|
||||
License: GPL-2 or GPL-3
|
||||
|
||||
Comment: The "KeePassXC Team" in every copyright notice is formed by the following people:
|
||||
@ -55,10 +55,6 @@ Files: cmake/GenerateProductVersion.cmake
|
||||
Copyright: 2015 halex2005 <akharlov@gmail.com>
|
||||
License: MIT
|
||||
|
||||
Files: cmake/CodeCoverage.cmake
|
||||
Copyright: 2012 - 2015, Lars Bilke
|
||||
License: BSD-3-clause
|
||||
|
||||
Files: share/icons/application/*/apps/keepassxc.png
|
||||
share/icons/application/scalable/apps/keepassxc.svgz
|
||||
share/icons/application/*/apps/keepassxc-dark.png
|
||||
@ -156,6 +152,7 @@ License: LGPL-2.1
|
||||
Comment: based on Nuvola icon theme
|
||||
|
||||
Files: share/icons/application/*/actions/application-exit.png
|
||||
share/icons/application/*/actions/chronometer.png
|
||||
share/icons/application/*/actions/configure.png
|
||||
share/icons/application/*/actions/dialog-close.png
|
||||
share/icons/application/*/actions/dialog-ok.png
|
||||
@ -178,6 +175,7 @@ Files: share/icons/application/*/actions/application-exit.png
|
||||
share/icons/application/*/actions/view-history.png
|
||||
share/icons/application/*/apps/internet-web-browser.png
|
||||
share/icons/application/*/apps/preferences-desktop-icons.png
|
||||
share/icons/application/*/apps/utilities-terminal.png
|
||||
share/icons/application/*/categories/preferences-other.png
|
||||
share/icons/application/*/status/dialog-error.png
|
||||
share/icons/application/*/status/dialog-information.png
|
||||
@ -225,8 +223,8 @@ Copyright: 2009-2010, Iowa State University
|
||||
License: Boost-1.0
|
||||
|
||||
Files: src/zxcvbn/zxcvbn.*
|
||||
Copyright: 2015, Tony Evans
|
||||
License: BSD 3-clause
|
||||
Copyright: 2015-2017, Tony Evans
|
||||
License: MIT
|
||||
|
||||
Files: src/http/qhttp/*
|
||||
Copyright: 2014, Amir Zamani
|
||||
@ -237,3 +235,7 @@ Files: src/gui/KMessageWidget.h
|
||||
Copyright: 2011 Aurélien Gâteau <agateau@kde.org>
|
||||
2014 Dominik Haumann <dhaumann@kde.org>
|
||||
License: LGPL-2.1
|
||||
|
||||
Files: share/macosx/dmg-background.tiff
|
||||
Copyright: 2008-2014, Andrey Tarantsov
|
||||
License: MIT
|
||||
|
29
Dockerfile
@ -1,5 +1,5 @@
|
||||
# KeePassXC Linux Release Build Dockerfile
|
||||
# Copyright (C) 2017 KeePassXC team <https://keepassxc.org/>
|
||||
# Copyright (C) 2017-2018 KeePassXC team <https://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
|
||||
@ -16,8 +16,10 @@
|
||||
|
||||
FROM ubuntu:14.04
|
||||
|
||||
ENV REBUILD_COUNTER=8
|
||||
|
||||
ENV QT5_VERSION=59
|
||||
ENV QT5_PPA_VERSION=${QT5_VERSION}2
|
||||
ENV QT5_PPA_VERSION=${QT5_VERSION}4
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get update -y \
|
||||
@ -36,10 +38,16 @@ RUN set -x \
|
||||
&& apt-get install -y \
|
||||
cmake3 \
|
||||
g++ \
|
||||
libgcrypt20-dev \
|
||||
git \
|
||||
libgcrypt20-18-dev \
|
||||
libargon2-0-dev \
|
||||
libsodium-dev \
|
||||
libcurl-no-gcrypt-dev \
|
||||
qt${QT5_VERSION}base \
|
||||
qt${QT5_VERSION}tools \
|
||||
qt${QT5_VERSION}x11extras \
|
||||
qt${QT5_VERSION}translations \
|
||||
qt${QT5_VERSION}imageformats \
|
||||
zlib1g-dev \
|
||||
libxi-dev \
|
||||
libxtst-dev \
|
||||
@ -47,10 +55,15 @@ RUN set -x \
|
||||
libyubikey-dev \
|
||||
libykpers-1-dev
|
||||
|
||||
ENV CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake
|
||||
ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib
|
||||
ENV CMAKE_PREFIX_PATH="/opt/qt${QT5_VERSION}/lib/cmake"
|
||||
ENV CMAKE_INCLUDE_PATH="/opt/keepassxc-libs/include"
|
||||
ENV CMAKE_LIBRARY_PATH="/opt/keepassxc-libs/lib/x86_64-linux-gnu"
|
||||
ENV CPATH="${CMAKE_INCLUDE_PATH}"
|
||||
ENV LD_LIBRARY_PATH="${CMAKE_LIBRARY_PATH}:/opt/qt${QT5_VERSION}/lib"
|
||||
|
||||
RUN set -x \
|
||||
&& echo /opt/qt${QT_VERSION}/lib > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf
|
||||
&& echo "/opt/qt${QT5_VERSION}/lib" > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf \
|
||||
&& echo "/opt/keepassxc-libs/lib/x86_64-linux-gnu" > /etc/ld.so.conf.d/keepassxc.conf
|
||||
|
||||
# AppImage dependencies
|
||||
RUN set -x \
|
||||
@ -58,6 +71,10 @@ RUN set -x \
|
||||
libfuse2 \
|
||||
wget
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get autoremove --purge \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
VOLUME /keepassxc/src
|
||||
VOLUME /keepassxc/out
|
||||
WORKDIR /keepassxc
|
||||
|
78
INSTALL.md
@ -1,11 +1,13 @@
|
||||
Install KeePassXC
|
||||
Build and Install KeePassXC
|
||||
=================
|
||||
|
||||
This document will guide you across the steps to install KeePassXC.
|
||||
You can visit the online version of this document a the following link
|
||||
This document will guide you through the steps to build and install KeePassXC from source.
|
||||
You can visit the online version of this document at the following link:
|
||||
|
||||
https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source
|
||||
|
||||
The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using KeePassXC on your
|
||||
Windows, Mac, or Linux computer using the pre-built binaries.
|
||||
|
||||
Build Dependencies
|
||||
==================
|
||||
@ -23,15 +25,16 @@ The following libraries are required:
|
||||
* zlib
|
||||
* libmicrohttpd
|
||||
* libxi, libxtst, qtx11extras (optional for auto-type on X11)
|
||||
* libsodium (>= 1.0.12, optional for KeePassXC-Browser support)
|
||||
* libargon2
|
||||
|
||||
|
||||
Prepare the Building Environment
|
||||
================================
|
||||
|
||||
Building Environment on Linux ==> https://github.com/keepassxreboot/keepassx/wiki/Building-Environment-on-Linux
|
||||
Building Environment on Windows ==> https://github.com/keepassxreboot/keepassx/wiki/Building-Environment-on-Windows
|
||||
Building Environment on MacOS ==> https://github.com/keepassxreboot/keepassx/wiki/Building-Environment-on-MacOS
|
||||
|
||||
* [Building Environment on Linux](https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-Linux)
|
||||
* [Building Environment on Windows](https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-Windows)
|
||||
* [Building Environment on MacOS](https://github.com/keepassxreboot/keepassxc/wiki/Set-up-Build-Environment-on-OS-X)
|
||||
|
||||
Build Steps
|
||||
===========
|
||||
@ -39,32 +42,69 @@ Build Steps
|
||||
To compile from source, open a **Terminal (on Linux/MacOS)** or a **MSYS2-MinGW shell (on Windows)**<br/>
|
||||
**Note:** on Windows make sure you are using a **MINGW shell** by checking the label before the current path
|
||||
|
||||
Navigate to the path you have downloaded KeePassXC and type these commands:
|
||||
First, download the KeePassXC [source tarball](https://keepassxc.org/download#source)
|
||||
or check out the latest version from our [Git repository](https://github.com/keepassxreboot/keepassxc).
|
||||
|
||||
To clone the project from Git, `cd` to a suitable location and run
|
||||
|
||||
```bash
|
||||
git clone https://github.com/keepassxreboot/keepassxc.git
|
||||
```
|
||||
|
||||
This will clone the entire contents of the repository and check out the current `develop` branch.
|
||||
|
||||
To update the project from within the project's folder, you can run the following command:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
```
|
||||
|
||||
Navigate to the directory where you have downloaded KeePassXC and type these commands:
|
||||
|
||||
```
|
||||
cd directory-where-sources-live
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DWITH_TESTS=OFF
|
||||
cmake -DWITH_TESTS=OFF ...and other options - see below...
|
||||
make
|
||||
```
|
||||
These steps place the compiled KeePassXC binary inside the `./build/src/` directory.
|
||||
(Note the cmake notes/options below.)
|
||||
|
||||
**Note:** If you are on MacOS you must add this parameter to **Cmake**, with the Qt version you have installed<br/> `-DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake/`
|
||||
**Cmake Notes:**
|
||||
|
||||
You will have the compiled KeePassXC binary inside the `./build/src/` directory.
|
||||
* Common cmake parameters
|
||||
|
||||
Common cmake parameters
|
||||
```
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local
|
||||
-DCMAKE_VERBOSE_MAKEFILE=ON
|
||||
-DCMAKE_BUILD_TYPE=<RelWithDebInfo/Debug/Release>
|
||||
-DWITH_GUI_TESTS=ON
|
||||
```
|
||||
```
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local
|
||||
-DCMAKE_VERBOSE_MAKEFILE=ON
|
||||
-DCMAKE_BUILD_TYPE=<RelWithDebInfo/Debug/Release>
|
||||
-DWITH_GUI_TESTS=ON
|
||||
```
|
||||
|
||||
* cmake accepts the following options:
|
||||
|
||||
```
|
||||
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
|
||||
-DWITH_XC_HTTP=[ON|OFF] Enable/Disable KeePassHTTP and custom icon downloads (default: OFF)
|
||||
-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_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_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)
|
||||
```
|
||||
|
||||
* If you are on MacOS you must add this parameter to **Cmake**, with the Qt version you have installed<br/> `-DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake/`
|
||||
|
||||
:exclamation: When building with ASan support on macOS, you need to use `export ASAN_OPTIONS=detect_leaks=0` before running the tests (no LSan support in macOS).
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
To install this binary execute the following:
|
||||
After you have successfully built KeePassXC, install the binary by executing the following:
|
||||
|
||||
```bash
|
||||
sudo make install
|
||||
|
19
LICENSE.MIT
Normal file
@ -0,0 +1,19 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
106
README.md
@ -1,78 +1,68 @@
|
||||
# <img src="https://keepassxc.org/logo.png" width="40" height="40"/> KeePassXC [![Travis Build Status](https://travis-ci.org/keepassxreboot/keepassxc.svg?branch=develop)](https://travis-ci.org/keepassxreboot/keepassxc) [![Coverage Status](https://coveralls.io/repos/github/keepassxreboot/keepassxc/badge.svg)](https://coveralls.io/github/keepassxreboot/keepassxc)
|
||||
# <img src="https://keepassxc.org/logo.png" width="40" height="40"/> KeePassXC
|
||||
[![TeamCity Build Status](https://ci.keepassxc.org/app/rest/builds/buildType:\(id:KeepassXC_TeamCityCi\)/statusIcon?guest=1)](https://ci.keepassxc.org/viewType.html?buildTypeId=KeepassXC_TeamCityCi&guest=1) [![Coverage Status](https://coveralls.io/repos/github/keepassxreboot/keepassxc/badge.svg)](https://coveralls.io/github/keepassxreboot/keepassxc)
|
||||
|
||||
KeePass Cross-platform Community Edition
|
||||
## About KeePassXC
|
||||
[KeePassXC](https://keepassxc.org) is a cross-platform community fork of
|
||||
[KeePassX](https://www.keepassx.org/).
|
||||
Our goal is to extend and improve it with new features and bugfixes
|
||||
to provide a feature-rich, fully cross-platform and modern
|
||||
open-source password manager.
|
||||
|
||||
## About
|
||||
[KeePassXC](https://keepassxc.org) is a community fork of [KeePassX](https://www.keepassx.org/) with the goal to extend and improve it with new features and bugfixes to provide a feature-rich, fully cross-platform and modern open-source password manager.
|
||||
## Installation
|
||||
The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using
|
||||
KeePassXC on your Windows, Mac, 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 out your distribution's package list to see if KeePassXC is available.
|
||||
|
||||
## Additional features compared to KeePassX
|
||||
- Auto-Type on all three major platforms (Linux, Windows, OS X)
|
||||
- Stand-alone password generator
|
||||
- Auto-Type on all three major platforms (Linux, Windows, macOS)
|
||||
- Twofish encryption
|
||||
- YubiKey challenge-response support
|
||||
- TOTP generation
|
||||
- CSV import
|
||||
- Command line interface
|
||||
- DEP and ASLR hardening
|
||||
- Stand-alone password and passphrase generator
|
||||
- Password strength meter
|
||||
- YubiKey HMAC-SHA1 authentication for unlocking databases
|
||||
- Using website favicons as entry icons
|
||||
- Merging of databases
|
||||
- Automatic reload when the database changed on disk
|
||||
- KeePassHTTP support for use with KeePassHTTP-Connector for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/) and [Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb), and [passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari.
|
||||
- Browser integration with KeePassHTTP-Connector for
|
||||
[Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/) and
|
||||
[Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb), and
|
||||
[passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari. [[See note about KeePassHTTP]](#Note_about_KeePassHTTP)
|
||||
- Browser integration with KeePassXC-Browser using [native messaging](https://developer.chrome.com/extensions/nativeMessaging) for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/) and [Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepassxc-browser/iopaggbpplllidnfmcghoonnokmjoicf)
|
||||
- Many bug fixes
|
||||
|
||||
For a full list of features and changes, read the [CHANGELOG](CHANGELOG) document.
|
||||
|
||||
### Note about KeePassHTTP
|
||||
KeePassHTTP is not a highly secure protocol and has certain flaw which allow an attacker to decrypt your passwords when they manage to intercept communication between a KeePassHTTP server and PassIFox/chromeIPass over a network connection (see [here](https://github.com/pfn/keepasshttp/issues/258) and [here](https://github.com/keepassxreboot/keepassxc/issues/147)). KeePassXC therefore strictly limits communication between itself and the browser plugin to your local computer. As long as your computer is not compromised, your passwords are fairly safe that way, but use it at your own risk!
|
||||
## Building KeePassXC
|
||||
|
||||
### Installation
|
||||
Pre-compiled binaries can be found on the [downloads page](https://keepassxc.org/download). Additionally, individual Linux distributions may ship their own versions, so please check out your distribution's package list to see if KeePassXC is available.
|
||||
Detailed instructions are available in the [Build and Install](./INSTALL.md)
|
||||
page or on the [Wiki page](https://github.com/keepassxreboot/keepassxc/wiki/Building-KeePassXC).
|
||||
|
||||
### Building KeePassXC
|
||||
## Contributing
|
||||
|
||||
*More detailed instructions are available in the INSTALL file or on the [Wiki page](https://github.com/keepassxreboot/keepassxc/wiki/Building-KeePassXC).*
|
||||
|
||||
First, you must download the KeePassXC [source tarball](https://keepassxc.org/download#source) or check out the latest version from our [Git repository](https://github.com/keepassxreboot/keepassxc).
|
||||
|
||||
To clone the project from Git, `cd` to a suitable location and run
|
||||
|
||||
```bash
|
||||
git clone https://github.com/keepassxreboot/keepassxc.git
|
||||
```
|
||||
|
||||
This will clone the entire contents of the repository and check out the current `develop` branch.
|
||||
|
||||
To update the project from within the project's folder, you can run the following command:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
```
|
||||
|
||||
Once you have downloaded the source code, you can `cd` into the source code directory, build and install KeePassXC:
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DWITH_TESTS=OFF ..
|
||||
make -j8
|
||||
sudo make install
|
||||
```
|
||||
|
||||
cmake accepts the following options:
|
||||
|
||||
```
|
||||
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
|
||||
-DWITH_XC_HTTP=[ON|OFF] Enable/Disable KeePassHTTP and custom icon downloads (default: OFF)
|
||||
-DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF)
|
||||
|
||||
-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_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux only) (default: OFF)
|
||||
-DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF)
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
We are always looking for suggestions how to improve our application. If you find any bugs or have an idea for a new feature, please let us know by opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues) on GitHub or join us on IRC on freenode channels #keepassxc or #keepassxc-dev.
|
||||
We are always looking for suggestions how to improve our application.
|
||||
If you find any bugs or have an idea for a new feature, please let us know by
|
||||
opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues)
|
||||
on GitHub or join us on IRC on freenode channels #keepassxc or #keepassxc-dev.
|
||||
|
||||
You can of course also directly contribute your own code. We are happy to accept your pull requests.
|
||||
|
||||
Please read the [CONTRIBUTING document](.github/CONTRIBUTING.md) for further information.
|
||||
|
||||
### Note about KeePassHTTP
|
||||
The KeePassHTTP protocol is not a highly secure protocol.
|
||||
It has a certain flaw which could allow an attacker to decrypt your passwords
|
||||
should they manage to impersonate the web browser extension from a remote address.
|
||||
<!--intercept communication between a KeePassHTTP server
|
||||
and PassIFox/chromeIPass over a network connection -->
|
||||
(See [here](https://github.com/pfn/keepasshttp/issues/258) and [here](https://github.com/keepassxreboot/keepassxc/issues/147)).
|
||||
|
||||
To minimize the risk, KeePassXC strictly limits communication between itself
|
||||
and the browser plugin to your local computer (localhost).
|
||||
This makes your passwords quite safe,
|
||||
but as with all open source software, use it at your own risk!
|
||||
|
73
ci/trusty/Dockerfile
Normal file
@ -0,0 +1,73 @@
|
||||
# KeePassXC Linux Release Build Dockerfile
|
||||
# Copyright (C) 2017 KeePassXC team <https://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/>.
|
||||
|
||||
# TIP: check this Dockerfile using this online tool: https://www.fromlatest.io
|
||||
|
||||
FROM ubuntu:14.04
|
||||
|
||||
ENV REBUILD_COUNTER=4
|
||||
|
||||
ENV QT5_VERSION=53
|
||||
ENV QT5_PPA_VERSION=${QT5_VERSION}2
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get update -y \
|
||||
&& apt-get -y install software-properties-common
|
||||
|
||||
RUN set -x \
|
||||
&& add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty \
|
||||
&& add-apt-repository ppa:phoerious/keepassxc
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get -y update \
|
||||
&& apt-get -y --no-install-recommends install \
|
||||
build-essential \
|
||||
clang-3.6 \
|
||||
libclang-common-3.6-dev \
|
||||
clang-format-3.6 \
|
||||
cmake3 \
|
||||
make \
|
||||
libgcrypt20-18-dev \
|
||||
libargon2-0-dev \
|
||||
libsodium-dev \
|
||||
libcurl-no-gcrypt-dev \
|
||||
qt${QT5_VERSION}base \
|
||||
qt${QT5_VERSION}tools \
|
||||
qt${QT5_VERSION}x11extras \
|
||||
qt${QT5_VERSION}translations \
|
||||
zlib1g-dev \
|
||||
libyubikey-dev \
|
||||
libykpers-1-dev \
|
||||
libxi-dev \
|
||||
libxtst-dev \
|
||||
xvfb
|
||||
|
||||
ENV CMAKE_PREFIX_PATH="/opt/qt${QT5_VERSION}/lib/cmake"
|
||||
ENV CMAKE_INCLUDE_PATH="/opt/keepassxc-libs/include"
|
||||
ENV CMAKE_LIBRARY_PATH="/opt/keepassxc-libs/lib/x86_64-linux-gnu"
|
||||
ENV CPATH="${CMAKE_INCLUDE_PATH}"
|
||||
ENV LD_LIBRARY_PATH="${CMAKE_LIBRARY_PATH}:/opt/qt${QT5_VERSION}/lib"
|
||||
|
||||
RUN set -x \
|
||||
&& echo "/opt/qt${QT5_VERSION}/lib" > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf \
|
||||
&& echo "/opt/keepassxc-libs/lib/x86_64-linux-gnu" > /etc/ld.so.conf.d/keepassxc.conf
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get autoremove --purge \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
VOLUME ["/keepassxc"]
|
||||
WORKDIR /keepassxc
|
34
cmake/FindArgon2.cmake
Normal file
@ -0,0 +1,34 @@
|
||||
# Copyright (C) 2017 KeePassXC Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 or (at your option)
|
||||
# version 3 of the License.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
find_path(ARGON2_INCLUDE_DIR argon2.h)
|
||||
if (MINGW)
|
||||
# find static library on Windows, and redefine used symbols to
|
||||
# avoid definition name conflicts with libsodium
|
||||
find_library(ARGON2_SYS_LIBRARIES libargon2.a)
|
||||
message(STATUS "Patching libargon2...\n")
|
||||
execute_process(COMMAND objcopy
|
||||
--redefine-sym argon2_hash=libargon2_argon2_hash
|
||||
--redefine-sym argon2_error_message=libargon2_argon2_error_message
|
||||
${ARGON2_SYS_LIBRARIES} ${CMAKE_BINARY_DIR}/libargon2_patched.a
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
find_library(ARGON2_LIBRARIES libargon2_patched.a PATHS ${CMAKE_BINARY_DIR} NO_DEFAULT_PATH)
|
||||
else()
|
||||
find_library(ARGON2_LIBRARIES argon2)
|
||||
endif()
|
||||
mark_as_advanced(ARGON2_LIBRARIES ARGON2_INCLUDE_DIR)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Argon2 DEFAULT_MSG ARGON2_LIBRARIES ARGON2_INCLUDE_DIR)
|
@ -14,10 +14,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
find_path(GPGERROR_INCLUDE_DIR gpg-error.h)
|
||||
|
||||
find_library(GPGERROR_LIBRARIES gpg-error)
|
||||
|
||||
mark_as_advanced(GPGERROR_LIBRARIES GPGERROR_INCLUDE_DIR)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
include_directories(${GPGERROR_INCLUDE_DIR})
|
||||
find_package_handle_standard_args(LibGPGError DEFAULT_MSG GPGERROR_LIBRARIES GPGERROR_INCLUDE_DIR)
|
||||
|
@ -17,8 +17,8 @@ find_path(YUBIKEY_CORE_INCLUDE_DIR yubikey.h)
|
||||
find_path(YUBIKEY_PERS_INCLUDE_DIR ykcore.h PATH_SUFFIXES ykpers-1)
|
||||
set(YUBIKEY_INCLUDE_DIRS ${YUBIKEY_CORE_INCLUDE_DIR} ${YUBIKEY_PERS_INCLUDE_DIR})
|
||||
|
||||
find_library(YUBIKEY_CORE_LIBRARY yubikey)
|
||||
find_library(YUBIKEY_PERS_LIBRARY ykpers-1)
|
||||
find_library(YUBIKEY_CORE_LIBRARY NAMES yubikey.dll libyubikey.so yubikey)
|
||||
find_library(YUBIKEY_PERS_LIBRARY NAMES ykpers-1.dll libykpers-1.so ykpers-1)
|
||||
set(YUBIKEY_LIBRARIES ${YUBIKEY_CORE_LIBRARY} ${YUBIKEY_PERS_LIBRARY})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
267
cmake/Findsodium.cmake
Normal file
@ -0,0 +1,267 @@
|
||||
# Written in 2016 by Henrik Steffen Gaßmann <henrik@gassmann.onl>
|
||||
#
|
||||
# To the extent possible under law, the author(s) have dedicated all
|
||||
# copyright and related and neighboring rights to this software to the
|
||||
# public domain worldwide. This software is distributed without any warranty.
|
||||
#
|
||||
# You should have received a copy of the CC0 Public Domain Dedication
|
||||
# along with this software. If not, see
|
||||
#
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
#
|
||||
########################################################################
|
||||
# Tries to find the local libsodium installation.
|
||||
#
|
||||
# On Windows the sodium_DIR environment variable is used as a default
|
||||
# hint which can be overridden by setting the corresponding cmake variable.
|
||||
#
|
||||
# Once done the following variables will be defined:
|
||||
#
|
||||
# sodium_FOUND
|
||||
# sodium_INCLUDE_DIR
|
||||
# sodium_LIBRARY_DEBUG
|
||||
# sodium_LIBRARY_RELEASE
|
||||
#
|
||||
#
|
||||
# Furthermore an imported "sodium" target is created.
|
||||
#
|
||||
|
||||
if (CMAKE_C_COMPILER_ID STREQUAL "GNU"
|
||||
OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||
set(_GCC_COMPATIBLE 1)
|
||||
endif()
|
||||
|
||||
# static library option
|
||||
option(sodium_USE_STATIC_LIBS "enable to statically link against sodium")
|
||||
if(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST))
|
||||
unset(sodium_LIBRARY CACHE)
|
||||
unset(sodium_LIBRARY_DEBUG CACHE)
|
||||
unset(sodium_LIBRARY_RELEASE CACHE)
|
||||
unset(sodium_DLL_DEBUG CACHE)
|
||||
unset(sodium_DLL_RELEASE CACHE)
|
||||
set(sodium_USE_STATIC_LIBS_LAST ${sodium_USE_STATIC_LIBS} CACHE INTERNAL "internal change tracking variable")
|
||||
endif()
|
||||
|
||||
|
||||
########################################################################
|
||||
# UNIX
|
||||
if (UNIX)
|
||||
# import pkg-config
|
||||
find_package(PkgConfig QUIET)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(sodium_PKG QUIET libsodium)
|
||||
endif()
|
||||
|
||||
if(sodium_USE_STATIC_LIBS)
|
||||
set(XPREFIX sodium_PKG_STATIC)
|
||||
else()
|
||||
set(XPREFIX sodium_PKG)
|
||||
endif()
|
||||
|
||||
find_path(sodium_INCLUDE_DIR sodium.h
|
||||
HINTS ${${XPREFIX}_INCLUDE_DIRS}
|
||||
)
|
||||
find_library(sodium_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES} sodium
|
||||
HINTS ${${XPREFIX}_LIBRARY_DIRS}
|
||||
)
|
||||
find_library(sodium_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES} sodium
|
||||
HINTS ${${XPREFIX}_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
|
||||
########################################################################
|
||||
# Windows
|
||||
elseif (WIN32)
|
||||
set(sodium_DIR "$ENV{sodium_DIR}" CACHE FILEPATH "sodium install directory")
|
||||
mark_as_advanced(sodium_DIR)
|
||||
|
||||
find_path(sodium_INCLUDE_DIR sodium.h
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
# detect target architecture
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.c" [=[
|
||||
#if defined _M_IX86
|
||||
#error ARCH_VALUE x86_32
|
||||
#elif defined _M_X64
|
||||
#error ARCH_VALUE x86_64
|
||||
#endif
|
||||
#error ARCH_VALUE unknown
|
||||
]=])
|
||||
try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/arch.c"
|
||||
OUTPUT_VARIABLE _COMPILATION_LOG
|
||||
)
|
||||
string(REGEX REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" "\\1" _TARGET_ARCH "${_COMPILATION_LOG}")
|
||||
|
||||
# construct library path
|
||||
if (_TARGET_ARCH STREQUAL "x86_32")
|
||||
string(APPEND _PLATFORM_PATH "Win32")
|
||||
elseif(_TARGET_ARCH STREQUAL "x86_64")
|
||||
string(APPEND _PLATFORM_PATH "x64")
|
||||
else()
|
||||
message(FATAL_ERROR "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.")
|
||||
endif()
|
||||
string(APPEND _PLATFORM_PATH "/$$CONFIG$$")
|
||||
|
||||
if (MSVC_VERSION LESS 1900)
|
||||
math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60")
|
||||
else()
|
||||
math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50")
|
||||
endif()
|
||||
string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}")
|
||||
|
||||
if (sodium_USE_STATIC_LIBS)
|
||||
string(APPEND _PLATFORM_PATH "/static")
|
||||
else()
|
||||
string(APPEND _PLATFORM_PATH "/dynamic")
|
||||
endif()
|
||||
|
||||
string(REPLACE "$$CONFIG$$" "Debug" _DEBUG_PATH_SUFFIX "${_PLATFORM_PATH}")
|
||||
string(REPLACE "$$CONFIG$$" "Release" _RELEASE_PATH_SUFFIX "${_PLATFORM_PATH}")
|
||||
|
||||
find_library(sodium_LIBRARY_DEBUG libsodium.lib
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}
|
||||
)
|
||||
find_library(sodium_LIBRARY_RELEASE libsodium.lib
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}
|
||||
)
|
||||
if (NOT sodium_USE_STATIC_LIBS)
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
|
||||
find_library(sodium_DLL_DEBUG libsodium
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}
|
||||
)
|
||||
find_library(sodium_DLL_RELEASE libsodium
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}
|
||||
)
|
||||
endif()
|
||||
|
||||
elseif(_GCC_COMPATIBLE)
|
||||
if (sodium_USE_STATIC_LIBS)
|
||||
find_library(sodium_LIBRARY_DEBUG libsodium.a
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES lib
|
||||
)
|
||||
find_library(sodium_LIBRARY_RELEASE libsodium.a
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES lib
|
||||
)
|
||||
else()
|
||||
find_library(sodium_LIBRARY_DEBUG libsodium.dll.a
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES lib
|
||||
)
|
||||
find_library(sodium_LIBRARY_RELEASE libsodium.dll.a
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES lib
|
||||
)
|
||||
|
||||
file(GLOB _DLL
|
||||
LIST_DIRECTORIES false
|
||||
RELATIVE "${sodium_DIR}/bin"
|
||||
"${sodium_DIR}/bin/libsodium*.dll"
|
||||
)
|
||||
find_library(sodium_DLL_DEBUG ${_DLL} libsodium
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES bin
|
||||
)
|
||||
find_library(sodium_DLL_RELEASE ${_DLL} libsodium
|
||||
HINTS ${sodium_DIR}
|
||||
PATH_SUFFIXES bin
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "this platform is not supported by FindSodium.cmake")
|
||||
endif()
|
||||
|
||||
|
||||
########################################################################
|
||||
# unsupported
|
||||
else()
|
||||
message(FATAL_ERROR "this platform is not supported by FindSodium.cmake")
|
||||
endif()
|
||||
|
||||
|
||||
########################################################################
|
||||
# common stuff
|
||||
|
||||
# extract sodium version
|
||||
if (sodium_INCLUDE_DIR)
|
||||
set(_VERSION_HEADER "${_INCLUDE_DIR}/sodium/version.h")
|
||||
if (EXISTS _VERSION_HEADER)
|
||||
file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT)
|
||||
string(REGEX REPLACE ".*#[ \t]*define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" "\\1"
|
||||
sodium_VERSION "${_VERSION_HEADER_CONTENT}")
|
||||
set(sodium_VERSION "${sodium_VERSION}" PARENT_SCOPE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# communicate results
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(sodium
|
||||
REQUIRED_VARS
|
||||
sodium_LIBRARY_RELEASE
|
||||
sodium_LIBRARY_DEBUG
|
||||
sodium_INCLUDE_DIR
|
||||
VERSION_VAR
|
||||
sodium_VERSION
|
||||
)
|
||||
|
||||
# mark file paths as advanced
|
||||
mark_as_advanced(sodium_INCLUDE_DIR)
|
||||
mark_as_advanced(sodium_LIBRARY_DEBUG)
|
||||
mark_as_advanced(sodium_LIBRARY_RELEASE)
|
||||
if (WIN32)
|
||||
mark_as_advanced(sodium_DLL_DEBUG)
|
||||
mark_as_advanced(sodium_DLL_RELEASE)
|
||||
endif()
|
||||
|
||||
# create imported target
|
||||
if(sodium_USE_STATIC_LIBS)
|
||||
set(_LIB_TYPE STATIC)
|
||||
else()
|
||||
set(_LIB_TYPE SHARED)
|
||||
endif()
|
||||
add_library(sodium ${_LIB_TYPE} IMPORTED)
|
||||
|
||||
set_target_properties(sodium PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${sodium_INCLUDE_DIR}"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
)
|
||||
|
||||
if (sodium_USE_STATIC_LIBS)
|
||||
set_target_properties(sodium PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "SODIUM_STATIC"
|
||||
IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}"
|
||||
IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}"
|
||||
)
|
||||
else()
|
||||
if (UNIX)
|
||||
set_target_properties(sodium PROPERTIES
|
||||
IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}"
|
||||
IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}"
|
||||
)
|
||||
elseif (WIN32)
|
||||
set_target_properties(sodium PROPERTIES
|
||||
IMPORTED_IMPLIB "${sodium_LIBRARY_RELEASE}"
|
||||
IMPORTED_IMPLIB_DEBUG "${sodium_LIBRARY_DEBUG}"
|
||||
)
|
||||
if (NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND"))
|
||||
set_target_properties(sodium PROPERTIES
|
||||
IMPORTED_LOCATION_DEBUG "${sodium_DLL_DEBUG}"
|
||||
)
|
||||
endif()
|
||||
if (NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND"))
|
||||
set_target_properties(sodium PROPERTIES
|
||||
IMPORTED_LOCATION_RELWITHDEBINFO "${sodium_DLL_RELEASE}"
|
||||
IMPORTED_LOCATION_MINSIZEREL "${sodium_DLL_RELEASE}"
|
||||
IMPORTED_LOCATION_RELEASE "${sodium_DLL_RELEASE}"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
BIN
docs/KeePassXC-Accept-Button.png
Normal file
After Width: | Height: | Size: 97 KiB |
BIN
docs/KeePassXC-Confirm.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
docs/KeePassXC-Connect.png
Normal file
After Width: | Height: | Size: 101 KiB |
47
docs/QUICKSTART.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Quick Start for KeePassXC
|
||||
|
||||
This procedure gets KeePassXC running on your computer with browser integration,
|
||||
using the pre-built binaries available for [download](https://keepassxc.org/download)
|
||||
from [KeePassXC site](https://keepassxc.org).
|
||||
|
||||
**TL;DR** KeePassXC saves your passwords securely.
|
||||
When you double-click a URL in KeePassXC, it launches your default browser to that URL.
|
||||
With browser integration configured, KeePassXC automatically enters
|
||||
username/password credentials into web page fields.
|
||||
|
||||
## Installing and Starting KeePassXC
|
||||
|
||||
* [Download the native installer](https://keepassxc.org/download) and install
|
||||
KeePassXC for your Windows, macOS, or Linux computer in the usual way for your platform.
|
||||
* Open the KeePassXC application.
|
||||
* Create a new database and give it a master key that's used to unlock the database file.
|
||||
This database holds entries (usernames, passwords, account numbers, notes)
|
||||
for all your websites, programs, etc.
|
||||
* Create a few entries - enter the username, password, URL, and optionally notes about the entry.
|
||||
* KeePassXC securely stores those entries in the database.
|
||||
|
||||
|
||||
## Setting up Browser Integration with KeePassXC
|
||||
|
||||
* *Within KeePassXC*, go to **Tools->Settings** (on macOS, go to **KeePassXC->Preferences**.)
|
||||
* In **Browser Integration**, check **Enable KeePassHTTP server**
|
||||
Leave the other options at their defaults.
|
||||
* *In your default web browser,* install the KeePassHTTP-Connector extension/add-on. Instructions for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/?src=api) or [Chrome](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb?utm_source=chrome-app-launcher-info-dialog)
|
||||
* Click the KeePassXC icon in the upper-right corner. You'll see the dialog below.
|
||||
* Click the blue Connect button to make the browser extension connect to the KeePassXC application.
|
||||
<img src="./KeePassXC-Connect.png" height="200" alt="KeePassXC Connect dialog">
|
||||
|
||||
* *Switch back to KeePassXC.* You'll see a dialog (below) indicating that a request to connect has arrived.
|
||||
* Give the connection a name (perhaps *Keepass-Browsername*, any unique name will suffice) and click OK to accept it.
|
||||
* This one-time operation connects KeePassXC and your browser.
|
||||
<img src="./KeePassXC-Accept-Button.png" height="200" alt="KeePassXC accept connection dialog">
|
||||
|
||||
## Using Browser Integration
|
||||
|
||||
* *Within KeePassXC,* double-click the URL of an entry,
|
||||
or select it and type Ctrl+U (Cmd+U on macOS).
|
||||
* Your browser opens to that URL.
|
||||
* If there are username/password fields on that page, you will see the dialog below.
|
||||
Click *Allow* to confirm that KeePassXC may access the credentials to auto-fill the fields.
|
||||
* Check *Remember this decision* to allow this each time you visit the page.
|
||||
<img src="./KeePassXC-Confirm.png" height="200" alt="KeePassCX Confirm Access dialog">
|
363
release-tool
@ -37,9 +37,10 @@ DOCKER_CONTAINER_NAME="keepassxc-build-container"
|
||||
CMAKE_OPTIONS=""
|
||||
COMPILER="g++"
|
||||
MAKE_OPTIONS="-j8"
|
||||
BUILD_PLUGINS="autotype http yubikey"
|
||||
BUILD_PLUGINS="all"
|
||||
INSTALL_PREFIX="/usr/local"
|
||||
BUILD_SOURCE_TARBALL=true
|
||||
BUILD_SNAPSHOT=false
|
||||
ORIG_BRANCH=""
|
||||
ORIG_CWD="$(pwd)"
|
||||
|
||||
@ -50,14 +51,14 @@ printUsage() {
|
||||
local cmd
|
||||
if [ "" == "$1" ] || [ "help" == "$1" ]; then
|
||||
cmd="COMMAND"
|
||||
elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] || [ "sign" == "$1" ]; then
|
||||
elif [ "check" == "$1" ] || [ "merge" == "$1" ] || [ "build" == "$1" ] || [ "gpgsign" == "$1" ] || [ "appsign" == "$1" ]; then
|
||||
cmd="$1"
|
||||
else
|
||||
logError "Unknown command: '$1'\n"
|
||||
cmd="COMMAND"
|
||||
fi
|
||||
|
||||
printf "\e[1mUsage:\e[0m $(basename $0) $cmd [--version x.y.z] [options]\n"
|
||||
|
||||
printf "\e[1mUsage:\e[0m $(basename $0) $cmd [options]\n"
|
||||
|
||||
if [ "COMMAND" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
@ -66,7 +67,8 @@ Commands:
|
||||
check Perform a dry-run check, nothing is changed
|
||||
merge Merge release branch into main branch and create release tags
|
||||
build Build and package binary release from sources
|
||||
sign Sign previously compiled release packages
|
||||
gpgsign Sign previously compiled release packages with GPG
|
||||
appsign Sign binaries with code signing certificates on Windows and macOS
|
||||
help Show help for the given command
|
||||
EOF
|
||||
elif [ "merge" == "$cmd" ]; then
|
||||
@ -110,19 +112,29 @@ Options:
|
||||
-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
|
||||
EOF
|
||||
elif [ "sign" == "$cmd" ]; then
|
||||
elif [ "gpgsign" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
|
||||
Sign previously compiled release packages
|
||||
Sign previously compiled release packages with GPG
|
||||
|
||||
Options:
|
||||
-f, --files Files to sign (required)
|
||||
-g, --gpg-key GPG key used to sign the files (default: '${GPG_KEY}')
|
||||
--signtool Specify the signtool executable (default: 'signtool')
|
||||
--signtool-key Provide a key to be used with signtool (for Windows EXE)
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
elif [ "appsign" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
|
||||
Sign binaries with code signing certificates on Windows and macOS
|
||||
|
||||
Options:
|
||||
-f, --files Files to sign (required)
|
||||
-k, --signtool-key Key to be used with signtool (required for Windows EXE)
|
||||
-i, --identity Apple Developer ID to be used with codesign (required for macOS APP and DMG)
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
fi
|
||||
@ -234,7 +246,7 @@ checkVersionInCMake() {
|
||||
local app_name_upper="$(echo "$APP_NAME" | tr '[:lower:]' '[:upper:]')"
|
||||
local major_num="$(echo ${RELEASE_NAME} | cut -f1 -d.)"
|
||||
local minor_num="$(echo ${RELEASE_NAME} | cut -f2 -d.)"
|
||||
local patch_num="$(echo ${RELEASE_NAME} | cut -f3 -d.)"
|
||||
local patch_num="$(echo ${RELEASE_NAME} | cut -f3 -d. | cut -f1 -d-)"
|
||||
|
||||
grep -q "${app_name_upper}_VERSION_MAJOR \"${major_num}\"" CMakeLists.txt
|
||||
if [ $? -ne 0 ]; then
|
||||
@ -289,7 +301,28 @@ checkSnapcraft() {
|
||||
checkTransifexCommandExists() {
|
||||
command -v tx > /dev/null
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'"
|
||||
exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'."
|
||||
fi
|
||||
}
|
||||
|
||||
checkOsslsigncodeCommandExists() {
|
||||
command -v osslsigncode > /dev/null
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "osslsigncode command not found on the PATH! Please install it using 'pacman -S mingw-w64-osslsigncode'."
|
||||
fi
|
||||
}
|
||||
|
||||
checkSigntoolCommandExists() {
|
||||
command -v signtool > /dev/null
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "signtool command not found on the PATH! Add the Windows SDK binary folder to your PATH."
|
||||
fi
|
||||
}
|
||||
|
||||
checkCodesignCommandExists() {
|
||||
command -v codesign > /dev/null
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "codesign command not found on the PATH! Please check that you have correctly installed Xcode."
|
||||
fi
|
||||
}
|
||||
|
||||
@ -434,7 +467,8 @@ merge() {
|
||||
performChecks
|
||||
|
||||
logInfo "Updating language files..."
|
||||
./share/translations/update.sh
|
||||
./share/translations/update.sh update
|
||||
./share/translations/update.sh pull
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Updating translations failed!"
|
||||
fi
|
||||
@ -531,6 +565,9 @@ build() {
|
||||
|
||||
-n|--no-source-tarball)
|
||||
BUILD_SOURCE_TARBALL=false ;;
|
||||
|
||||
--snapshot)
|
||||
BUILD_SNAPSHOT=true ;;
|
||||
|
||||
-h|--help)
|
||||
printUsage "build"
|
||||
@ -545,12 +582,27 @@ build() {
|
||||
done
|
||||
|
||||
init
|
||||
checkWorkingTreeClean
|
||||
|
||||
OUTPUT_DIR="$(realpath "$OUTPUT_DIR")"
|
||||
|
||||
logInfo "Checking out release tag '${TAG_NAME}'..."
|
||||
git checkout "$TAG_NAME"
|
||||
if ${BUILD_SNAPSHOT}; then
|
||||
TAG_NAME="HEAD"
|
||||
local branch=`git rev-parse --abbrev-ref HEAD`
|
||||
logInfo "Using current branch ${branch} to build..."
|
||||
RELEASE_NAME="${RELEASE_NAME}-snapshot"
|
||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DKEEPASSXC_BUILD_TYPE=Snapshot"
|
||||
else
|
||||
checkWorkingTreeClean
|
||||
|
||||
if $(echo "$TAG_NAME" | grep -qP "\-(alpha|beta)\\d+\$"); 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
|
||||
git checkout "$TAG_NAME"
|
||||
fi
|
||||
|
||||
logInfo "Creating output directory..."
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
@ -559,20 +611,40 @@ build() {
|
||||
exitError "Failed to create output directory!"
|
||||
fi
|
||||
|
||||
if $BUILD_SOURCE_TARBALL; then
|
||||
if ${BUILD_SOURCE_TARBALL}; then
|
||||
logInfo "Creating source tarball..."
|
||||
local app_name_lower="$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]')"
|
||||
TARBALL_NAME="${app_name_lower}-${RELEASE_NAME}-src.tar.xz"
|
||||
git archive --format=tar "$TAG_NAME" --prefix="${app_name_lower}-${RELEASE_NAME}/" \
|
||||
| xz -6 > "${OUTPUT_DIR}/${TARBALL_NAME}"
|
||||
local prefix="${app_name_lower}-${RELEASE_NAME}"
|
||||
local tarball_name="${prefix}-src.tar"
|
||||
|
||||
git archive --format=tar "$TAG_NAME" --prefix="${prefix}/" --output="${OUTPUT_DIR}/${tarball_name}"
|
||||
|
||||
if ! ${BUILD_SNAPSHOT}; then
|
||||
# add .version file to tar
|
||||
mkdir "${prefix}"
|
||||
echo -n ${RELEASE_NAME} > "${prefix}/.version"
|
||||
tar --append --file="${OUTPUT_DIR}/${tarball_name}" "${prefix}/.version"
|
||||
rm "${prefix}/.version"
|
||||
rmdir "${prefix}" 2> /dev/null
|
||||
fi
|
||||
|
||||
xz -6 "${OUTPUT_DIR}/${tarball_name}"
|
||||
fi
|
||||
|
||||
|
||||
if ! ${BUILD_SNAPSHOT} && [ -e "${OUTPUT_DIR}/build-release" ]; then
|
||||
logInfo "Cleaning existing build directory..."
|
||||
rm -r "${OUTPUT_DIR}/build-release" 2> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Failed to clean existing build directory, please do it manually."
|
||||
fi
|
||||
fi
|
||||
|
||||
logInfo "Creating build directory..."
|
||||
mkdir -p "${OUTPUT_DIR}/build-release"
|
||||
cd "${OUTPUT_DIR}/build-release"
|
||||
|
||||
logInfo "Configuring sources..."
|
||||
for p in $BUILD_PLUGINS; do
|
||||
for p in ${BUILD_PLUGINS}; do
|
||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_XC_$(echo $p | tr '[:lower:]' '[:upper:]')=On"
|
||||
done
|
||||
|
||||
@ -585,33 +657,33 @@ build() {
|
||||
|
||||
if [ "" == "$DOCKER_IMAGE" ]; then
|
||||
if [ "$(uname -s)" == "Darwin" ]; then
|
||||
# Building on OS X
|
||||
local qt_vers="$(ls /usr/local/Cellar/qt5 2> /dev/null | sort -r | head -n1)"
|
||||
if [ "" == "$qt_vers" ]; then
|
||||
exitError "Couldn't find Qt5! Please make sure it is available in '/usr/local/Cellar/qt5'."
|
||||
fi
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.7
|
||||
|
||||
# Building on macOS
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.10
|
||||
|
||||
logInfo "Configuring build..."
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 -DWITH_CXX11=OFF \
|
||||
-DCMAKE_PREFIX_PATH="/usr/local/Cellar/qt5/${qt_vers}/lib/cmake" \
|
||||
-DQT_BINARY_DIR="/usr/local/Cellar/qt5/${qt_vers}/bin" $CMAKE_OPTIONS "$SRC_DIR"
|
||||
|
||||
cmake -DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
|
||||
-DCMAKE_PREFIX_PATH="/usr/local/opt/qt/lib/cmake" \
|
||||
${CMAKE_OPTIONS} "$SRC_DIR"
|
||||
|
||||
logInfo "Compiling and packaging sources..."
|
||||
make $MAKE_OPTIONS package
|
||||
make ${MAKE_OPTIONS} package
|
||||
|
||||
mv "./${APP_NAME}-${RELEASE_NAME}.dmg" ../
|
||||
elif [ "$(uname -o)" == "Msys" ]; then
|
||||
# Building on Windows with Msys
|
||||
# Building on Windows with Msys2
|
||||
logInfo "Configuring build..."
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off -G"MSYS Makefiles" \
|
||||
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" $CMAKE_OPTIONS "$SRC_DIR"
|
||||
|
||||
logInfo "Compiling and packaging sources..."
|
||||
make $MAKE_OPTIONS package
|
||||
mingw32-make ${MAKE_OPTIONS} preinstall
|
||||
# Call cpack directly instead of calling make package.
|
||||
# This is important because we want to build the MSI when making a
|
||||
# release.
|
||||
cpack -G "NSIS;ZIP;${CPACK_GENERATORS}"
|
||||
|
||||
mv "./${APP_NAME}-${RELEASE_NAME}-"*.{exe,zip} ../
|
||||
mv "./${APP_NAME}-"*.* ../
|
||||
else
|
||||
mkdir -p "${OUTPUT_DIR}/bin-release"
|
||||
|
||||
@ -643,7 +715,8 @@ build() {
|
||||
"$DOCKER_IMAGE" \
|
||||
bash -c "cd /keepassxc/out/build-release && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off $CMAKE_OPTIONS \
|
||||
-DCMAKE_INSTALL_PREFIX=\"${INSTALL_PREFIX}\" /keepassxc/src && \
|
||||
-DCMAKE_INSTALL_PREFIX=\"${INSTALL_PREFIX}\" \
|
||||
-DKEEPASSXC_DIST_TYPE=AppImage /keepassxc/src && \
|
||||
make $MAKE_OPTIONS && make DESTDIR=/keepassxc/out/bin-release install/strip && \
|
||||
/keepassxc/src/AppImage-Recipe.sh "$APP_NAME" "$RELEASE_NAME""
|
||||
|
||||
@ -661,81 +734,54 @@ build() {
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# sign command
|
||||
# gpgsign command
|
||||
# -----------------------------------------------------------------------
|
||||
sign() {
|
||||
SIGN_FILES=()
|
||||
SIGNTOOL="signtool"
|
||||
SIGNTOOL_KEY=""
|
||||
|
||||
gpgsign() {
|
||||
local sign_files=()
|
||||
|
||||
while [ $# -ge 1 ]; do
|
||||
local arg="$1"
|
||||
case "$arg" in
|
||||
-f|--files)
|
||||
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
|
||||
SIGN_FILES+=("$2")
|
||||
sign_files+=("$2")
|
||||
shift
|
||||
done ;;
|
||||
|
||||
|
||||
-g|--gpg-key)
|
||||
GPG_KEY="$2"
|
||||
shift ;;
|
||||
|
||||
--signtool)
|
||||
SIGNTOOL="$2"
|
||||
shift ;;
|
||||
|
||||
--signtool-key)
|
||||
SIGNTOOL_KEY="$2"
|
||||
shift ;;
|
||||
|
||||
-h|--help)
|
||||
printUsage "sign"
|
||||
printUsage "gpgsign"
|
||||
exit ;;
|
||||
|
||||
|
||||
*)
|
||||
logError "Unknown option '$arg'\n"
|
||||
printUsage "sign"
|
||||
printUsage "gpgsign"
|
||||
exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -z "$SIGN_FILES" ]; then
|
||||
if [ -z "${sign_files}" ]; then
|
||||
logError "Missing arguments, --files is required!\n"
|
||||
printUsage "sign"
|
||||
printUsage "gpgsign"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$SIGNTOOL_KEY" && ! -f "$SIGNTOOL_KEY" ]]; then
|
||||
exitError "Signtool Key was not found!"
|
||||
elif [[ -f "$SIGNTOOL_KEY" && ! -x $(command -v "${SIGNTOOL}") ]]; then
|
||||
exitError "signtool program not found on PATH!"
|
||||
fi
|
||||
|
||||
for f in "${SIGN_FILES[@]}"; do
|
||||
for f in "${sign_files[@]}"; do
|
||||
if [ ! -f "$f" ]; then
|
||||
exitError "File '${f}' does not exist!"
|
||||
exitError "File '${f}' does not exist or is not a file!"
|
||||
fi
|
||||
|
||||
if [[ -n "$SIGNTOOL_KEY" && ${f: -4} == '.exe' ]]; then
|
||||
logInfo "Signing file '${f}' using signtool...\n"
|
||||
read -s -p "Signtool Key Password: " password
|
||||
echo
|
||||
"${SIGNTOOL}" sign -f "${SIGNTOOL_KEY}" -p ${password} -v -t http://timestamp.comodoca.com/authenticode ${f}
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Signing failed!"
|
||||
fi
|
||||
fi
|
||||
|
||||
logInfo "Signing file '${f}' using release key..."
|
||||
gpg --output "${f}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$f"
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Signing failed!"
|
||||
fi
|
||||
|
||||
|
||||
logInfo "Creating digest for file '${f}'..."
|
||||
local rp="$(realpath "$f")"
|
||||
local bname="$(basename "$f")"
|
||||
@ -746,6 +792,161 @@ sign() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# appsign command
|
||||
# -----------------------------------------------------------------------
|
||||
appsign() {
|
||||
local sign_files=()
|
||||
local signtool_key
|
||||
local codesign_identity
|
||||
|
||||
while [ $# -ge 1 ]; do
|
||||
local arg="$1"
|
||||
case "$arg" in
|
||||
-f|--files)
|
||||
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
|
||||
sign_files+=("$2")
|
||||
shift
|
||||
done ;;
|
||||
|
||||
-k|--signtool-key)
|
||||
signtool_key="$2"
|
||||
shift ;;
|
||||
|
||||
-i|--identity)
|
||||
codesign_identity="$2"
|
||||
shift ;;
|
||||
|
||||
-h|--help)
|
||||
printUsage "appsign"
|
||||
exit ;;
|
||||
|
||||
*)
|
||||
logError "Unknown option '$arg'\n"
|
||||
printUsage "appsign"
|
||||
exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -z "${sign_files}" ]; then
|
||||
logError "Missing arguments, --files is required!\n"
|
||||
printUsage "appsign"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for f in "${sign_files[@]}"; do
|
||||
if [ ! -f "${f}" ]; then
|
||||
exitError "File '${f}' does not exist or is not a file!"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$(uname -s)" == "Darwin" ]; then
|
||||
if [ -z "${codesign_identity}" ]; then
|
||||
logError "Missing arguments, --identity is required on macOS!\n"
|
||||
printUsage "appsign"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
checkCodesignCommandExists
|
||||
|
||||
local orig_dir="$(pwd)"
|
||||
for f in "${sign_files[@]}"; do
|
||||
if [[ ${f: -4} == '.dmg' ]]; then
|
||||
logInfo "Unpacking disk image '${f}'..."
|
||||
local tmp_dir="/tmp/KeePassXC_${RANDOM}"
|
||||
mkdir -p ${tmp_dir}/mnt
|
||||
hdiutil attach -quiet -noautoopen -mountpoint ${tmp_dir}/mnt "${f}"
|
||||
cd ${tmp_dir}
|
||||
cp -a ./mnt ./app
|
||||
hdiutil detach -quiet ${tmp_dir}/mnt
|
||||
|
||||
if [ ! -d ./app/KeePassXC.app ]; then
|
||||
cd "${orig_dir}"
|
||||
exitError "Unpacking failed!"
|
||||
fi
|
||||
|
||||
logInfo "Signing app using codesign..."
|
||||
codesign --sign "${codesign_identity}" --verbose --deep ./app/KeePassXC.app
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
cd "${orig_dir}"
|
||||
exitError "Signing failed!"
|
||||
fi
|
||||
|
||||
logInfo "Repacking disk image..."
|
||||
hdiutil create \
|
||||
-volname "KeePassXC" \
|
||||
-size $((1000 * ($(du -sk ./app | cut -f1) + 5000))) \
|
||||
-srcfolder ./app \
|
||||
-fs HFS+ \
|
||||
-fsargs "-c c=64,a=16,e=16" \
|
||||
-format UDBZ \
|
||||
"${tmp_dir}/$(basename "${f}")"
|
||||
cd "${orig_dir}"
|
||||
cp -f "${tmp_dir}/$(basename "${f}")" "${f}"
|
||||
rm -Rf ${tmp_dir}
|
||||
else
|
||||
logInfo "Skipping non-DMG file '${f}'..."
|
||||
fi
|
||||
done
|
||||
|
||||
elif [ "$(uname -o)" == "Msys" ]; then
|
||||
if [ -z "${signtool_key}" ]; then
|
||||
logError "Missing arguments, --signtool-key is required on Windows!\n"
|
||||
printUsage "appsign"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
checkOsslsigncodeCommandExists
|
||||
|
||||
if [[ ! -f "${signtool_key}" ]]; then
|
||||
exitError "Key file was not found!"
|
||||
fi
|
||||
|
||||
read -s -p "Key password: " password
|
||||
echo
|
||||
|
||||
for f in "${sign_files[@]}"; do
|
||||
if [[ ${f: -4} == ".exe" ]]; then
|
||||
logInfo "Signing file '${f}' using osslsigncode..."
|
||||
# output a signed exe; we have to use a different name due to osslsigntool limitations
|
||||
osslsigncode sign -pkcs12 "${signtool_key}" -pass "${password}" -n "KeePassXC" \
|
||||
-t "http://timestamp.comodoca.com/authenticode" -in "${f}" -out "${f}.signed"
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
rm -f "${f}.signed"
|
||||
exitError "Signing failed!"
|
||||
fi
|
||||
|
||||
# overwrite the original exe with the signed exe
|
||||
mv -f "${f}.signed" "${f}"
|
||||
elif [[ ${f: -4} == ".msi" ]]; then
|
||||
# Make sure we can find the signtool
|
||||
checkSigntoolCommandExists
|
||||
|
||||
# osslsigncode does not succeed at signing MSI files at this time...
|
||||
logInfo "Signing file '${f}' using Microsoft signtool..."
|
||||
signtool sign -f "${signtool_key}" -p "${password}" -d "KeePassXC" \
|
||||
-t "http://timestamp.comodoca.com/authenticode" "${f}"
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Signing failed!"
|
||||
fi
|
||||
else
|
||||
logInfo "Skipping non-executable file '${f}'..."
|
||||
fi
|
||||
done
|
||||
|
||||
else
|
||||
exitError "Unsupported platform for code signing!\n"
|
||||
fi
|
||||
|
||||
logInfo "All done!"
|
||||
}
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# parse global command line
|
||||
# -----------------------------------------------------------------------
|
||||
@ -758,8 +959,8 @@ if [ "" == "$MODE" ]; then
|
||||
elif [ "help" == "$MODE" ]; then
|
||||
printUsage "$1"
|
||||
exit
|
||||
elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "sign" == "$MODE" ]; then
|
||||
$MODE "$@"
|
||||
elif [ "check" == "$MODE" ] || [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "gpgsign" == "$MODE" ] || [ "appsign" == "$MODE" ]; then
|
||||
${MODE} "$@"
|
||||
else
|
||||
printUsage "$MODE"
|
||||
fi
|
||||
|
BIN
share/icons/application/16x16/actions/paperclip.png
Normal file
After Width: | Height: | Size: 352 B |
BIN
share/icons/application/16x16/actions/url-copy.png
Normal file
After Width: | Height: | Size: 900 B |
BIN
share/icons/application/22x22/actions/chronometer.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
share/icons/application/22x22/actions/paperclip.png
Normal file
After Width: | Height: | Size: 421 B |
BIN
share/icons/application/22x22/actions/url-copy.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
share/icons/application/32x32/actions/document-encrypt.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
share/icons/application/32x32/actions/paperclip.png
Normal file
After Width: | Height: | Size: 559 B |
BIN
share/icons/application/32x32/apps/utilities-terminal.png
Normal file
After Width: | Height: | Size: 958 B |
BIN
share/icons/svg/paperclip.svgz
Normal file
BIN
share/icons/svg/utilities-terminal.svgz
Normal file
@ -1,5 +1,4 @@
|
||||
[General]
|
||||
ShowToolbar=true
|
||||
RememberLastDatabases=true
|
||||
RememberLastKeyFiles=true
|
||||
OpenPreviousDatabasesOnStartup=true
|
||||
@ -17,6 +16,7 @@ LastOpenedDatabases=@Invalid()
|
||||
[GUI]
|
||||
Language=system
|
||||
ShowTrayIcon=false
|
||||
DarkTrayIcon=false
|
||||
MinimizeToTray=false
|
||||
MinimizeOnClose=false
|
||||
MinimizeOnStartup=false
|
||||
|
@ -1,16 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--Copyright 2017 KeePassXC Team <team@keepassxc.org> -->
|
||||
<component type="desktop-application">
|
||||
<id>org.keepassxc</id>
|
||||
<id>org.keepassxc.KeePassXC.desktop</id>
|
||||
<name>KeePassXC</name>
|
||||
<metadata_license>CC-BY-3.0</metadata_license>
|
||||
<project_license>GPL-3.0+</project_license>
|
||||
<icon type="stock">keepassxc</icon>
|
||||
<url type="homepage">https://keepassxc.org</url>
|
||||
<mimetypes>
|
||||
<mimetype>application/x-keepass2</mimetype>
|
||||
</mimetypes>
|
||||
<summary>Community-driven port of the Windows application “KeePass Password Safe”</summary>
|
||||
<developer_name>KeePassXC Team</developer_name>
|
||||
<description>
|
||||
<p>
|
||||
KeePassXC is an application for people with extremely high demands on secure
|
||||
@ -19,54 +18,92 @@
|
||||
</p>
|
||||
</description>
|
||||
|
||||
<launchable type="desktop-id">org.keepassxc.desktop</launchable>
|
||||
<launchable type="desktop-id">org.keepassxc.KeePassXC.desktop</launchable>
|
||||
|
||||
<url type="homepage">https://keepassxc.org</url>
|
||||
<url type="bugtracker">https://github.com/keepassxreboot/keepassxc/issues</url>
|
||||
<url type="faq">https://keepassxc.org/docs#faq</url>
|
||||
<url type="help">https://keepassxc.org/docs</url>
|
||||
<url type="translate">https://www.transifex.com/keepassxc/keepassxc</url>
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_001.png</image>
|
||||
<caption>Create, Import or Open Databases</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_002.png</image>
|
||||
<caption>Organize with Groups and Entries</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_003.png</image>
|
||||
<caption>Database Entry</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_004.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_005.png</image>
|
||||
<caption>Icon Selection for Entry</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_006.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_007.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_008.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_009.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_010.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_011.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_012.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_013.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://keepassxc.org/images/screenshots/linux/screen_014.png</image>
|
||||
<caption>Password Generator</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.3.0" date="2018-02-27">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Add support for KDBX 4.0, Argon2 and ChaCha20 [#148, #1179, #1230, #1494]</li>
|
||||
<li>Add SSH Agent feature [#1098, #1450, #1463]</li>
|
||||
<li>Add preview panel with details of the selected entry [#879, #1338]</li>
|
||||
<li>Add more and configurable columns to entry table and allow copying of values by double click [#1305]</li>
|
||||
<li>Add KeePassXC-Browser API as a replacement for KeePassHTTP [#608]</li>
|
||||
<li>Deprecate KeePassHTTP [#1392]</li>
|
||||
<li>Add support for Steam one-time passwords [#1206]</li>
|
||||
<li>Add support for multiple Auto-Type sequences for a single entry [#1390]</li>
|
||||
<li>Adjust YubiKey HMAC-SHA1 challenge-response key generation for KDBX 4.0 [#1060]</li>
|
||||
<li>Replace qHttp with cURL for website icon downloads [#1460]</li>
|
||||
<li>Remove lock file [#1231]</li>
|
||||
<li>Add option to create backup file before saving [#1385]</li>
|
||||
<li>Ask to save a generated password before closing the entry password generator [#1499]</li>
|
||||
<li>Resolve placeholders recursively [#1078]</li>
|
||||
<li>Add Auto-Type button to the toolbar [#1056]</li>
|
||||
<li>Improve window focus handling for Auto-Type dialogs [#1204, #1490]</li>
|
||||
<li>Auto-Type dialog and password generator can now be exited with ESC [#1252, #1412]</li>
|
||||
<li>Add optional dark tray icon [#1154]</li>
|
||||
<li>Add new "Unsafe saving" option to work around saving problems with file sync services [#1385]</li>
|
||||
<li>Add IBus support to AppImage and additional image formats to Windows builds [#1534, #1537]</li>
|
||||
<li>Add diceware password generator to CLI [#1406]</li>
|
||||
<li>Add --key-file option to CLI [#816, #824]</li>
|
||||
<li>Add DBus interface for opening and closing KeePassXC databases [#283]</li>
|
||||
<li>Add KDBX compression options to database settings [#1419]</li>
|
||||
<li>Discourage use of old fixed-length key files in favor of arbitrary files [#1326, #1327]</li>
|
||||
<li>Correct reference resolution in entry fields [#1486]</li>
|
||||
<li>Fix window state and recent databases not being remembered on exit [#1453]</li>
|
||||
<li>Correct history item generation when configuring TOTP for an entry [#1446]</li>
|
||||
<li>Correct multiple TOTP bugs [#1414]</li>
|
||||
<li>Automatic saving after every change is now a default [#279]</li>
|
||||
<li>Allow creation of new entries during search [#1398]</li>
|
||||
<li>Correct menu issues on macOS [#1335]</li>
|
||||
<li>Allow compilation on OpenBSD [#1328]</li>
|
||||
<li>Improve entry attachments view [#1139, #1298]</li>
|
||||
<li>Fix auto lock for Gnome and Xfce [#910, #1249]</li>
|
||||
<li>Don't remember key files in file dialogs when the setting is disabled [#1188]</li>
|
||||
<li>Improve database merging and conflict resolution [#807, #1165]</li>
|
||||
<li>Fix macOS pasteboard issues [#1202]</li>
|
||||
<li>Improve startup times on some platforms [#1205]</li>
|
||||
<li>Hide the notes field by default [#1124]</li>
|
||||
<li>Toggle main window by clicking tray icon with the middle mouse button [#992]</li>
|
||||
<li>Fix custom icons not copied over when databases are merged [#1008]</li>
|
||||
<li>Allow use of DEL key to delete entries [#914]</li>
|
||||
<li>Correct intermittent crash due to stale history items [#1527]</li>
|
||||
<li>Sanitize newline characters in title, username and URL fields [#1502]</li>
|
||||
<li>Reopen previously opened databases in correct order [#774]</li>
|
||||
<li>Use system's zxcvbn library if available [#701]</li>
|
||||
<li>Implement various i18n improvements [#690, #875, #1436]</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="2.2.4" date="2017-12-13">
|
||||
<description>
|
||||
<ul>
|
||||
@ -81,6 +118,7 @@
|
||||
</release>
|
||||
<release version="2.2.2" date="2017-10-22">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Fixed entries with empty URLs being reported to KeePassHTTP clients [#1031]</li>
|
||||
<li>Fixed YubiKey detection and enabled CLI tool for AppImage binary [#1100]</li>
|
||||
@ -100,6 +138,7 @@
|
||||
</release>
|
||||
<release version="2.2.1" date="2017-10-01">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Corrected multiple snap issues [#934, #1011]</li>
|
||||
<li>Corrected multiple custom icon issues [#708, #719, #994]</li>
|
||||
@ -116,6 +155,7 @@
|
||||
</release>
|
||||
<release version="2.2.0" date="2017-06-23">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Added YubiKey 2FA integration for unlocking databases [#127]</li>
|
||||
<li>Added TOTP support [#519]</li>
|
||||
@ -151,6 +191,7 @@
|
||||
</release>
|
||||
<release version="2.1.4" date="2017-04-09">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Bumped KeePassHTTP version to 1.8.4.2</li>
|
||||
<li>KeePassHTTP confirmation window comes to foreground [#466]</li>
|
||||
@ -159,6 +200,7 @@
|
||||
</release>
|
||||
<release version="2.1.3" date="2017-03-03">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Fix possible overflow in zxcvbn library [#363]</li>
|
||||
<li>Revert HiDPI setting to avoid problems on laptop screens [#332]</li>
|
||||
@ -173,6 +215,7 @@
|
||||
</release>
|
||||
<release version="2.1.2" date="2017-02-17">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Ask for save location when creating a new database [#302]</li>
|
||||
<li>Remove Libmicrohttpd dependency to clean up the code and ensure better OS X compatibility [#317, #265]</li>
|
||||
@ -190,6 +233,7 @@
|
||||
</release>
|
||||
<release version="2.1.1" date="2017-02-06">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Enabled HTTP plugin build; plugin is disabled by default and limited to localhost [#147]</li>
|
||||
<li>Escape HTML in dialog boxes [#247]</li>
|
||||
@ -202,6 +246,7 @@
|
||||
</release>
|
||||
<release version="2.1.0" date="2017-01-22">
|
||||
<description>
|
||||
<p>Changes included in this release:</p>
|
||||
<ul>
|
||||
<li>Show unlock dialog when using autotype on a closed database [#10, #89]</li>
|
||||
<li>Show different tray icon when database is locked [#37, #46]</li>
|
||||
|
BIN
share/macosx/DS_Store.in
Normal file
@ -29,7 +29,7 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${KEEPASSXC_VERSION_NUM}</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright 2016-2017 KeePassXC Development Team</string>
|
||||
<string>Copyright 2016-2018 KeePassXC Development Team</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
BIN
share/macosx/background.tiff
Normal file
@ -1,3 +1,4 @@
|
||||
# Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
# Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
@ -17,10 +18,20 @@ file(GLOB TRANSLATION_FILES *.ts)
|
||||
get_filename_component(TRANSLATION_EN_ABS keepassx_en.ts ABSOLUTE)
|
||||
list(REMOVE_ITEM TRANSLATION_FILES keepassx_en.ts)
|
||||
list(REMOVE_ITEM TRANSLATION_FILES ${TRANSLATION_EN_ABS})
|
||||
message(STATUS "${TRANSLATION_FILES}")
|
||||
message(STATUS "Including translations...\n")
|
||||
|
||||
qt5_add_translation(QM_FILES ${TRANSLATION_FILES})
|
||||
|
||||
if(MINGW)
|
||||
file(GLOB QTBASE_TRANSLATIONS ${Qt5_PREFIX}/share/qt5/translations/qtbase_*.qm)
|
||||
elseif(APPLE OR KEEPASSXC_DIST_APPIMAGE)
|
||||
file(GLOB QTBASE_TRANSLATIONS
|
||||
/usr/share/qt/translations/qtbase_*.qm
|
||||
/usr/share/qt5/translations/qtbase_*.qm
|
||||
${Qt5_PREFIX}/translations/qtbase_*.qm)
|
||||
endif()
|
||||
set(QM_FILES ${QM_FILES} ${QTBASE_TRANSLATIONS})
|
||||
|
||||
install(FILES ${QM_FILES} DESTINATION ${DATA_INSTALL_DIR}/translations)
|
||||
add_custom_target(translations DEPENDS ${QM_FILES})
|
||||
add_dependencies(${PROGNAME} translations)
|
||||
|
4093
share/translations/keepassx_ar.ts
Normal file
4120
share/translations/keepassx_en_US.ts
Normal file
@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en_US">
|
||||
<context>
|
||||
<name>DatabaseWidget</name>
|
||||
<message numerus="yes">
|
||||
<source>Do you really want to move %n entry(s) to the recycle bin?</source>
|
||||
<translation>
|
||||
<numerusform>Do you really want to move %n entry to the recycle bin?</numerusform>
|
||||
<numerusform>Do you really want to move %n entries to the recycle bin?</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditEntryWidget</name>
|
||||
<message numerus="yes">
|
||||
<source>%n week(s)</source>
|
||||
<translation>
|
||||
<numerusform>%n week</numerusform>
|
||||
<numerusform>%n weeks</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>%n month(s)</source>
|
||||
<translation>
|
||||
<numerusform>%n month</numerusform>
|
||||
<numerusform>%n months</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditWidgetIcons</name>
|
||||
<message numerus="yes">
|
||||
<source>Can't delete icon. Still used by %n item(s).</source>
|
||||
<translation type="vanished">
|
||||
<numerusform>Can't delete icon. Still used by %n item.</numerusform>
|
||||
<numerusform>Can't delete icon. Still used by %n items.</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
4073
share/translations/keepassx_ro.ts
Normal file
4083
share/translations/keepassx_sr.ts
Normal file
4078
share/translations/keepassx_th.ts
Normal file
@ -32,10 +32,12 @@ elif [ "$1" == "update" ]; then
|
||||
PULL=false
|
||||
elif [ "$1" != "" ]; then
|
||||
echo "Unknown command '${1}'"
|
||||
echo "Usage: $(basename $0) [update|pull|push]"
|
||||
echo "Usage: $(basename $0) [update|pull|push] [additional tx options]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
shift
|
||||
|
||||
cd "${BASEDIR}/../.."
|
||||
|
||||
if $UPDATE; then
|
||||
@ -47,18 +49,17 @@ if $UPDATE; then
|
||||
LUPDATE=lupdate
|
||||
fi
|
||||
$LUPDATE -no-ui-lines -disable-heuristic similartext -locations none -no-obsolete src -ts share/translations/keepassx_en.ts
|
||||
$LUPDATE -no-ui-lines -disable-heuristic similartext -locations none -pluralonly src -ts share/translations/keepassx_en_plurals.ts
|
||||
echo
|
||||
fi
|
||||
|
||||
if $PUSH; then
|
||||
echo "Pushing English source files to Transifex..."
|
||||
tx push -s
|
||||
tx push -s $@
|
||||
echo
|
||||
fi
|
||||
|
||||
if $PULL; then
|
||||
echo "Pulling translations from Transifex..."
|
||||
tx pull -af --minimum-perc=40
|
||||
tx pull -af --minimum-perc=40 $@
|
||||
echo
|
||||
fi
|
||||
|
BIN
share/windows/wix-banner.bmp
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
share/windows/wix-dialog.bmp
Normal file
After Width: | Height: | Size: 451 KiB |
8
share/windows/wix-patch.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CPackWiXPatch>
|
||||
<CPackWiXFragment Id="CM_FP_KeePassXC.exe">
|
||||
<Shortcut Id="CM_SP_KeePassXC.exe" Directory="ProgramMenuFolder"
|
||||
Name="KeePassXC" Icon="ProductIcon.ico"
|
||||
WorkingDirectory="INSTALL_ROOT" Advertise="yes" />
|
||||
</CPackWiXFragment>
|
||||
</CPackWiXPatch>
|
67
share/windows/wix-template.xml
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?include "cpack_variables.wxi"?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
|
||||
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
|
||||
RequiredVersion="3.6.3303.0">
|
||||
|
||||
<Product Id="$(var.CPACK_WIX_PRODUCT_GUID)"
|
||||
Name="$(var.CPACK_PACKAGE_NAME)"
|
||||
Language="1033"
|
||||
Version="$(var.CPACK_PACKAGE_VERSION)"
|
||||
Manufacturer="$(var.CPACK_PACKAGE_VENDOR)"
|
||||
UpgradeCode="$(var.CPACK_WIX_UPGRADE_GUID)">
|
||||
|
||||
<Package InstallScope="perMachine" InstallerVersion="301" Compressed="yes"/>
|
||||
|
||||
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes"/>
|
||||
|
||||
<MajorUpgrade
|
||||
Schedule="afterInstallInitialize"
|
||||
AllowSameVersionUpgrades="yes"
|
||||
DowngradeErrorMessage="A later version of [ProductName] is already installed. Setup will now exit."/>
|
||||
|
||||
<WixVariable Id="WixUILicenseRtf" Value="$(var.CPACK_WIX_LICENSE_RTF)"/>
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALL_ROOT"/>
|
||||
|
||||
<?ifdef CPACK_WIX_PRODUCT_ICON?>
|
||||
<Property Id="ARPPRODUCTICON">ProductIcon.ico</Property>
|
||||
<Icon Id="ProductIcon.ico" SourceFile="$(var.CPACK_WIX_PRODUCT_ICON)"/>
|
||||
<?endif?>
|
||||
|
||||
<?ifdef CPACK_WIX_UI_BANNER?>
|
||||
<WixVariable Id="WixUIBannerBmp" Value="$(var.CPACK_WIX_UI_BANNER)"/>
|
||||
<?endif?>
|
||||
|
||||
<?ifdef CPACK_WIX_UI_DIALOG?>
|
||||
<WixVariable Id="WixUIDialogBmp" Value="$(var.CPACK_WIX_UI_DIALOG)"/>
|
||||
<?endif?>
|
||||
|
||||
<FeatureRef Id="ProductFeature"/>
|
||||
|
||||
<UIRef Id="$(var.CPACK_WIX_UI_REF)" />
|
||||
|
||||
<?include "properties.wxi"?>
|
||||
<?include "product_fragment.wxi"?>
|
||||
|
||||
<DirectoryRef Id="TARGETDIR">
|
||||
<Directory Id="ProgramMenuFolder" />
|
||||
</DirectoryRef>
|
||||
|
||||
<Property Id="WixSilentExecCmdLine" Value='"Taskkill" /IM KeePassXC.exe'/>
|
||||
<CustomAction Id="KillKeePassXCInstall" BinaryKey="WixCA" DllEntry="WixSilentExec" Execute="immediate" Return="ignore"/>
|
||||
<CustomAction Id="KillKeePassXCUninstall" BinaryKey="WixCA" DllEntry="WixSilentExec" Execute="immediate" Return="ignore"/>
|
||||
|
||||
<Property Id="WixQuietExecCmdLine" Value='"Taskkill" /IM keepassxc-proxy.exe /F'/>
|
||||
<CustomAction Id="KillProxyInstall" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="immediate" Return="ignore"/>
|
||||
<CustomAction Id="KillProxyUninstall" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="immediate" Return="ignore"/>
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="KillKeePassXCInstall" After="InstallInitialize"/>
|
||||
<Custom Action="KillProxyInstall" After="InstallInitialize"/>
|
||||
<Custom Action="KillKeePassXCUninstall" Before="InstallValidate">Installed</Custom>
|
||||
<Custom Action="KillProxyUninstall" Before="InstallValidate">Installed</Custom>
|
||||
</InstallExecuteSequence>
|
||||
</Product>
|
||||
</Wix>
|
@ -1,5 +1,5 @@
|
||||
name: keepassxc
|
||||
version: 2.2.4
|
||||
version: 2.3.0
|
||||
grade: stable
|
||||
summary: Community-driven port of the Windows application “KeePass Password Safe”
|
||||
description: |
|
||||
@ -16,6 +16,9 @@ apps:
|
||||
cli:
|
||||
command: keepassxc-cli
|
||||
plugs: [gsettings, home, removable-media, raw-usb]
|
||||
proxy:
|
||||
command: keepassxc-proxy
|
||||
plugs: [home]
|
||||
|
||||
parts:
|
||||
keepassxc:
|
||||
@ -26,9 +29,7 @@ parts:
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
- -DKEEPASSXC_DIST_TYPE=Snap
|
||||
- -DWITH_TESTS=OFF
|
||||
- -DWITH_XC_AUTOTYPE=ON
|
||||
- -DWITH_XC_HTTP=ON
|
||||
- -DWITH_XC_YUBIKEY=ON
|
||||
- -DWITH_XC_ALL=ON
|
||||
build-packages:
|
||||
- g++
|
||||
- libgcrypt20-dev
|
||||
@ -41,8 +42,15 @@ parts:
|
||||
- libxtst-dev
|
||||
- libyubikey-dev
|
||||
- libykpers-1-dev
|
||||
- libcurl4-openssl-dev
|
||||
- libsodium-dev
|
||||
stage-packages:
|
||||
- dbus
|
||||
- qttranslations5-l10n # common translations
|
||||
install: |
|
||||
sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/org.keepassxc.KeePassXC.desktop
|
||||
organize:
|
||||
usr/share/qt5/translations/*.qm: usr/share/keepassxc/translations/
|
||||
after: [desktop-qt5]
|
||||
|
||||
# Redefine desktop-qt5 stage packages to work with Ubuntu 17.04
|
||||
@ -52,19 +60,11 @@ parts:
|
||||
- ttf-ubuntu-font-family
|
||||
- dmz-cursor-theme
|
||||
- light-themes
|
||||
- adwaita-icon-theme
|
||||
- gnome-themes-standard
|
||||
- shared-mime-info
|
||||
- libqt5gui5
|
||||
- libgdk-pixbuf2.0-0
|
||||
- libqt5svg5 # for loading icon themes which are svg
|
||||
- locales-all
|
||||
|
||||
# Overcome limitation in snapd to support URL loading (CTRL+U)
|
||||
# client needs to install "snapd-xdg-open" on their system
|
||||
snapd-xdg-open:
|
||||
source: https://github.com/ubuntu-core/snapd-xdg-open.git
|
||||
source-depth: 1
|
||||
plugin: nil
|
||||
install: |
|
||||
install -D -t $SNAPCRAFT_PART_INSTALL/usr/bin/ data/xdg-open
|
||||
stage-packages:
|
||||
- dbus
|
||||
- xdg-user-dirs
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
# Copyright (C) 2017 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
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -27,15 +27,24 @@ if (NOT GIT_HEAD OR NOT GIT_DESCRIBE)
|
||||
set(GIT_DESCRIBE "")
|
||||
endif()
|
||||
|
||||
find_library(ZXCVBN_LIBRARIES zxcvbn)
|
||||
if(NOT ZXCVBN_LIBRARIES)
|
||||
add_library(zxcvbn STATIC zxcvbn/zxcvbn.c)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/zxcvbn)
|
||||
set(ZXCVBN_LIBRARIES zxcvbn)
|
||||
endif(NOT ZXCVBN_LIBRARIES)
|
||||
|
||||
configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY)
|
||||
|
||||
set(keepassx_SOURCES
|
||||
core/AutoTypeAssociations.cpp
|
||||
core/AsyncTask.h
|
||||
core/AutoTypeMatch.cpp
|
||||
core/Config.cpp
|
||||
core/CsvParser.cpp
|
||||
core/CustomData.cpp
|
||||
core/Database.cpp
|
||||
core/DatabaseIcons.cpp
|
||||
core/Endian.cpp
|
||||
core/Entry.cpp
|
||||
core/EntryAttachments.cpp
|
||||
core/EntryAttributes.cpp
|
||||
@ -55,30 +64,39 @@ set(keepassx_SOURCES
|
||||
core/ScreenLockListenerPrivate.cpp
|
||||
core/TimeDelta.cpp
|
||||
core/TimeInfo.cpp
|
||||
core/ToDbExporter.cpp
|
||||
core/Tools.cpp
|
||||
core/Translator.cpp
|
||||
core/Uuid.cpp
|
||||
core/Base32.h
|
||||
core/Base32.cpp
|
||||
cli/PasswordInput.cpp
|
||||
cli/PasswordInput.h
|
||||
cli/Utils.cpp
|
||||
cli/Utils.h
|
||||
crypto/Crypto.cpp
|
||||
crypto/CryptoHash.cpp
|
||||
crypto/Random.cpp
|
||||
crypto/SymmetricCipher.cpp
|
||||
crypto/SymmetricCipherBackend.h
|
||||
crypto/SymmetricCipherGcrypt.cpp
|
||||
crypto/kdf/Kdf.cpp
|
||||
crypto/kdf/Kdf_p.h
|
||||
crypto/kdf/AesKdf.cpp
|
||||
crypto/kdf/Argon2Kdf.cpp
|
||||
format/CsvExporter.cpp
|
||||
format/KeePass1.h
|
||||
format/KeePass1Reader.cpp
|
||||
format/KeePass2.h
|
||||
format/KeePass2.cpp
|
||||
format/KeePass2RandomStream.cpp
|
||||
format/KeePass2Reader.cpp
|
||||
format/KeePass2Repair.cpp
|
||||
format/KdbxReader.cpp
|
||||
format/KdbxWriter.cpp
|
||||
format/KdbxXmlReader.cpp
|
||||
format/KeePass2Reader.cpp
|
||||
format/KeePass2Writer.cpp
|
||||
format/KeePass2XmlReader.cpp
|
||||
format/KeePass2XmlWriter.cpp
|
||||
format/Kdbx3Reader.cpp
|
||||
format/Kdbx3Writer.cpp
|
||||
format/Kdbx4Reader.cpp
|
||||
format/Kdbx4Writer.cpp
|
||||
format/KdbxXmlWriter.cpp
|
||||
gui/AboutDialog.cpp
|
||||
gui/Application.cpp
|
||||
gui/CategoryListWidget.cpp
|
||||
@ -91,12 +109,14 @@ set(keepassx_SOURCES
|
||||
gui/DatabaseTabWidget.cpp
|
||||
gui/DatabaseWidget.cpp
|
||||
gui/DatabaseWidgetStateSync.cpp
|
||||
gui/DetailsWidget.cpp
|
||||
gui/DialogyWidget.cpp
|
||||
gui/DragTabBar.cpp
|
||||
gui/EditWidget.cpp
|
||||
gui/EditWidgetIcons.cpp
|
||||
gui/EditWidgetProperties.cpp
|
||||
gui/FileDialog.cpp
|
||||
gui/Font.cpp
|
||||
gui/IconModels.cpp
|
||||
gui/KeePass1OpenWidget.cpp
|
||||
gui/KMessageWidget.cpp
|
||||
@ -114,13 +134,17 @@ set(keepassx_SOURCES
|
||||
gui/UnlockDatabaseWidget.cpp
|
||||
gui/UnlockDatabaseDialog.cpp
|
||||
gui/WelcomeWidget.cpp
|
||||
gui/widgets/ElidedLabel.cpp
|
||||
gui/csvImport/CsvImportWidget.cpp
|
||||
gui/csvImport/CsvImportWizard.cpp
|
||||
gui/csvImport/CsvParserModel.cpp
|
||||
gui/entry/AutoTypeAssociationsModel.cpp
|
||||
gui/entry/AutoTypeMatchModel.cpp
|
||||
gui/entry/AutoTypeMatchView.cpp
|
||||
gui/entry/EditEntryWidget.cpp
|
||||
gui/entry/EditEntryWidget_p.h
|
||||
gui/entry/EntryAttachmentsModel.cpp
|
||||
gui/entry/EntryAttachmentsWidget.cpp
|
||||
gui/entry/EntryAttributesModel.cpp
|
||||
gui/entry/EntryHistoryModel.cpp
|
||||
gui/entry/EntryModel.cpp
|
||||
@ -129,13 +153,13 @@ set(keepassx_SOURCES
|
||||
gui/group/GroupModel.cpp
|
||||
gui/group/GroupView.cpp
|
||||
keys/CompositeKey.cpp
|
||||
keys/CompositeKey_p.h
|
||||
keys/drivers/YubiKey.h
|
||||
keys/FileKey.cpp
|
||||
keys/Key.h
|
||||
keys/PasswordKey.cpp
|
||||
keys/YkChallengeResponseKey.cpp
|
||||
streams/HashedBlockStream.cpp
|
||||
streams/HmacBlockStream.cpp
|
||||
streams/LayeredStream.cpp
|
||||
streams/qtiocompressor.cpp
|
||||
streams/StoreDataStream.cpp
|
||||
@ -147,12 +171,15 @@ if(APPLE)
|
||||
set(keepassx_SOURCES ${keepassx_SOURCES}
|
||||
core/ScreenLockListenerMac.h
|
||||
core/ScreenLockListenerMac.cpp
|
||||
core/MacPasteboard.h
|
||||
core/MacPasteboard.cpp
|
||||
)
|
||||
endif()
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
||||
set(keepassx_SOURCES ${keepassx_SOURCES}
|
||||
core/ScreenLockListenerDBus.h
|
||||
core/ScreenLockListenerDBus.cpp
|
||||
gui/MainWindowAdaptor.cpp
|
||||
)
|
||||
endif()
|
||||
if(MINGW)
|
||||
@ -166,18 +193,33 @@ set(keepassx_SOURCES_MAINEXE
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_feature_info(AutoType WITH_XC_AUTOTYPE "Automatic password typing")
|
||||
add_feature_info(KeePassHTTP WITH_XC_HTTP "Browser integration compatible with ChromeIPass and PassIFox")
|
||||
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(KeePassHTTP WITH_XC_HTTP "Browser integration compatible with ChromeIPass and PassIFox (deprecated, implies Networking)")
|
||||
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
|
||||
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
|
||||
|
||||
add_subdirectory(http)
|
||||
if(WITH_XC_HTTP)
|
||||
set(keepasshttp_LIB keepasshttp qhttp Qt5::Network)
|
||||
if(WITH_XC_NETWORKING)
|
||||
find_package(CURL REQUIRED)
|
||||
endif()
|
||||
|
||||
set(BROWSER_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/browser)
|
||||
add_subdirectory(browser)
|
||||
add_subdirectory(proxy)
|
||||
if(WITH_XC_BROWSER)
|
||||
set(keepassxcbrowser_LIB keepassxcbrowser)
|
||||
endif()
|
||||
|
||||
add_subdirectory(autotype)
|
||||
add_subdirectory(cli)
|
||||
|
||||
add_subdirectory(sshagent)
|
||||
if(WITH_XC_SSHAGENT)
|
||||
set(sshagent_LIB sshagent)
|
||||
endif()
|
||||
|
||||
set(autotype_SOURCES
|
||||
core/Tools.cpp
|
||||
autotype/AutoType.cpp
|
||||
@ -203,32 +245,34 @@ else()
|
||||
list(APPEND keepassx_SOURCES keys/drivers/YubiKeyStub.cpp)
|
||||
endif()
|
||||
|
||||
add_library(zxcvbn STATIC zxcvbn/zxcvbn.cpp)
|
||||
target_link_libraries(zxcvbn)
|
||||
|
||||
add_library(autotype STATIC ${autotype_SOURCES})
|
||||
target_link_libraries(autotype Qt5::Core Qt5::Widgets)
|
||||
|
||||
set(autotype_LIB autotype)
|
||||
|
||||
add_library(keepassx_core STATIC ${keepassx_SOURCES})
|
||||
|
||||
set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE)
|
||||
target_link_libraries(keepassx_core
|
||||
${keepasshttp_LIB}
|
||||
${autotype_LIB}
|
||||
${YUBIKEY_LIBRARIES}
|
||||
zxcvbn
|
||||
autotype
|
||||
${keepassxchttp_LIB}
|
||||
${keepassxcbrowser_LIB}
|
||||
${sshagent_LIB}
|
||||
Qt5::Core
|
||||
Qt5::Network
|
||||
Qt5::Concurrent
|
||||
Qt5::Widgets
|
||||
${CURL_LIBRARIES}
|
||||
${YUBIKEY_LIBRARIES}
|
||||
${ZXCVBN_LIBRARIES}
|
||||
${ARGON2_LIBRARIES}
|
||||
${GCRYPT_LIBRARIES}
|
||||
${GPGERROR_LIBRARIES}
|
||||
${ZLIB_LIBRARIES})
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(keepassx_core "-framework Foundation")
|
||||
if(Qt5MacExtras_FOUND)
|
||||
target_link_libraries(keepassx_core Qt5::MacExtras)
|
||||
endif()
|
||||
endif()
|
||||
if (UNIX AND NOT APPLE)
|
||||
target_link_libraries(keepassx_core Qt5::DBus)
|
||||
@ -259,13 +303,7 @@ if(APPLE AND WITH_APP_BUNDLE)
|
||||
set_target_properties(${PROGNAME} PROPERTIES
|
||||
MACOSX_BUNDLE ON
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist)
|
||||
endif()
|
||||
|
||||
install(TARGETS ${PROGNAME}
|
||||
BUNDLE DESTINATION . COMPONENT Runtime
|
||||
RUNTIME DESTINATION ${BIN_INSTALL_DIR} COMPONENT Runtime)
|
||||
|
||||
if(APPLE AND WITH_APP_BUNDLE)
|
||||
if(QT_MAC_USE_COCOA AND EXISTS "${QT_LIBRARY_DIR}/Resources/qt_menu.nib")
|
||||
install(DIRECTORY "${QT_LIBRARY_DIR}/Resources/qt_menu.nib"
|
||||
DESTINATION "${DATA_INSTALL_DIR}")
|
||||
@ -273,22 +311,25 @@ if(APPLE AND WITH_APP_BUNDLE)
|
||||
|
||||
set(CPACK_GENERATOR "DragNDrop")
|
||||
set(CPACK_DMG_FORMAT "UDBZ")
|
||||
set(CPACK_DMG_DS_STORE "${CMAKE_SOURCE_DIR}/share/macosx/DS_Store.in")
|
||||
set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/share/macosx/background.tiff")
|
||||
set(CPACK_DMG_VOLUME_NAME "${PROGNAME}")
|
||||
set(CPACK_SYSTEM_NAME "OSX")
|
||||
set(CPACK_STRIP_FILES ON)
|
||||
set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION}")
|
||||
include(CPack)
|
||||
|
||||
if(NOT DEFINED QT_BINARY_DIR)
|
||||
set(QT_BINARY_DIR "/usr/local/opt/qt5/bin" CACHE PATH "QT binary folder")
|
||||
endif()
|
||||
add_custom_command(TARGET ${PROGNAME}
|
||||
POST_BUILD
|
||||
COMMAND ${QT_BINARY_DIR}/macdeployqt ${PROGNAME}.app
|
||||
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
|
||||
COMMENT "Deploying app bundle")
|
||||
endif()
|
||||
|
||||
install(TARGETS ${PROGNAME}
|
||||
BUNDLE DESTINATION . COMPONENT Runtime
|
||||
RUNTIME DESTINATION ${BIN_INSTALL_DIR} COMPONENT Runtime)
|
||||
|
||||
if(MINGW)
|
||||
if(${CMAKE_SIZEOF_VOID_P} EQUAL "8")
|
||||
set(OUTPUT_FILE_POSTFIX "Win64")
|
||||
@ -296,14 +337,23 @@ if(MINGW)
|
||||
set(OUTPUT_FILE_POSTFIX "Win32")
|
||||
endif()
|
||||
|
||||
# We have to copy the license file in the configuration phase.
|
||||
# CMake checks that CPACK_RESOURCE_FILE_LICENSE actually exists and
|
||||
# we have to copy it because WiX needs it to have a .txt extension.
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E copy
|
||||
"${CMAKE_SOURCE_DIR}/LICENSE.GPL-2"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/INSTALLER_LICENSE.txt")
|
||||
|
||||
string(REGEX REPLACE "-snapshot$" "" KEEPASSXC_VERSION_CLEAN ${KEEPASSXC_VERSION})
|
||||
|
||||
set(CPACK_GENERATOR "ZIP;NSIS")
|
||||
set(CPACK_STRIP_FILES ON)
|
||||
set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION}-${OUTPUT_FILE_POSTFIX}")
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${PROGNAME})
|
||||
set(CPACK_PACKAGE_VERSION ${KEEPASSXC_VERSION})
|
||||
set(CPACK_PACKAGE_VERSION ${KEEPASSXC_VERSION_CLEAN})
|
||||
set(CPACK_PACKAGE_VENDOR "${PROGNAME} Team")
|
||||
string(REGEX REPLACE "/" "\\\\\\\\" CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/share/windows/installer-header.bmp")
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE.GPL-2")
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_BINARY_DIR}/INSTALLER_LICENSE.txt")
|
||||
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
|
||||
set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/share/windows/keepassxc.ico")
|
||||
set(CPACK_NSIS_MUI_UNIICON "${CPACK_NSIS_MUI_ICON}")
|
||||
@ -312,10 +362,22 @@ if(MINGW)
|
||||
set(CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP "${CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP}")
|
||||
set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${PROGNAME}.lnk' '$INSTDIR\\\\${PROGNAME}.exe'")
|
||||
set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\${PROGNAME}.lnk'")
|
||||
set(CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS "ExecWait 'Taskkill /IM KeePassXC.exe'")
|
||||
set(CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS "${CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS}\nExecWait 'Taskkill /IM keepassxc-proxy.exe /F'")
|
||||
set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "ExecWait 'Taskkill /IM KeePassXC.exe'")
|
||||
set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS}\nExecWait 'Taskkill /IM keepassxc-proxy.exe /F'")
|
||||
set(CPACK_NSIS_URL_INFO_ABOUT "https://keepassxc.org")
|
||||
set(CPACK_NSIS_DISPLAY_NAME ${PROGNAME})
|
||||
set(CPACK_NSIS_PACKAGE_NAME "${PROGNAME} v${KEEPASSXC_VERSION}")
|
||||
set(CPACK_NSIS_MUI_FINISHPAGE_RUN "../${PROGNAME}.exe")
|
||||
set(CPACK_WIX_UPGRADE_GUID 88785A72-3EAE-4F29-89E3-BC6B19BA9A5B)
|
||||
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/share/windows/keepassxc.ico")
|
||||
set(CPACK_WIX_UI_BANNER "${CMAKE_SOURCE_DIR}/share/windows/wix-banner.bmp")
|
||||
set(CPACK_WIX_UI_DIALOG "${CMAKE_SOURCE_DIR}/share/windows/wix-dialog.bmp")
|
||||
set(CPACK_WIX_TEMPLATE "${CMAKE_SOURCE_DIR}/share/windows/wix-template.xml")
|
||||
set(CPACK_WIX_PATCH_FILE "${CMAKE_SOURCE_DIR}/share/windows/wix-patch.xml")
|
||||
set(CPACK_WIX_PROPERTY_ARPURLINFOABOUT "https://keepassxc.org")
|
||||
set(CPACK_WIX_EXTENSIONS "WixUtilExtension.dll")
|
||||
include(CPack)
|
||||
|
||||
install(CODE "
|
||||
@ -324,8 +386,25 @@ if(MINGW)
|
||||
|
||||
include(DeployQt4)
|
||||
install_qt4_executable(${PROGNAME}.exe)
|
||||
add_custom_command(TARGET ${PROGNAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${Qt5Core_DIR}/../../../share/qt5/plugins/platforms/qwindows$<$<CONFIG:Debug>:d>.dll
|
||||
$<TARGET_FILE_DIR:${PROGNAME}>)
|
||||
install(FILES $<TARGET_FILE_DIR:${PROGNAME}>/qwindows$<$<CONFIG:Debug>:d>.dll DESTINATION "platforms")
|
||||
|
||||
# install Qt5 plugins
|
||||
set(PLUGINS_DIR ${Qt5_PREFIX}/share/qt5/plugins)
|
||||
install(FILES
|
||||
${PLUGINS_DIR}/platforms/qwindows$<$<CONFIG:Debug>:d>.dll
|
||||
${PLUGINS_DIR}/platforms/qdirect2d$<$<CONFIG:Debug>:d>.dll
|
||||
DESTINATION "platforms")
|
||||
install(FILES ${PLUGINS_DIR}/styles/qwindowsvistastyle$<$<CONFIG:Debug>:d>.dll DESTINATION "styles")
|
||||
install(FILES ${PLUGINS_DIR}/platforminputcontexts/qtvirtualkeyboardplugin$<$<CONFIG:Debug>:d>.dll DESTINATION "platforminputcontexts")
|
||||
install(FILES ${PLUGINS_DIR}/iconengines/qsvgicon$<$<CONFIG:Debug>:d>.dll DESTINATION "iconengines")
|
||||
install(FILES
|
||||
${PLUGINS_DIR}/imageformats/qgif$<$<CONFIG:Debug>:d>.dll
|
||||
${PLUGINS_DIR}/imageformats/qicns$<$<CONFIG:Debug>:d>.dll
|
||||
${PLUGINS_DIR}/imageformats/qico$<$<CONFIG:Debug>:d>.dll
|
||||
${PLUGINS_DIR}/imageformats/qjpeg$<$<CONFIG:Debug>:d>.dll
|
||||
${PLUGINS_DIR}/imageformats/qsvg$<$<CONFIG:Debug>:d>.dll
|
||||
${PLUGINS_DIR}/imageformats/qwebp$<$<CONFIG:Debug>:d>.dll
|
||||
DESTINATION "imageformats")
|
||||
|
||||
# install CA cert chains
|
||||
install(FILES ${Qt5_PREFIX}/ssl/certs/ca-bundle.crt DESTINATION "ssl/certs")
|
||||
endif()
|
||||
|
@ -20,12 +20,14 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QPluginLoader>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#include "autotype/AutoTypePlatformPlugin.h"
|
||||
#include "autotype/AutoTypeSelectDialog.h"
|
||||
#include "autotype/WildcardMatcher.h"
|
||||
#include "core/AutoTypeMatch.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
@ -39,7 +41,6 @@ AutoType* AutoType::m_instance = nullptr;
|
||||
|
||||
AutoType::AutoType(QObject* parent, bool test)
|
||||
: QObject(parent)
|
||||
, m_inAutoType(false)
|
||||
, m_autoTypeDelay(0)
|
||||
, m_currentGlobalKey(static_cast<Qt::Key>(0))
|
||||
, m_currentGlobalModifiers(0)
|
||||
@ -54,17 +55,16 @@ AutoType::AutoType(QObject* parent, bool test)
|
||||
QString pluginName = "keepassx-autotype-";
|
||||
if (!test) {
|
||||
pluginName += QApplication::platformName();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
pluginName += "test";
|
||||
}
|
||||
|
||||
QString pluginPath = filePath()->pluginPath(pluginName);
|
||||
|
||||
if (!pluginPath.isEmpty()) {
|
||||
#ifdef WITH_XC_AUTOTYPE
|
||||
#ifdef WITH_XC_AUTOTYPE
|
||||
loadPlugin(pluginPath);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
connect(qApp, SIGNAL(aboutToQuit()), SLOT(unloadPlugin()));
|
||||
@ -91,8 +91,7 @@ void AutoType::loadPlugin(const QString& pluginPath)
|
||||
if (m_plugin->isAvailable()) {
|
||||
m_executor = m_plugin->createExecutor();
|
||||
connect(pluginInstance, SIGNAL(globalShortcutTriggered()), SIGNAL(globalShortcutTriggered()));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
unloadPlugin();
|
||||
}
|
||||
}
|
||||
@ -103,6 +102,19 @@ void AutoType::loadPlugin(const QString& pluginPath)
|
||||
}
|
||||
}
|
||||
|
||||
void AutoType::unloadPlugin()
|
||||
{
|
||||
if (m_executor) {
|
||||
delete m_executor;
|
||||
m_executor = nullptr;
|
||||
}
|
||||
|
||||
if (m_plugin) {
|
||||
m_plugin->unload();
|
||||
m_plugin = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AutoType* AutoType::instance()
|
||||
{
|
||||
if (!m_instance) {
|
||||
@ -128,29 +140,76 @@ QStringList AutoType::windowTitles()
|
||||
return m_plugin->windowTitles();
|
||||
}
|
||||
|
||||
void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow, const QString& customSequence, WId window)
|
||||
void AutoType::resetInAutoType()
|
||||
{
|
||||
if (m_inAutoType || !m_plugin) {
|
||||
m_inAutoType.unlock();
|
||||
|
||||
emit autotypeRejected();
|
||||
}
|
||||
|
||||
void AutoType::raiseWindow()
|
||||
{
|
||||
#if defined(Q_OS_MAC)
|
||||
m_plugin->raiseOwnWindow();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool AutoType::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
Q_ASSERT(key);
|
||||
Q_ASSERT(modifiers);
|
||||
|
||||
if (!m_plugin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key != m_currentGlobalKey || modifiers != m_currentGlobalModifiers) {
|
||||
if (m_currentGlobalKey && m_currentGlobalModifiers) {
|
||||
m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers);
|
||||
}
|
||||
|
||||
if (m_plugin->registerGlobalShortcut(key, modifiers)) {
|
||||
m_currentGlobalKey = key;
|
||||
m_currentGlobalModifiers = modifiers;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AutoType::unregisterGlobalShortcut()
|
||||
{
|
||||
if (m_plugin && m_currentGlobalKey && m_currentGlobalModifiers) {
|
||||
m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers);
|
||||
}
|
||||
}
|
||||
|
||||
int AutoType::callEventFilter(void* event)
|
||||
{
|
||||
if (!m_plugin) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return m_plugin->platformEventFilter(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Core Autotype function that will execute actions
|
||||
*/
|
||||
void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, const QString& sequence, WId window)
|
||||
{
|
||||
// no edit to the sequence beyond this point
|
||||
if (!verifyAutoTypeSyntax(sequence)) {
|
||||
emit autotypeRejected();
|
||||
return;
|
||||
}
|
||||
m_inAutoType = true;
|
||||
|
||||
QString sequence;
|
||||
if (customSequence.isEmpty()) {
|
||||
sequence = autoTypeSequence(entry);
|
||||
}
|
||||
else {
|
||||
sequence = customSequence;
|
||||
}
|
||||
|
||||
sequence.replace("{{}", "{LEFTBRACE}");
|
||||
sequence.replace("{}}", "{RIGHTBRACE}");
|
||||
|
||||
QList<AutoTypeAction*> actions;
|
||||
ListDeleter<AutoTypeAction*> actionsDeleter(&actions);
|
||||
|
||||
if (!parseActions(sequence, entry, actions)) {
|
||||
m_inAutoType = false; // TODO: make this automatic
|
||||
emit autotypeRejected();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -173,19 +232,49 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow, const QS
|
||||
for (AutoTypeAction* action : asConst(actions)) {
|
||||
if (m_plugin->activeWindow() != window) {
|
||||
qWarning("Active window changed, interrupting auto-type.");
|
||||
break;
|
||||
emit autotypeRejected();
|
||||
return;
|
||||
}
|
||||
|
||||
action->accept(m_executor);
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||
}
|
||||
|
||||
m_inAutoType = false;
|
||||
// emit signal only if autotype performed correctly
|
||||
emit autotypePerformed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Single Autotype entry-point function
|
||||
* Perfom autotype sequence in the active window
|
||||
*/
|
||||
void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow)
|
||||
{
|
||||
if (!m_plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<QString> sequences = autoTypeSequences(entry);
|
||||
if (sequences.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_inAutoType.tryLock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
executeAutoTypeActions(entry, hideWindow, sequences.first());
|
||||
|
||||
m_inAutoType.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Global Autotype entry-point function
|
||||
* Perform global Auto-Type on the active window
|
||||
*/
|
||||
void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
|
||||
{
|
||||
if (m_inAutoType || !m_plugin) {
|
||||
if (!m_plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -195,40 +284,51 @@ void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
|
||||
return;
|
||||
}
|
||||
|
||||
m_inAutoType = true;
|
||||
if (!m_inAutoType.tryLock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Entry*> entryList;
|
||||
QHash<Entry*, QString> sequenceHash;
|
||||
QList<AutoTypeMatch> matchList;
|
||||
|
||||
for (Database* db : dbList) {
|
||||
const QList<Entry*> dbEntries = db->rootGroup()->entriesRecursive();
|
||||
for (Entry* entry : dbEntries) {
|
||||
QString sequence = autoTypeSequence(entry, windowTitle);
|
||||
if (!sequence.isEmpty()) {
|
||||
entryList << entry;
|
||||
sequenceHash.insert(entry, sequence);
|
||||
const QSet<QString> sequences = autoTypeSequences(entry, windowTitle).toSet();
|
||||
for (const QString& sequence : sequences) {
|
||||
if (!sequence.isEmpty()) {
|
||||
matchList << AutoTypeMatch(entry, sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entryList.isEmpty()) {
|
||||
m_inAutoType = false;
|
||||
QString message = tr("Couldn't find an entry that matches the window title:");
|
||||
message.append("\n\n");
|
||||
message.append(windowTitle);
|
||||
MessageBox::information(nullptr, tr("Auto-Type - KeePassXC"), message);
|
||||
}
|
||||
else if ((entryList.size() == 1) && !config()->get("security/autotypeask").toBool()) {
|
||||
m_inAutoType = false;
|
||||
performAutoType(entryList.first(), nullptr, sequenceHash[entryList.first()]);
|
||||
}
|
||||
else {
|
||||
if (matchList.isEmpty()) {
|
||||
m_inAutoType.unlock();
|
||||
|
||||
if (qobject_cast<QApplication*>(QCoreApplication::instance())) {
|
||||
auto* msgBox = new QMessageBox();
|
||||
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||
msgBox->setWindowTitle(tr("Auto-Type - KeePassXC"));
|
||||
msgBox->setText(tr("Couldn't find an entry that matches the window title:").append("\n\n")
|
||||
.append(windowTitle));
|
||||
msgBox->setIcon(QMessageBox::Information);
|
||||
msgBox->setStandardButtons(QMessageBox::Ok);
|
||||
msgBox->show();
|
||||
msgBox->raise();
|
||||
msgBox->activateWindow();
|
||||
}
|
||||
|
||||
emit autotypeRejected();
|
||||
} else if ((matchList.size() == 1) && !config()->get("security/autotypeask").toBool()) {
|
||||
executeAutoTypeActions(matchList.first().entry, nullptr, matchList.first().sequence);
|
||||
m_inAutoType.unlock();
|
||||
} else {
|
||||
m_windowFromGlobal = m_plugin->activeWindow();
|
||||
AutoTypeSelectDialog* selectDialog = new AutoTypeSelectDialog();
|
||||
connect(selectDialog, SIGNAL(entryActivated(Entry*,QString)),
|
||||
SLOT(performAutoTypeFromGlobal(Entry*,QString)));
|
||||
auto* selectDialog = new AutoTypeSelectDialog();
|
||||
connect(selectDialog, SIGNAL(matchActivated(AutoTypeMatch)),
|
||||
SLOT(performAutoTypeFromGlobal(AutoTypeMatch)));
|
||||
connect(selectDialog, SIGNAL(rejected()), SLOT(resetInAutoType()));
|
||||
selectDialog->setEntries(entryList, sequenceHash);
|
||||
selectDialog->setMatchList(matchList);
|
||||
#if defined(Q_OS_MAC)
|
||||
m_plugin->raiseOwnWindow();
|
||||
Tools::wait(500);
|
||||
@ -239,110 +339,52 @@ void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
|
||||
}
|
||||
}
|
||||
|
||||
void AutoType::performAutoTypeFromGlobal(Entry* entry, const QString& sequence)
|
||||
void AutoType::performAutoTypeFromGlobal(AutoTypeMatch match)
|
||||
{
|
||||
Q_ASSERT(m_inAutoType);
|
||||
// We don't care about the result here, the mutex should already be locked. Now it's locked for sure
|
||||
m_inAutoType.tryLock();
|
||||
|
||||
m_plugin->raiseWindow(m_windowFromGlobal);
|
||||
|
||||
m_inAutoType = false;
|
||||
performAutoType(entry, nullptr, sequence, m_windowFromGlobal);
|
||||
executeAutoTypeActions(match.entry, nullptr, match.sequence, m_windowFromGlobal);
|
||||
|
||||
m_inAutoType.unlock();
|
||||
}
|
||||
|
||||
void AutoType::resetInAutoType()
|
||||
{
|
||||
Q_ASSERT(m_inAutoType);
|
||||
|
||||
m_inAutoType = false;
|
||||
}
|
||||
|
||||
void AutoType::unloadPlugin()
|
||||
{
|
||||
if (m_executor) {
|
||||
delete m_executor;
|
||||
m_executor = nullptr;
|
||||
}
|
||||
|
||||
if (m_plugin) {
|
||||
m_plugin->unload();
|
||||
m_plugin = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool AutoType::registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
Q_ASSERT(key);
|
||||
Q_ASSERT(modifiers);
|
||||
|
||||
if (!m_plugin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key != m_currentGlobalKey || modifiers != m_currentGlobalModifiers) {
|
||||
if (m_currentGlobalKey && m_currentGlobalModifiers) {
|
||||
m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers);
|
||||
}
|
||||
|
||||
if (m_plugin->registerGlobalShortcut(key, modifiers)) {
|
||||
m_currentGlobalKey = key;
|
||||
m_currentGlobalModifiers = modifiers;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void AutoType::unregisterGlobalShortcut()
|
||||
{
|
||||
if (m_plugin && m_currentGlobalKey && m_currentGlobalModifiers) {
|
||||
m_plugin->unregisterGlobalShortcut(m_currentGlobalKey, m_currentGlobalModifiers);
|
||||
}
|
||||
}
|
||||
|
||||
int AutoType::callEventFilter(void* event)
|
||||
{
|
||||
if (!m_plugin) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return m_plugin->platformEventFilter(event);
|
||||
}
|
||||
|
||||
bool AutoType::parseActions(const QString& sequence, const Entry* entry, QList<AutoTypeAction*>& actions)
|
||||
/**
|
||||
* Parse an autotype sequence and resolve its Template/command inside as AutoTypeActions
|
||||
*/
|
||||
bool AutoType::parseActions(const QString& actionSequence, const Entry* entry, QList<AutoTypeAction*>& actions)
|
||||
{
|
||||
QString tmpl;
|
||||
bool inTmpl = false;
|
||||
m_autoTypeDelay = config()->get("AutoTypeDelay").toInt();
|
||||
|
||||
QString sequence = actionSequence;
|
||||
sequence.replace("{{}", "{LEFTBRACE}");
|
||||
sequence.replace("{}}", "{RIGHTBRACE}");
|
||||
|
||||
for (const QChar& ch : sequence) {
|
||||
if (inTmpl) {
|
||||
if (ch == '{') {
|
||||
qWarning("Syntax error in auto-type sequence.");
|
||||
qWarning("Syntax error in Auto-Type sequence.");
|
||||
return false;
|
||||
}
|
||||
else if (ch == '}') {
|
||||
actions.append(createActionFromTemplate(tmpl, entry));
|
||||
} else if (ch == '}') {
|
||||
QList<AutoTypeAction*> autoType = createActionFromTemplate(tmpl, entry);
|
||||
if (!autoType.isEmpty()) {
|
||||
actions.append(autoType);
|
||||
}
|
||||
inTmpl = false;
|
||||
tmpl.clear();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
tmpl += ch;
|
||||
}
|
||||
}
|
||||
else if (ch == '{') {
|
||||
} else if (ch == '{') {
|
||||
inTmpl = true;
|
||||
}
|
||||
else if (ch == '}') {
|
||||
qWarning("Syntax error in auto-type sequence.");
|
||||
} else if (ch == '}') {
|
||||
qWarning("Syntax error in Auto-Type sequence.");
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
actions.append(new AutoTypeChar(ch));
|
||||
}
|
||||
}
|
||||
@ -360,6 +402,9 @@ bool AutoType::parseActions(const QString& sequence, const Entry* entry, QList<A
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an autotype Template/command to its AutoTypeAction that will be executed by the plugin executor
|
||||
*/
|
||||
QList<AutoTypeAction*> AutoType::createActionFromTemplate(const QString& tmpl, const Entry* entry)
|
||||
{
|
||||
QString tmplName = tmpl;
|
||||
@ -381,120 +426,78 @@ QList<AutoTypeAction*> AutoType::createActionFromTemplate(const QString& tmpl, c
|
||||
if (num == 0) {
|
||||
return list;
|
||||
}
|
||||
// some safety checks
|
||||
else if (tmplName.compare("delay",Qt::CaseInsensitive)==0) {
|
||||
if (num > 10000) {
|
||||
return list;
|
||||
}
|
||||
}
|
||||
else if (num > 100) {
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
if (tmplName.compare("tab",Qt::CaseInsensitive)==0) {
|
||||
if (tmplName.compare("tab", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Tab));
|
||||
}
|
||||
else if (tmplName.compare("enter",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("enter", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Enter));
|
||||
}
|
||||
else if (tmplName.compare("space",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("space", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Space));
|
||||
}
|
||||
else if (tmplName.compare("up",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("up", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Up));
|
||||
}
|
||||
else if (tmplName.compare("down",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("down", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Down));
|
||||
}
|
||||
else if (tmplName.compare("left",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("left", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Left));
|
||||
}
|
||||
else if (tmplName.compare("right",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("right", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Right));
|
||||
}
|
||||
else if (tmplName.compare("insert",Qt::CaseInsensitive)==0 ||
|
||||
tmplName.compare("ins",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("insert", Qt::CaseInsensitive) == 0 ||
|
||||
tmplName.compare("ins", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Insert));
|
||||
}
|
||||
else if (tmplName.compare("delete",Qt::CaseInsensitive)==0 ||
|
||||
tmplName.compare("del",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("delete", Qt::CaseInsensitive) == 0 ||
|
||||
tmplName.compare("del", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Delete));
|
||||
}
|
||||
else if (tmplName.compare("home",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("home", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Home));
|
||||
}
|
||||
else if (tmplName.compare("end",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("end", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_End));
|
||||
}
|
||||
else if (tmplName.compare("pgup",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("pgup", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_PageUp));
|
||||
}
|
||||
else if (tmplName.compare("pgdown",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("pgdown", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_PageDown));
|
||||
}
|
||||
else if (tmplName.compare("backspace",Qt::CaseInsensitive)==0 ||
|
||||
tmplName.compare("bs",Qt::CaseInsensitive)==0 ||
|
||||
tmplName.compare("bksp",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("backspace", Qt::CaseInsensitive) == 0 ||
|
||||
tmplName.compare("bs", Qt::CaseInsensitive) == 0 || tmplName.compare("bksp", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Backspace));
|
||||
}
|
||||
else if (tmplName.compare("break",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("break", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Pause));
|
||||
}
|
||||
else if (tmplName.compare("capslock",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("capslock", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_CapsLock));
|
||||
}
|
||||
else if (tmplName.compare("esc",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("esc", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Escape));
|
||||
}
|
||||
else if (tmplName.compare("help",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("help", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Help));
|
||||
}
|
||||
else if (tmplName.compare("numlock",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("numlock", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_NumLock));
|
||||
}
|
||||
else if (tmplName.compare("ptrsc",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("ptrsc", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_Print));
|
||||
}
|
||||
else if (tmplName.compare("scrolllock",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("scrolllock", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeKey(Qt::Key_ScrollLock));
|
||||
}
|
||||
// Qt doesn't know about keypad keys so use the normal ones instead
|
||||
else if (tmplName.compare("add",Qt::CaseInsensitive)==0 ||
|
||||
tmplName.compare("+",Qt::CaseInsensitive)==0) {
|
||||
else if (tmplName.compare("add", Qt::CaseInsensitive) == 0 || tmplName.compare("+", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeChar('+'));
|
||||
}
|
||||
else if (tmplName.compare("subtract",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("subtract", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeChar('-'));
|
||||
}
|
||||
else if (tmplName.compare("multiply",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("multiply", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeChar('*'));
|
||||
}
|
||||
else if (tmplName.compare("divide",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("divide", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeChar('/'));
|
||||
}
|
||||
else if (tmplName.compare("^",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("^", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeChar('^'));
|
||||
}
|
||||
else if (tmplName.compare("%",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("%", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeChar('%'));
|
||||
}
|
||||
else if (tmplName.compare("~",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("~", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeChar('~'));
|
||||
}
|
||||
else if (tmplName.compare("(",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("(", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeChar('('));
|
||||
}
|
||||
else if (tmplName.compare(")",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare(")", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeChar(')'));
|
||||
}
|
||||
else if (tmplName.compare("leftbrace",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("leftbrace", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeChar('{'));
|
||||
}
|
||||
else if (tmplName.compare("rightbrace",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("rightbrace", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeChar('}'));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
QRegExp fnRegexp("f(\\d+)", Qt::CaseInsensitive, QRegExp::RegExp2);
|
||||
if (fnRegexp.exactMatch(tmplName)) {
|
||||
int fnNo = fnRegexp.cap(1).toInt();
|
||||
@ -508,18 +511,14 @@ QList<AutoTypeAction*> AutoType::createActionFromTemplate(const QString& tmpl, c
|
||||
for (int i = 1; i < num; i++) {
|
||||
list.append(list.at(0)->clone());
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
if (tmplName.compare("delay",Qt::CaseInsensitive)==0 && num > 0) {
|
||||
if (tmplName.compare("delay", Qt::CaseInsensitive) == 0 && num > 0) {
|
||||
list.append(new AutoTypeDelay(num));
|
||||
}
|
||||
else if (tmplName.compare("clearfield",Qt::CaseInsensitive)==0) {
|
||||
} else if (tmplName.compare("clearfield", Qt::CaseInsensitive) == 0) {
|
||||
list.append(new AutoTypeClearField());
|
||||
}
|
||||
else if (tmplName.compare("totp", Qt::CaseInsensitive) == 0) {
|
||||
} else if (tmplName.compare("totp", Qt::CaseInsensitive) == 0) {
|
||||
QString totp = entry->totp();
|
||||
if (!totp.isEmpty()) {
|
||||
for (const QChar& ch : totp) {
|
||||
@ -538,11 +537,9 @@ QList<AutoTypeAction*> AutoType::createActionFromTemplate(const QString& tmpl, c
|
||||
for (const QChar& ch : resolved) {
|
||||
if (ch == '\n') {
|
||||
list.append(new AutoTypeKey(Qt::Key_Enter));
|
||||
}
|
||||
else if (ch == '\t') {
|
||||
} else if (ch == '\t') {
|
||||
list.append(new AutoTypeKey(Qt::Key_Tab));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
list.append(new AutoTypeChar(ch));
|
||||
}
|
||||
}
|
||||
@ -551,94 +548,88 @@ QList<AutoTypeAction*> AutoType::createActionFromTemplate(const QString& tmpl, c
|
||||
return list;
|
||||
}
|
||||
|
||||
QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitle)
|
||||
/**
|
||||
* Retrive the autotype sequences matches for a given windowTitle
|
||||
* This returns a list with priority ordering. If you don't want duplicates call .toSet() on it.
|
||||
*/
|
||||
QList<QString> AutoType::autoTypeSequences(const Entry* entry, const QString& windowTitle)
|
||||
{
|
||||
QList<QString> sequenceList;
|
||||
|
||||
if (!entry->autoTypeEnabled()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool enableSet = false;
|
||||
QString sequence;
|
||||
if (!windowTitle.isEmpty()) {
|
||||
bool match = false;
|
||||
const QList<AutoTypeAssociations::Association> assocList = entry->autoTypeAssociations()->getAll();
|
||||
for (const AutoTypeAssociations::Association& assoc : assocList) {
|
||||
if (windowMatches(windowTitle, assoc.window)) {
|
||||
if (!assoc.sequence.isEmpty()) {
|
||||
sequence = assoc.sequence;
|
||||
}
|
||||
else {
|
||||
sequence = entry->defaultAutoTypeSequence();
|
||||
}
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match && config()->get("AutoTypeEntryTitleMatch").toBool()
|
||||
&& (windowMatchesTitle(windowTitle, entry->resolvePlaceholder(entry->title()))
|
||||
|| windowMatchesUrl(windowTitle, entry->resolvePlaceholder(entry->url())))) {
|
||||
sequence = entry->defaultAutoTypeSequence();
|
||||
match = true;
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
else {
|
||||
sequence = entry->defaultAutoTypeSequence();
|
||||
return sequenceList;
|
||||
}
|
||||
|
||||
const Group* group = entry->group();
|
||||
do {
|
||||
if (!enableSet) {
|
||||
if (group->autoTypeEnabled() == Group::Disable) {
|
||||
return QString();
|
||||
}
|
||||
else if (group->autoTypeEnabled() == Group::Enable) {
|
||||
enableSet = true;
|
||||
}
|
||||
if (group->autoTypeEnabled() == Group::Disable) {
|
||||
return sequenceList;
|
||||
} else if (group->autoTypeEnabled() == Group::Enable) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (sequence.isEmpty()) {
|
||||
sequence = group->defaultAutoTypeSequence();
|
||||
}
|
||||
|
||||
group = group->parentGroup();
|
||||
} while (group && (!enableSet || sequence.isEmpty()));
|
||||
|
||||
if (sequence.isEmpty() && (!entry->resolvePlaceholder(entry->username()).isEmpty() || !entry->resolvePlaceholder(entry->password()).isEmpty())) {
|
||||
if (entry->resolvePlaceholder(entry->username()).isEmpty()) {
|
||||
sequence = "{PASSWORD}{ENTER}";
|
||||
} while (group);
|
||||
|
||||
if (!windowTitle.isEmpty()) {
|
||||
const QList<AutoTypeAssociations::Association> assocList = entry->autoTypeAssociations()->getAll();
|
||||
for (const AutoTypeAssociations::Association& assoc : assocList) {
|
||||
const QString window = entry->resolveMultiplePlaceholders(assoc.window);
|
||||
if (windowMatches(windowTitle, window)) {
|
||||
if (!assoc.sequence.isEmpty()) {
|
||||
sequenceList.append(assoc.sequence);
|
||||
} else {
|
||||
sequenceList.append(entry->effectiveAutoTypeSequence());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (entry->resolvePlaceholder(entry->password()).isEmpty()) {
|
||||
sequence = "{USERNAME}{ENTER}";
|
||||
|
||||
if (config()->get("AutoTypeEntryTitleMatch").toBool() &&
|
||||
windowMatchesTitle(windowTitle, entry->resolvePlaceholder(entry->title()))) {
|
||||
sequenceList.append(entry->effectiveAutoTypeSequence());
|
||||
}
|
||||
else {
|
||||
sequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}";
|
||||
|
||||
if (config()->get("AutoTypeEntryURLMatch").toBool() &&
|
||||
windowMatchesUrl(windowTitle, entry->resolvePlaceholder(entry->url()))) {
|
||||
sequenceList.append(entry->effectiveAutoTypeSequence());
|
||||
}
|
||||
|
||||
if (sequenceList.isEmpty()) {
|
||||
return sequenceList;
|
||||
}
|
||||
} else {
|
||||
sequenceList.append(entry->effectiveAutoTypeSequence());
|
||||
}
|
||||
|
||||
return sequence;
|
||||
return sequenceList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a window title matches a pattern
|
||||
*/
|
||||
bool AutoType::windowMatches(const QString& windowTitle, const QString& windowPattern)
|
||||
{
|
||||
if (windowPattern.startsWith("//") && windowPattern.endsWith("//") && windowPattern.size() >= 4) {
|
||||
QRegExp regExp(windowPattern.mid(2, windowPattern.size() - 4), Qt::CaseInsensitive, QRegExp::RegExp2);
|
||||
return (regExp.indexIn(windowTitle) != -1);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return WildcardMatcher(windowTitle).match(windowPattern);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a window title matches an entry Title
|
||||
* The entry title should be Spr-compiled by the caller
|
||||
*/
|
||||
bool AutoType::windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle)
|
||||
{
|
||||
return !resolvedTitle.isEmpty() && windowTitle.contains(resolvedTitle, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a window title matches an entry URL
|
||||
* The entry URL should be Spr-compiled by the caller
|
||||
*/
|
||||
bool AutoType::windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl)
|
||||
{
|
||||
if (!resolvedUrl.isEmpty() && windowTitle.contains(resolvedUrl, Qt::CaseInsensitive)) {
|
||||
@ -652,3 +643,101 @@ bool AutoType::windowMatchesUrl(const QString& windowTitle, const QString& resol
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the overall syntax of an autotype sequence is fine
|
||||
*/
|
||||
bool AutoType::checkSyntax(const QString& string)
|
||||
{
|
||||
QString allowRepetition = "(?:\\s\\d+)?";
|
||||
// the ":" allows custom commands with syntax S:Field
|
||||
// exclude BEEP otherwise will be checked as valid
|
||||
QString normalCommands = "(?!BEEP\\s)[A-Z:]*" + allowRepetition;
|
||||
QString specialLiterals = "[\\^\\%\\(\\)~\\{\\}\\[\\]\\+]" + allowRepetition;
|
||||
QString functionKeys = "(?:F[1-9]" + allowRepetition + "|F1[0-2])" + allowRepetition;
|
||||
QString numpad = "NUMPAD\\d" + allowRepetition;
|
||||
QString delay = "DELAY=\\d+";
|
||||
QString beep = "BEEP\\s\\d+\\s\\d+";
|
||||
QString vkey = "VKEY(?:-[EN]X)?\\s\\w+";
|
||||
|
||||
// these chars aren't in parentheses
|
||||
QString shortcutKeys = "[\\^\\%~\\+@]";
|
||||
// a normal string not in parentheses
|
||||
QString fixedStrings = "[^\\^\\%~\\+@\\{\\}]*";
|
||||
|
||||
QRegularExpression autoTypeSyntax("^(?:" + shortcutKeys + "|" + fixedStrings + "|\\{(?:" + normalCommands + "|" + specialLiterals +
|
||||
"|" + functionKeys + "|" + numpad + "|" + delay + "|" + beep + "|" + vkey + ")\\})*$",
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
QRegularExpressionMatch match = autoTypeSyntax.match(string);
|
||||
return match.hasMatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks an autotype sequence for high delay
|
||||
*/
|
||||
bool AutoType::checkHighDelay(const QString& string)
|
||||
{
|
||||
// 5 digit numbers(10 seconds) are too much
|
||||
QRegularExpression highDelay("\\{DELAY\\s\\d{5,}\\}", QRegularExpression::CaseInsensitiveOption);
|
||||
QRegularExpressionMatch match = highDelay.match(string);
|
||||
return match.hasMatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks an autotype sequence for slow keypress
|
||||
*/
|
||||
bool AutoType::checkSlowKeypress(const QString& string)
|
||||
{
|
||||
// 3 digit numbers(100 milliseconds) are too much
|
||||
QRegularExpression slowKeypress("\\{DELAY=\\d{3,}\\}", QRegularExpression::CaseInsensitiveOption);
|
||||
QRegularExpressionMatch match = slowKeypress.match(string);
|
||||
return match.hasMatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks an autotype sequence for high repetition command
|
||||
*/
|
||||
bool AutoType::checkHighRepetition(const QString& string)
|
||||
{
|
||||
// 3 digit numbers are too much
|
||||
QRegularExpression highRepetition("\\{(?!DELAY\\s)\\w+\\s\\d{3,}\\}", QRegularExpression::CaseInsensitiveOption);
|
||||
QRegularExpressionMatch match = highRepetition.match(string);
|
||||
return match.hasMatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the syntax of an autotype sequence is correct and doesn't have silly parameters
|
||||
*/
|
||||
bool AutoType::verifyAutoTypeSyntax(const QString& sequence)
|
||||
{
|
||||
if (!AutoType::checkSyntax(sequence)) {
|
||||
QMessageBox messageBox;
|
||||
messageBox.critical(nullptr, tr("Auto-Type"), tr("The Syntax of your Auto-Type statement is incorrect!"));
|
||||
return false;
|
||||
} else if (AutoType::checkHighDelay(sequence)) {
|
||||
QMessageBox::StandardButton reply;
|
||||
reply = QMessageBox::question(nullptr, tr("Auto-Type"),
|
||||
tr("This Auto-Type command contains a very long delay. Do you really want to proceed?"));
|
||||
|
||||
if (reply == QMessageBox::No) {
|
||||
return false;
|
||||
}
|
||||
} else if (AutoType::checkSlowKeypress(sequence)) {
|
||||
QMessageBox::StandardButton reply;
|
||||
reply = QMessageBox::question(nullptr, tr("Auto-Type"),
|
||||
tr("This Auto-Type command contains very slow key presses. Do you really want to proceed?"));
|
||||
|
||||
if (reply == QMessageBox::No) {
|
||||
return false;
|
||||
}
|
||||
} else if (AutoType::checkHighRepetition(sequence)) {
|
||||
QMessageBox::StandardButton reply;
|
||||
reply = QMessageBox::question(nullptr, tr("Auto-Type"),
|
||||
tr("This Auto-Type command contains arguments which are repeated very often. Do you really want to proceed?"));
|
||||
|
||||
if (reply == QMessageBox::No) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
@ -22,6 +22,9 @@
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QWidget>
|
||||
#include <QMutex>
|
||||
|
||||
#include "core/AutoTypeMatch.h"
|
||||
|
||||
class AutoTypeAction;
|
||||
class AutoTypeExecutor;
|
||||
@ -36,13 +39,19 @@ class AutoType : public QObject
|
||||
|
||||
public:
|
||||
QStringList windowTitles();
|
||||
void performAutoType(const Entry* entry, QWidget* hideWindow = nullptr,
|
||||
const QString& customSequence = QString(), WId window = 0);
|
||||
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers);
|
||||
void unregisterGlobalShortcut();
|
||||
int callEventFilter(void* event);
|
||||
static bool checkSyntax(const QString& string);
|
||||
static bool checkHighRepetition(const QString& string);
|
||||
static bool checkSlowKeypress(const QString& string);
|
||||
static bool checkHighDelay(const QString& string);
|
||||
static bool verifyAutoTypeSyntax(const QString& sequence);
|
||||
void performAutoType(const Entry* entry,
|
||||
QWidget* hideWindow = nullptr);
|
||||
|
||||
inline bool isAvailable() {
|
||||
inline bool isAvailable()
|
||||
{
|
||||
return m_plugin;
|
||||
}
|
||||
|
||||
@ -51,12 +60,15 @@ public:
|
||||
|
||||
public slots:
|
||||
void performGlobalAutoType(const QList<Database*>& dbList);
|
||||
void raiseWindow();
|
||||
|
||||
signals:
|
||||
void globalShortcutTriggered();
|
||||
void autotypePerformed();
|
||||
void autotypeRejected();
|
||||
|
||||
private slots:
|
||||
void performAutoTypeFromGlobal(Entry* entry, const QString& sequence);
|
||||
void performAutoTypeFromGlobal(AutoTypeMatch match);
|
||||
void resetInAutoType();
|
||||
void unloadPlugin();
|
||||
|
||||
@ -64,14 +76,18 @@ private:
|
||||
explicit AutoType(QObject* parent = nullptr, bool test = false);
|
||||
~AutoType();
|
||||
void loadPlugin(const QString& pluginPath);
|
||||
void executeAutoTypeActions(const Entry* entry,
|
||||
QWidget* hideWindow = nullptr,
|
||||
const QString& customSequence = QString(),
|
||||
WId window = 0);
|
||||
bool parseActions(const QString& sequence, const Entry* entry, QList<AutoTypeAction*>& actions);
|
||||
QList<AutoTypeAction*> createActionFromTemplate(const QString& tmpl, const Entry* entry);
|
||||
QString autoTypeSequence(const Entry* entry, const QString& windowTitle = QString());
|
||||
QList<QString> autoTypeSequences(const Entry* entry, const QString& windowTitle = QString());
|
||||
bool windowMatchesTitle(const QString& windowTitle, const QString& resolvedTitle);
|
||||
bool windowMatchesUrl(const QString& windowTitle, const QString& resolvedUrl);
|
||||
bool windowMatches(const QString& windowTitle, const QString& windowPattern);
|
||||
|
||||
bool m_inAutoType;
|
||||
QMutex m_inAutoType;
|
||||
int m_autoTypeDelay;
|
||||
Qt::Key m_currentGlobalKey;
|
||||
Qt::KeyboardModifiers m_currentGlobalModifiers;
|
||||
@ -84,7 +100,8 @@ private:
|
||||
Q_DISABLE_COPY(AutoType)
|
||||
};
|
||||
|
||||
inline AutoType* autoType() {
|
||||
inline AutoType* autoType()
|
||||
{
|
||||
return AutoType::instance();
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <QChar>
|
||||
#include <Qt>
|
||||
#include <QObject>
|
||||
|
||||
#include "core/Global.h"
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -25,14 +26,16 @@
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "autotype/AutoTypeSelectView.h"
|
||||
#include "core/AutoTypeMatch.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/FilePath.h"
|
||||
#include "gui/entry/EntryModel.h"
|
||||
#include "gui/entry/AutoTypeMatchModel.h"
|
||||
|
||||
AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, m_view(new AutoTypeSelectView(this))
|
||||
, m_entryActivatedEmitted(false)
|
||||
, m_matchActivatedEmitted(false)
|
||||
, m_rejected(false)
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
// Places the window on the active (virtual) desktop instead of where the main window is.
|
||||
@ -42,7 +45,7 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
|
||||
setWindowIcon(filePath()->applicationIcon());
|
||||
|
||||
QRect screenGeometry = QApplication::desktop()->availableGeometry(QCursor::pos());
|
||||
QSize size = config()->get("GUI/AutoTypeSelectDialogSize", QSize(400, 250)).toSize();
|
||||
QSize size = config()->get("GUI/AutoTypeSelectDialogSize", QSize(600, 250)).toSize();
|
||||
size.setWidth(qMin(size.width(), screenGeometry.width()));
|
||||
size.setHeight(qMin(size.height(), screenGeometry.height()));
|
||||
resize(size);
|
||||
@ -56,9 +59,10 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
|
||||
QLabel* descriptionLabel = new QLabel(tr("Select entry to Auto-Type:"), this);
|
||||
layout->addWidget(descriptionLabel);
|
||||
|
||||
connect(m_view, SIGNAL(activated(QModelIndex)), SLOT(emitEntryActivated(QModelIndex)));
|
||||
connect(m_view, SIGNAL(clicked(QModelIndex)), SLOT(emitEntryActivated(QModelIndex)));
|
||||
connect(m_view->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(entryRemoved()));
|
||||
connect(m_view, SIGNAL(activated(QModelIndex)), SLOT(emitMatchActivated(QModelIndex)));
|
||||
connect(m_view, SIGNAL(clicked(QModelIndex)), SLOT(emitMatchActivated(QModelIndex)));
|
||||
connect(m_view->model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(matchRemoved()));
|
||||
connect(m_view, SIGNAL(rejected()), SLOT(reject()));
|
||||
layout->addWidget(m_view);
|
||||
|
||||
QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, Qt::Horizontal, this);
|
||||
@ -66,10 +70,9 @@ AutoTypeSelectDialog::AutoTypeSelectDialog(QWidget* parent)
|
||||
layout->addWidget(buttonBox);
|
||||
}
|
||||
|
||||
void AutoTypeSelectDialog::setEntries(const QList<Entry*>& entries, const QHash<Entry*, QString>& sequences)
|
||||
void AutoTypeSelectDialog::setMatchList(const QList<AutoTypeMatch>& matchList)
|
||||
{
|
||||
m_sequences = sequences;
|
||||
m_view->setEntryList(entries);
|
||||
m_view->setMatchList(matchList);
|
||||
|
||||
m_view->header()->resizeSections(QHeaderView::ResizeToContents);
|
||||
}
|
||||
@ -81,21 +84,32 @@ void AutoTypeSelectDialog::done(int r)
|
||||
QDialog::done(r);
|
||||
}
|
||||
|
||||
void AutoTypeSelectDialog::emitEntryActivated(const QModelIndex& index)
|
||||
void AutoTypeSelectDialog::reject()
|
||||
{
|
||||
// make sure we don't emit the signal twice when both activated() and clicked() are triggered
|
||||
if (m_entryActivatedEmitted) {
|
||||
return;
|
||||
}
|
||||
m_entryActivatedEmitted = true;
|
||||
m_rejected = true;
|
||||
|
||||
Entry* entry = m_view->entryFromIndex(index);
|
||||
accept();
|
||||
emit entryActivated(entry, m_sequences[entry]);
|
||||
QDialog::reject();
|
||||
}
|
||||
|
||||
void AutoTypeSelectDialog::entryRemoved()
|
||||
void AutoTypeSelectDialog::emitMatchActivated(const QModelIndex& index)
|
||||
{
|
||||
// make sure we don't emit the signal twice when both activated() and clicked() are triggered
|
||||
if (m_matchActivatedEmitted) {
|
||||
return;
|
||||
}
|
||||
m_matchActivatedEmitted = true;
|
||||
|
||||
AutoTypeMatch match = m_view->matchFromIndex(index);
|
||||
accept();
|
||||
emit matchActivated(match);
|
||||
}
|
||||
|
||||
void AutoTypeSelectDialog::matchRemoved()
|
||||
{
|
||||
if (m_rejected) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_view->model()->rowCount() == 0) {
|
||||
reject();
|
||||
}
|
||||
|
@ -22,8 +22,9 @@
|
||||
#include <QDialog>
|
||||
#include <QHash>
|
||||
|
||||
#include "core/AutoTypeMatch.h"
|
||||
|
||||
class AutoTypeSelectView;
|
||||
class Entry;
|
||||
|
||||
class AutoTypeSelectDialog : public QDialog
|
||||
{
|
||||
@ -31,22 +32,23 @@ class AutoTypeSelectDialog : public QDialog
|
||||
|
||||
public:
|
||||
explicit AutoTypeSelectDialog(QWidget* parent = nullptr);
|
||||
void setEntries(const QList<Entry*>& entries, const QHash<Entry*, QString>& sequences);
|
||||
void setMatchList(const QList<AutoTypeMatch>& matchList);
|
||||
|
||||
signals:
|
||||
void entryActivated(Entry* entry, const QString& sequence);
|
||||
void matchActivated(AutoTypeMatch match);
|
||||
|
||||
public slots:
|
||||
void done(int r) override;
|
||||
void reject() override;
|
||||
|
||||
private slots:
|
||||
void emitEntryActivated(const QModelIndex& index);
|
||||
void entryRemoved();
|
||||
void emitMatchActivated(const QModelIndex& index);
|
||||
void matchRemoved();
|
||||
|
||||
private:
|
||||
AutoTypeSelectView* const m_view;
|
||||
QHash<Entry*, QString> m_sequences;
|
||||
bool m_entryActivatedEmitted;
|
||||
bool m_matchActivatedEmitted;
|
||||
bool m_rejected;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_AUTOTYPESELECTDIALOG_H
|
||||
|
@ -17,18 +17,16 @@
|
||||
|
||||
#include "AutoTypeSelectView.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
|
||||
AutoTypeSelectView::AutoTypeSelectView(QWidget* parent)
|
||||
: EntryView(parent)
|
||||
: AutoTypeMatchView(parent)
|
||||
{
|
||||
hideColumn(3);
|
||||
setMouseTracking(true);
|
||||
setAllColumnsShowFocus(true);
|
||||
setDragEnabled(false);
|
||||
setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
|
||||
connect(model(), SIGNAL(modelReset()), SLOT(selectFirstEntry()));
|
||||
connect(model(), SIGNAL(modelReset()), SLOT(selectFirstMatch()));
|
||||
}
|
||||
|
||||
void AutoTypeSelectView::mouseMoveEvent(QMouseEvent* event)
|
||||
@ -43,10 +41,10 @@ void AutoTypeSelectView::mouseMoveEvent(QMouseEvent* event)
|
||||
unsetCursor();
|
||||
}
|
||||
|
||||
EntryView::mouseMoveEvent(event);
|
||||
AutoTypeMatchView::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void AutoTypeSelectView::selectFirstEntry()
|
||||
void AutoTypeSelectView::selectFirstMatch()
|
||||
{
|
||||
QModelIndex index = model()->index(0, 0);
|
||||
|
||||
@ -54,3 +52,12 @@ void AutoTypeSelectView::selectFirstEntry()
|
||||
setCurrentIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
void AutoTypeSelectView::keyReleaseEvent(QKeyEvent* e)
|
||||
{
|
||||
if (e->key() == Qt::Key_Escape) {
|
||||
emit rejected();
|
||||
} else {
|
||||
e->ignore();
|
||||
}
|
||||
}
|
@ -18,11 +18,9 @@
|
||||
#ifndef KEEPASSX_AUTOTYPESELECTVIEW_H
|
||||
#define KEEPASSX_AUTOTYPESELECTVIEW_H
|
||||
|
||||
#include "gui/entry/EntryView.h"
|
||||
#include "gui/entry/AutoTypeMatchView.h"
|
||||
|
||||
class Entry;
|
||||
|
||||
class AutoTypeSelectView : public EntryView
|
||||
class AutoTypeSelectView : public AutoTypeMatchView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@ -31,9 +29,13 @@ public:
|
||||
|
||||
protected:
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void keyReleaseEvent(QKeyEvent* e) override;
|
||||
|
||||
private slots:
|
||||
void selectFirstEntry();
|
||||
void selectFirstMatch();
|
||||
|
||||
signals:
|
||||
void rejected();
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_AUTOTYPESELECTVIEW_H
|
||||
|
@ -9,14 +9,12 @@ set(autotype_mac_mm_SOURCES
|
||||
add_library(keepassx-autotype-cocoa MODULE ${autotype_mac_SOURCES} ${autotype_mac_mm_SOURCES})
|
||||
set_target_properties(keepassx-autotype-cocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
|
||||
target_link_libraries(keepassx-autotype-cocoa ${PROGNAME} Qt5::Core Qt5::Widgets)
|
||||
if(NOT DEFINED QT_BINARY_DIR)
|
||||
set(QT_BINARY_DIR "/usr/local/opt/qt5/bin" CACHE PATH "QT binary folder")
|
||||
endif()
|
||||
|
||||
if(WITH_APP_BUNDLE)
|
||||
add_custom_command(TARGET keepassx-autotype-cocoa
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/libkeepassx-autotype-cocoa.so ${PLUGIN_INSTALL_DIR}
|
||||
COMMAND ${QT_BINARY_DIR}/macdeployqt ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins
|
||||
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app -executable=${PLUGIN_INSTALL_DIR}/libkeepassx-autotype-cocoa.so -no-plugins
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
|
||||
COMMENT "Deploying autotype plugin")
|
||||
else()
|
||||
|
59
src/browser/BrowserAccessControlDialog.cpp
Executable file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BrowserAccessControlDialog.h"
|
||||
#include "ui_BrowserAccessControlDialog.h"
|
||||
#include "core/Entry.h"
|
||||
|
||||
BrowserAccessControlDialog::BrowserAccessControlDialog(QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::BrowserAccessControlDialog())
|
||||
{
|
||||
this->setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
|
||||
|
||||
ui->setupUi(this);
|
||||
connect(ui->allowButton, SIGNAL(clicked()), this, SLOT(accept()));
|
||||
connect(ui->denyButton, SIGNAL(clicked()), this, SLOT(reject()));
|
||||
}
|
||||
|
||||
BrowserAccessControlDialog::~BrowserAccessControlDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void BrowserAccessControlDialog::setUrl(const QString& url)
|
||||
{
|
||||
ui->label->setText(QString(tr("%1 has requested access to passwords for the following item(s).\n"
|
||||
"Please select whether you want to allow access.")).arg(QUrl(url).host()));
|
||||
}
|
||||
|
||||
void BrowserAccessControlDialog::setItems(const QList<Entry*>& items)
|
||||
{
|
||||
for (Entry* entry : items) {
|
||||
ui->itemsList->addItem(entry->title() + " - " + entry->username());
|
||||
}
|
||||
}
|
||||
|
||||
bool BrowserAccessControlDialog::remember() const
|
||||
{
|
||||
return ui->rememberDecisionCheckBox->isChecked();
|
||||
}
|
||||
|
||||
void BrowserAccessControlDialog::setRemember(bool r)
|
||||
{
|
||||
ui->rememberDecisionCheckBox->setChecked(r);
|
||||
}
|
48
src/browser/BrowserAccessControlDialog.h
Executable file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERACCESSCONTROLDIALOG_H
|
||||
#define BROWSERACCESSCONTROLDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class Entry;
|
||||
|
||||
namespace Ui {
|
||||
class BrowserAccessControlDialog;
|
||||
}
|
||||
|
||||
class BrowserAccessControlDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BrowserAccessControlDialog(QWidget* parent = nullptr);
|
||||
~BrowserAccessControlDialog();
|
||||
|
||||
void setUrl(const QString& url);
|
||||
void setItems(const QList<Entry*>& items);
|
||||
bool remember() const;
|
||||
void setRemember(bool r);
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::BrowserAccessControlDialog> ui;
|
||||
};
|
||||
|
||||
#endif // BROWSERACCESSCONTROLDIALOG_H
|
69
src/browser/BrowserAccessControlDialog.ui
Executable file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BrowserAccessControlDialog</class>
|
||||
<widget class="QDialog" name="BrowserAccessControlDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>221</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>KeePassXC-Browser Confirm Access</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="itemsList"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="rememberDecisionCheckBox">
|
||||
<property name="text">
|
||||
<string>Remember this decision</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="allowButton">
|
||||
<property name="text">
|
||||
<string>Allow</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="denyButton">
|
||||
<property name="text">
|
||||
<string>Deny</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
554
src/browser/BrowserAction.cpp
Executable file
@ -0,0 +1,554 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
#include "BrowserAction.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "sodium.h"
|
||||
#include "sodium/crypto_box.h"
|
||||
#include "sodium/randombytes.h"
|
||||
#include "config-keepassx.h"
|
||||
|
||||
BrowserAction::BrowserAction(BrowserService& browserService) :
|
||||
m_mutex(QMutex::Recursive),
|
||||
m_browserService(browserService),
|
||||
m_associated(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::readResponse(const QJsonObject& json)
|
||||
{
|
||||
if (json.isEmpty()) {
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
bool triggerUnlock = false;
|
||||
const QString trigger = json.value("triggerUnlock").toString();
|
||||
if (!trigger.isEmpty() && trigger.compare("true", Qt::CaseSensitive) == 0) {
|
||||
triggerUnlock = true;
|
||||
}
|
||||
|
||||
const QString action = json.value("action").toString();
|
||||
if (action.isEmpty()) {
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (action.compare("change-public-keys", Qt::CaseSensitive) != 0 && !m_browserService.isDatabaseOpened()) {
|
||||
if (m_clientPublicKey.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
|
||||
} else if (!m_browserService.openDatabase(triggerUnlock)) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
|
||||
}
|
||||
}
|
||||
|
||||
return handleAction(json);
|
||||
}
|
||||
|
||||
|
||||
// Private functions
|
||||
///////////////////////
|
||||
|
||||
QJsonObject BrowserAction::handleAction(const QJsonObject& json)
|
||||
{
|
||||
QString action = json.value("action").toString();
|
||||
if (action.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
if (action.compare("change-public-keys", Qt::CaseSensitive) == 0) {
|
||||
return handleChangePublicKeys(json, action);
|
||||
} else if (action.compare("get-databasehash", Qt::CaseSensitive) == 0) {
|
||||
return handleGetDatabaseHash(json, action);
|
||||
} else if (action.compare("associate", Qt::CaseSensitive) == 0) {
|
||||
return handleAssociate(json, action);
|
||||
} else if (action.compare("test-associate", Qt::CaseSensitive) == 0) {
|
||||
return handleTestAssociate(json, action);
|
||||
} else if (action.compare("get-logins", Qt::CaseSensitive) == 0) {
|
||||
return handleGetLogins(json, action);
|
||||
} else if (action.compare("generate-password", Qt::CaseSensitive) == 0) {
|
||||
return handleGeneratePassword(json, action);
|
||||
} else if (action.compare("set-login", Qt::CaseSensitive) == 0) {
|
||||
return handleSetLogin(json, action);
|
||||
} else if (action.compare("lock-database", Qt::CaseSensitive) == 0) {
|
||||
return handleLockDatabase(json, action);
|
||||
}
|
||||
|
||||
// Action was not recognized
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString clientPublicKey = json.value("publicKey").toString();
|
||||
|
||||
if (clientPublicKey.isEmpty() || nonce.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
|
||||
}
|
||||
|
||||
m_associated = false;
|
||||
unsigned char pk[crypto_box_PUBLICKEYBYTES];
|
||||
unsigned char sk[crypto_box_SECRETKEYBYTES];
|
||||
crypto_box_keypair(pk, sk);
|
||||
|
||||
const QString publicKey = getBase64FromKey(pk, crypto_box_PUBLICKEYBYTES);
|
||||
const QString secretKey = getBase64FromKey(sk, crypto_box_SECRETKEYBYTES);
|
||||
m_clientPublicKey = clientPublicKey;
|
||||
m_publicKey = publicKey;
|
||||
m_secretKey = secretKey;
|
||||
|
||||
QJsonObject response = buildMessage(incrementNonce(nonce));
|
||||
response["action"] = action;
|
||||
response["publicKey"] = publicKey;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
if (hash.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
|
||||
}
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (!command.isEmpty() && command.compare("get-databasehash", Qt::CaseSensitive) == 0) {
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["hash"] = hash;
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
const QString key = decrypted.value("key").toString();
|
||||
if (key.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (key.compare(m_clientPublicKey, Qt::CaseSensitive) == 0) {
|
||||
const QString id = m_browserService.storeKey(key);
|
||||
if (id.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
|
||||
}
|
||||
|
||||
m_associated = true;
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["hash"] = hash;
|
||||
message["id"] = id;
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
const QString responseKey = decrypted.value("key").toString();
|
||||
const QString id = decrypted.value("id").toString();
|
||||
if (responseKey.isEmpty() || id.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
const QString key = m_browserService.getKey(id);
|
||||
if (key.isEmpty() || key.compare(responseKey, Qt::CaseSensitive) != 0) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
m_associated = true;
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["hash"] = hash;
|
||||
message["id"] = id;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
const QString url = decrypted.value("url").toString();
|
||||
if (url.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
|
||||
}
|
||||
|
||||
const QString id = decrypted.value("id").toString();
|
||||
const QString submit = decrypted.value("submitUrl").toString();
|
||||
const QJsonArray users = m_browserService.findMatchingEntries(id, url, submit, "");
|
||||
|
||||
if (users.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_LOGINS_FOUND);
|
||||
}
|
||||
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["count"] = users.count();
|
||||
message["entries"] = users;
|
||||
message["hash"] = hash;
|
||||
message["id"] = id;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString password = BrowserSettings::generatePassword();
|
||||
const QString bits = QString::number(BrowserSettings::getbits()); // For some reason this always returns 1140 bits?
|
||||
|
||||
if (nonce.isEmpty() || password.isEmpty()) {
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonArray arr;
|
||||
QJsonObject passwd;
|
||||
passwd["login"] = QString::number(password.length() * 8); //bits;
|
||||
passwd["password"] = password;
|
||||
arr.append(passwd);
|
||||
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["entries"] = arr;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
const QString url = decrypted.value("url").toString();
|
||||
if (url.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
|
||||
}
|
||||
|
||||
const QString id = decrypted.value("id").toString();
|
||||
const QString login = decrypted.value("login").toString();
|
||||
const QString password = decrypted.value("password").toString();
|
||||
const QString submitUrl = decrypted.value("submitUrl").toString();
|
||||
const QString uuid = decrypted.value("uuid").toString();
|
||||
const QString realm;
|
||||
|
||||
if (uuid.isEmpty()) {
|
||||
m_browserService.addEntry(id, login, password, url, submitUrl, realm);
|
||||
} else {
|
||||
m_browserService.updateEntry(id, uuid, login, password, url);
|
||||
}
|
||||
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
message["count"] = QJsonValue::Null;
|
||||
message["entries"] = QJsonValue::Null;
|
||||
message["error"] = "";
|
||||
message["hash"] = hash;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
if (hash.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
|
||||
}
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (!command.isEmpty() && command.compare("lock-database", Qt::CaseSensitive) == 0) {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_browserService.lockDatabase();
|
||||
|
||||
const QString newNonce = incrementNonce(nonce);
|
||||
QJsonObject message = buildMessage(newNonce);
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
}
|
||||
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const
|
||||
{
|
||||
QJsonObject response;
|
||||
response["action"] = action;
|
||||
response["errorCode"] = QString::number(errorCode);
|
||||
response["error"] = getErrorMessage(errorCode);
|
||||
return response;
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::buildMessage(const QString& nonce) const
|
||||
{
|
||||
QJsonObject message;
|
||||
message["version"] = KEEPASSX_VERSION;
|
||||
message["success"] = "true";
|
||||
message["nonce"] = nonce;
|
||||
return message;
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::buildResponse(const QString& action, const QJsonObject& message, const QString& nonce)
|
||||
{
|
||||
QJsonObject response;
|
||||
response["action"] = action;
|
||||
response["message"] = encryptMessage(message, nonce);
|
||||
response["nonce"] = nonce;
|
||||
return response;
|
||||
}
|
||||
|
||||
QString BrowserAction::getErrorMessage(const int errorCode) const
|
||||
{
|
||||
switch (errorCode) {
|
||||
case ERROR_KEEPASS_DATABASE_NOT_OPENED: return QObject::tr("Database not opened");
|
||||
case ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED: return QObject::tr("Database hash not available");
|
||||
case ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED: return QObject::tr("Client public key not received");
|
||||
case ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE: return QObject::tr("Cannot decrypt message");
|
||||
case ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED: return QObject::tr("Timeout or cannot connect to KeePassXC");
|
||||
case ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED: return QObject::tr("Action cancelled or denied");
|
||||
case ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE: return QObject::tr("Cannot encrypt message or public key not found. Is Native Messaging enabled in KeePassXC?");
|
||||
case ERROR_KEEPASS_ASSOCIATION_FAILED: return QObject::tr("KeePassXC association failed, try again");
|
||||
case ERROR_KEEPASS_KEY_CHANGE_FAILED: return QObject::tr("Key change was not successful");
|
||||
case ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED: return QObject::tr("Encryption key is not recognized");
|
||||
case ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND: return QObject::tr("No saved databases found");
|
||||
case ERROR_KEEPASS_INCORRECT_ACTION: return QObject::tr("Incorrect action");
|
||||
case ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED: return QObject::tr("Empty message received");
|
||||
case ERROR_KEEPASS_NO_URL_PROVIDED: return QObject::tr("No URL provided");
|
||||
case ERROR_KEEPASS_NO_LOGINS_FOUND: return QObject::tr("No logins found");
|
||||
default: return QObject::tr("Unknown error");
|
||||
}
|
||||
}
|
||||
|
||||
QString BrowserAction::getDatabaseHash()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
QByteArray hash = QCryptographicHash::hash(
|
||||
(m_browserService.getDatabaseRootUuid() + m_browserService.getDatabaseRecycleBinUuid()).toUtf8(),
|
||||
QCryptographicHash::Sha256).toHex();
|
||||
return QString(hash);
|
||||
}
|
||||
|
||||
QString BrowserAction::encryptMessage(const QJsonObject& message, const QString& nonce)
|
||||
{
|
||||
if (message.isEmpty() || nonce.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
const QString reply(QJsonDocument(message).toJson());
|
||||
if (!reply.isEmpty()) {
|
||||
return encrypt(reply, nonce);
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::decryptMessage(const QString& message, const QString& nonce, const QString& action)
|
||||
{
|
||||
if (message.isEmpty() || nonce.isEmpty()) {
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QByteArray ba = decrypt(message, nonce);
|
||||
if (!ba.isEmpty()) {
|
||||
return getJsonObject(ba);
|
||||
}
|
||||
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
QString BrowserAction::encrypt(const QString plaintext, const QString nonce)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
const QByteArray ma = plaintext.toUtf8();
|
||||
const QByteArray na = base64Decode(nonce);
|
||||
const QByteArray ca = base64Decode(m_clientPublicKey);
|
||||
const QByteArray sa = base64Decode(m_secretKey);
|
||||
|
||||
std::vector<unsigned char> m(ma.cbegin(), ma.cend());
|
||||
std::vector<unsigned char> n(na.cbegin(), na.cend());
|
||||
std::vector<unsigned char> ck(ca.cbegin(), ca.cend());
|
||||
std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
|
||||
|
||||
std::vector<unsigned char> e;
|
||||
e.resize(max_length);
|
||||
|
||||
if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (crypto_box_easy(e.data(), m.data(), m.size(), n.data(), ck.data(), sk.data()) == 0) {
|
||||
QByteArray res = getQByteArray(e.data(), (crypto_box_MACBYTES + ma.length()));
|
||||
return res.toBase64();
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QByteArray BrowserAction::decrypt(const QString encrypted, const QString nonce)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
const QByteArray ma = base64Decode(encrypted);
|
||||
const QByteArray na = base64Decode(nonce);
|
||||
const QByteArray ca = base64Decode(m_clientPublicKey);
|
||||
const QByteArray sa = base64Decode(m_secretKey);
|
||||
|
||||
std::vector<unsigned char> m(ma.cbegin(), ma.cend());
|
||||
std::vector<unsigned char> n(na.cbegin(), na.cend());
|
||||
std::vector<unsigned char> ck(ca.cbegin(), ca.cend());
|
||||
std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
|
||||
|
||||
std::vector<unsigned char> d;
|
||||
d.resize(max_length);
|
||||
|
||||
if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
if (crypto_box_open_easy(d.data(), m.data(), ma.length(), n.data(), ck.data(), sk.data()) == 0) {
|
||||
return getQByteArray(d.data(), std::char_traits<char>::length(reinterpret_cast<const char *>(d.data())));
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QString BrowserAction::getBase64FromKey(const uchar* array, const uint len)
|
||||
{
|
||||
return getQByteArray(array, len).toBase64();
|
||||
}
|
||||
|
||||
QByteArray BrowserAction::getQByteArray(const uchar* array, const uint len) const
|
||||
{
|
||||
QByteArray qba;
|
||||
qba.reserve(len);
|
||||
for (uint i = 0; i < len; ++i) {
|
||||
qba.append(static_cast<char>(array[i]));
|
||||
}
|
||||
return qba;
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::getJsonObject(const uchar* pArray, const uint len) const
|
||||
{
|
||||
QByteArray arr = getQByteArray(pArray, len);
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc(QJsonDocument::fromJson(arr, &err));
|
||||
return doc.object();
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::getJsonObject(const QByteArray ba) const
|
||||
{
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc(QJsonDocument::fromJson(ba, &err));
|
||||
return doc.object();
|
||||
}
|
||||
|
||||
QByteArray BrowserAction::base64Decode(const QString str)
|
||||
{
|
||||
return QByteArray::fromBase64(str.toUtf8());
|
||||
}
|
||||
|
||||
QString BrowserAction::incrementNonce(const QString& nonce)
|
||||
{
|
||||
const QByteArray nonceArray = base64Decode(nonce);
|
||||
std::vector<unsigned char> n(nonceArray.cbegin(), nonceArray.cend());
|
||||
|
||||
sodium_increment(n.data(), n.size());
|
||||
return getQByteArray(n.data(), n.size()).toBase64();
|
||||
}
|
||||
|
||||
void BrowserAction::removeSharedEncryptionKeys()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_browserService.removeSharedEncryptionKeys();
|
||||
}
|
||||
|
||||
void BrowserAction::removeStoredPermissions()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_browserService.removeStoredPermissions();
|
||||
}
|
98
src/browser/BrowserAction.h
Executable file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERACTION_H
|
||||
#define BROWSERACTION_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QMutex>
|
||||
#include "BrowserService.h"
|
||||
|
||||
class BrowserAction : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
enum {
|
||||
ERROR_KEEPASS_DATABASE_NOT_OPENED = 1,
|
||||
ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED = 2,
|
||||
ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED = 3,
|
||||
ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE = 4,
|
||||
ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED = 5,
|
||||
ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED = 6,
|
||||
ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE = 7,
|
||||
ERROR_KEEPASS_ASSOCIATION_FAILED = 8,
|
||||
ERROR_KEEPASS_KEY_CHANGE_FAILED = 9,
|
||||
ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED = 10,
|
||||
ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND = 11,
|
||||
ERROR_KEEPASS_INCORRECT_ACTION = 12,
|
||||
ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13,
|
||||
ERROR_KEEPASS_NO_URL_PROVIDED = 14,
|
||||
ERROR_KEEPASS_NO_LOGINS_FOUND = 15
|
||||
};
|
||||
|
||||
public:
|
||||
BrowserAction(BrowserService& browserService);
|
||||
~BrowserAction() = default;
|
||||
|
||||
QJsonObject readResponse(const QJsonObject& json);
|
||||
|
||||
public slots:
|
||||
void removeSharedEncryptionKeys();
|
||||
void removeStoredPermissions();
|
||||
|
||||
private:
|
||||
QJsonObject handleAction(const QJsonObject& json);
|
||||
QJsonObject handleChangePublicKeys(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleGetDatabaseHash(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleAssociate(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleTestAssociate(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleGetLogins(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleGeneratePassword(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleSetLogin(const QJsonObject& json, const QString& action);
|
||||
QJsonObject handleLockDatabase(const QJsonObject& json, const QString& action);
|
||||
|
||||
QJsonObject buildMessage(const QString& nonce) const;
|
||||
QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce);
|
||||
QJsonObject getErrorReply(const QString& action, const int errorCode) const;
|
||||
QString getErrorMessage(const int errorCode) const;
|
||||
QString getDatabaseHash();
|
||||
|
||||
QString encryptMessage(const QJsonObject& message, const QString& nonce);
|
||||
QJsonObject decryptMessage(const QString& message, const QString& nonce, const QString& action = QString());
|
||||
QString encrypt(const QString plaintext, const QString nonce);
|
||||
QByteArray decrypt(const QString encrypted, const QString nonce);
|
||||
|
||||
QString getBase64FromKey(const uchar* array, const uint len);
|
||||
QByteArray getQByteArray(const uchar* array, const uint len) const;
|
||||
QJsonObject getJsonObject(const uchar* pArray, const uint len) const;
|
||||
QJsonObject getJsonObject(const QByteArray ba) const;
|
||||
QByteArray base64Decode(const QString str);
|
||||
QString incrementNonce(const QString& nonce);
|
||||
|
||||
private:
|
||||
QMutex m_mutex;
|
||||
BrowserService& m_browserService;
|
||||
QString m_clientPublicKey;
|
||||
QString m_publicKey;
|
||||
QString m_secretKey;
|
||||
bool m_associated;
|
||||
};
|
||||
|
||||
#endif // BROWSERACTION_H
|
77
src/browser/BrowserClients.cpp
Executable file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QJsonValue>
|
||||
#include <QJsonParseError>
|
||||
#include "BrowserClients.h"
|
||||
|
||||
BrowserClients::BrowserClients(BrowserService& browserService) :
|
||||
m_mutex(QMutex::Recursive),
|
||||
m_browserService(browserService)
|
||||
{
|
||||
m_clients.reserve(1000);
|
||||
}
|
||||
|
||||
QJsonObject BrowserClients::readResponse(const QByteArray& arr)
|
||||
{
|
||||
QJsonObject json;
|
||||
const QJsonObject message = byteArrayToJson(arr);
|
||||
const QString clientID = getClientID(message);
|
||||
|
||||
if (!clientID.isEmpty()) {
|
||||
const ClientPtr client = getClient(clientID);
|
||||
if (client->browserAction) {
|
||||
json = client->browserAction->readResponse(message);
|
||||
}
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
QJsonObject BrowserClients::byteArrayToJson(const QByteArray& arr) const
|
||||
{
|
||||
QJsonObject json;
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc(QJsonDocument::fromJson(arr, &err));
|
||||
if (doc.isObject()) {
|
||||
json = doc.object();
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
QString BrowserClients::getClientID(const QJsonObject& json) const
|
||||
{
|
||||
return json["clientID"].toString();
|
||||
}
|
||||
|
||||
BrowserClients::ClientPtr BrowserClients::getClient(const QString& clientID)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
for (const auto &i : m_clients) {
|
||||
if (i->clientID.compare(clientID, Qt::CaseSensitive) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// clientID not found, create a new client
|
||||
QSharedPointer<BrowserAction> ba = QSharedPointer<BrowserAction>::create(m_browserService);
|
||||
ClientPtr client = ClientPtr::create(clientID, ba);
|
||||
m_clients.push_back(client);
|
||||
return m_clients.back();
|
||||
}
|
56
src/browser/BrowserClients.h
Executable file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERCLIENTS_H
|
||||
#define BROWSERCLIENTS_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QMutex>
|
||||
#include <QVector>
|
||||
#include <QSharedPointer>
|
||||
#include <QLocalSocket>
|
||||
#include "BrowserAction.h"
|
||||
|
||||
class BrowserClients
|
||||
{
|
||||
struct Client {
|
||||
Client(const QString& id, QSharedPointer<BrowserAction> ba) : clientID(id), browserAction(ba) {}
|
||||
QString clientID;
|
||||
QSharedPointer<BrowserAction> browserAction;
|
||||
};
|
||||
|
||||
typedef QSharedPointer<Client> ClientPtr;
|
||||
|
||||
public:
|
||||
BrowserClients(BrowserService& browserService);
|
||||
~BrowserClients() = default;
|
||||
|
||||
QJsonObject readResponse(const QByteArray& arr);
|
||||
|
||||
private:
|
||||
QJsonObject byteArrayToJson(const QByteArray& arr) const;
|
||||
QString getClientID(const QJsonObject& json) const;
|
||||
ClientPtr getClient(const QString& clientID);
|
||||
|
||||
private:
|
||||
QMutex m_mutex;
|
||||
QVector<ClientPtr> m_clients;
|
||||
BrowserService& m_browserService;
|
||||
};
|
||||
|
||||
#endif // BROWSERCLIENTS_H
|
109
src/browser/BrowserEntryConfig.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BrowserEntryConfig.h"
|
||||
#include <QtCore>
|
||||
#include "core/Entry.h"
|
||||
#include "core/EntryAttributes.h"
|
||||
|
||||
static const char KEEPASSBROWSER_NAME[] = "KeePassXC-Browser Settings";
|
||||
|
||||
|
||||
BrowserEntryConfig::BrowserEntryConfig(QObject* parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QStringList BrowserEntryConfig::allowedHosts() const
|
||||
{
|
||||
return m_allowedHosts.toList();
|
||||
}
|
||||
|
||||
void BrowserEntryConfig::setAllowedHosts(const QStringList& allowedHosts)
|
||||
{
|
||||
m_allowedHosts = allowedHosts.toSet();
|
||||
}
|
||||
|
||||
QStringList BrowserEntryConfig::deniedHosts() const
|
||||
{
|
||||
return m_deniedHosts.toList();
|
||||
}
|
||||
|
||||
void BrowserEntryConfig::setDeniedHosts(const QStringList& deniedHosts)
|
||||
{
|
||||
m_deniedHosts = deniedHosts.toSet();
|
||||
}
|
||||
|
||||
bool BrowserEntryConfig::isAllowed(const QString& host) const
|
||||
{
|
||||
return m_allowedHosts.contains(host);
|
||||
}
|
||||
|
||||
void BrowserEntryConfig::allow(const QString& host)
|
||||
{
|
||||
m_allowedHosts.insert(host);
|
||||
m_deniedHosts.remove(host);
|
||||
}
|
||||
|
||||
bool BrowserEntryConfig::isDenied(const QString& host) const
|
||||
{
|
||||
return m_deniedHosts.contains(host);
|
||||
}
|
||||
|
||||
void BrowserEntryConfig::deny(const QString& host)
|
||||
{
|
||||
m_deniedHosts.insert(host);
|
||||
m_allowedHosts.remove(host);
|
||||
}
|
||||
|
||||
QString BrowserEntryConfig::realm() const
|
||||
{
|
||||
return m_realm;
|
||||
}
|
||||
|
||||
void BrowserEntryConfig::setRealm(const QString& realm)
|
||||
{
|
||||
m_realm = realm;
|
||||
}
|
||||
|
||||
bool BrowserEntryConfig::load(const Entry* entry)
|
||||
{
|
||||
QString s = entry->attributes()->value(KEEPASSBROWSER_NAME);
|
||||
if (s.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(s.toUtf8());
|
||||
if (doc.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariantMap map = doc.object().toVariantMap();
|
||||
for (QVariantMap::const_iterator iter = map.cbegin(); iter != map.cend(); ++iter) {
|
||||
setProperty(iter.key().toLatin1(), iter.value());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BrowserEntryConfig::save(Entry* entry)
|
||||
{
|
||||
QVariantMap v = qo2qv(this);
|
||||
QJsonObject o = QJsonObject::fromVariantMap(v);
|
||||
QByteArray json = QJsonDocument(o).toJson(QJsonDocument::Compact);
|
||||
entry->attributes()->set(KEEPASSBROWSER_NAME, json);
|
||||
}
|
60
src/browser/BrowserEntryConfig.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERENTRYCONFIG_H
|
||||
#define BROWSERENTRYCONFIG_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QSet>
|
||||
#include "Variant.h"
|
||||
|
||||
class Entry;
|
||||
|
||||
class BrowserEntryConfig : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QStringList Allow READ allowedHosts WRITE setAllowedHosts)
|
||||
Q_PROPERTY(QStringList Deny READ deniedHosts WRITE setDeniedHosts )
|
||||
Q_PROPERTY(QString Realm READ realm WRITE setRealm )
|
||||
|
||||
public:
|
||||
BrowserEntryConfig(QObject* object = 0);
|
||||
|
||||
bool load(const Entry* entry);
|
||||
void save(Entry* entry);
|
||||
bool isAllowed(const QString& host) const;
|
||||
void allow(const QString& host);
|
||||
bool isDenied(const QString& host) const;
|
||||
void deny(const QString& host);
|
||||
QString realm() const;
|
||||
void setRealm(const QString& realm);
|
||||
|
||||
private:
|
||||
QStringList allowedHosts() const;
|
||||
void setAllowedHosts(const QStringList& allowedHosts);
|
||||
QStringList deniedHosts() const;
|
||||
void setDeniedHosts(const QStringList& deniedHosts);
|
||||
|
||||
QSet<QString> m_allowedHosts;
|
||||
QSet<QString> m_deniedHosts;
|
||||
QString m_realm;
|
||||
};
|
||||
|
||||
#endif // BROWSERENTRYCONFIG_H
|
141
src/browser/BrowserOptionDialog.cpp
Executable file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "BrowserOptionDialog.h"
|
||||
#include "ui_BrowserOptionDialog.h"
|
||||
#include "config-keepassx.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "core/FilePath.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
|
||||
BrowserOptionDialog::BrowserOptionDialog(QWidget* parent) :
|
||||
QWidget(parent),
|
||||
m_ui(new Ui::BrowserOptionDialog())
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
connect(m_ui->removeSharedEncryptionKeys, SIGNAL(clicked()), this, SIGNAL(removeSharedEncryptionKeys()));
|
||||
connect(m_ui->removeStoredPermissions, SIGNAL(clicked()), this, SIGNAL(removeStoredPermissions()));
|
||||
|
||||
m_ui->warningWidget->showMessage(tr("<b>Warning:</b> The following options can be dangerous!"), MessageWidget::Warning);
|
||||
m_ui->warningWidget->setCloseButtonVisible(false);
|
||||
m_ui->warningWidget->setAutoHideTimeout(-1);
|
||||
|
||||
m_ui->tabWidget->setEnabled(m_ui->enableBrowserSupport->isChecked());
|
||||
connect(m_ui->enableBrowserSupport, SIGNAL(toggled(bool)), m_ui->tabWidget, SLOT(setEnabled(bool)));
|
||||
|
||||
m_ui->customProxyLocation->setEnabled(m_ui->useCustomProxy->isChecked());
|
||||
m_ui->customProxyLocationBrowseButton->setEnabled(m_ui->useCustomProxy->isChecked());
|
||||
connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), m_ui->customProxyLocation, SLOT(setEnabled(bool)));
|
||||
connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), m_ui->customProxyLocationBrowseButton, SLOT(setEnabled(bool)));
|
||||
connect(m_ui->customProxyLocationBrowseButton, SIGNAL(clicked()), this, SLOT(showProxyLocationFileDialog()));
|
||||
|
||||
m_ui->browserGlobalWarningWidget->setVisible(false);
|
||||
}
|
||||
|
||||
BrowserOptionDialog::~BrowserOptionDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void BrowserOptionDialog::loadSettings()
|
||||
{
|
||||
BrowserSettings settings;
|
||||
m_ui->enableBrowserSupport->setChecked(settings.isEnabled());
|
||||
|
||||
m_ui->showNotification->setChecked(settings.showNotification());
|
||||
m_ui->bestMatchOnly->setChecked(settings.bestMatchOnly());
|
||||
m_ui->unlockDatabase->setChecked(settings.unlockDatabase());
|
||||
m_ui->matchUrlScheme->setChecked(settings.matchUrlScheme());
|
||||
|
||||
// hide unimplemented options
|
||||
// TODO: fix this
|
||||
m_ui->showNotification->hide();
|
||||
m_ui->bestMatchOnly->hide();
|
||||
|
||||
if (settings.sortByUsername()) {
|
||||
m_ui->sortByUsername->setChecked(true);
|
||||
} else {
|
||||
m_ui->sortByTitle->setChecked(true);
|
||||
}
|
||||
|
||||
m_ui->alwaysAllowAccess->setChecked(settings.alwaysAllowAccess());
|
||||
m_ui->alwaysAllowUpdate->setChecked(settings.alwaysAllowUpdate());
|
||||
m_ui->searchInAllDatabases->setChecked(settings.searchInAllDatabases());
|
||||
m_ui->supportKphFields->setChecked(settings.supportKphFields());
|
||||
m_ui->supportBrowserProxy->setChecked(settings.supportBrowserProxy());
|
||||
m_ui->useCustomProxy->setChecked(settings.useCustomProxy());
|
||||
m_ui->customProxyLocation->setText(settings.customProxyLocation());
|
||||
m_ui->updateBinaryPath->setChecked(settings.updateBinaryPath());
|
||||
m_ui->chromeSupport->setChecked(settings.chromeSupport());
|
||||
m_ui->chromiumSupport->setChecked(settings.chromiumSupport());
|
||||
m_ui->firefoxSupport->setChecked(settings.firefoxSupport());
|
||||
m_ui->vivaldiSupport->setChecked(settings.vivaldiSupport());
|
||||
|
||||
#if defined(KEEPASSXC_DIST_APPIMAGE)
|
||||
m_ui->supportBrowserProxy->setChecked(true);
|
||||
m_ui->supportBrowserProxy->setEnabled(false);
|
||||
#elif defined(KEEPASSXC_DIST_SNAP)
|
||||
m_ui->enableBrowserSupport->setChecked(false);
|
||||
m_ui->enableBrowserSupport->setEnabled(false);
|
||||
m_ui->browserGlobalWarningWidget->showMessage(
|
||||
tr("We're sorry, but KeePassXC-Browser is not supported for Snap releases at the moment."), MessageWidget::Warning);
|
||||
m_ui->browserGlobalWarningWidget->setCloseButtonVisible(false);
|
||||
m_ui->browserGlobalWarningWidget->setAutoHideTimeout(-1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void BrowserOptionDialog::saveSettings()
|
||||
{
|
||||
BrowserSettings settings;
|
||||
settings.setEnabled(m_ui->enableBrowserSupport->isChecked());
|
||||
settings.setShowNotification(m_ui->showNotification->isChecked());
|
||||
settings.setBestMatchOnly(m_ui->bestMatchOnly->isChecked());
|
||||
settings.setUnlockDatabase(m_ui->unlockDatabase->isChecked());
|
||||
settings.setMatchUrlScheme(m_ui->matchUrlScheme->isChecked());
|
||||
settings.setSortByUsername(m_ui->sortByUsername->isChecked());
|
||||
|
||||
settings.setSupportBrowserProxy(m_ui->supportBrowserProxy->isChecked());
|
||||
settings.setUseCustomProxy(m_ui->useCustomProxy->isChecked());
|
||||
settings.setCustomProxyLocation(m_ui->customProxyLocation->text());
|
||||
|
||||
settings.setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked());
|
||||
settings.setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked());
|
||||
settings.setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked());
|
||||
settings.setSearchInAllDatabases(m_ui->searchInAllDatabases->isChecked());
|
||||
settings.setSupportKphFields(m_ui->supportKphFields->isChecked());
|
||||
|
||||
settings.setChromeSupport(m_ui->chromeSupport->isChecked());
|
||||
settings.setChromiumSupport(m_ui->chromiumSupport->isChecked());
|
||||
settings.setFirefoxSupport(m_ui->firefoxSupport->isChecked());
|
||||
settings.setVivaldiSupport(m_ui->vivaldiSupport->isChecked());
|
||||
}
|
||||
|
||||
void BrowserOptionDialog::showProxyLocationFileDialog()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
QString fileTypeFilter(tr("Executable Files (*.exe);;All Files (*.*)"));
|
||||
#else
|
||||
QString fileTypeFilter(tr("Executable Files (*)"));
|
||||
#endif
|
||||
auto proxyLocation = QFileDialog::getOpenFileName(this, tr("Select custom proxy location"),
|
||||
QFileInfo(QCoreApplication::applicationDirPath()).filePath(),
|
||||
fileTypeFilter);
|
||||
m_ui->customProxyLocation->setText(proxyLocation);
|
||||
}
|
53
src/browser/BrowserOptionDialog.h
Executable file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSEROPTIONDIALOG_H
|
||||
#define BROWSEROPTIONDIALOG_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QScopedPointer>
|
||||
|
||||
namespace Ui {
|
||||
class BrowserOptionDialog;
|
||||
}
|
||||
|
||||
class BrowserOptionDialog : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BrowserOptionDialog(QWidget* parent = nullptr);
|
||||
~BrowserOptionDialog();
|
||||
|
||||
public slots:
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
signals:
|
||||
void removeSharedEncryptionKeys();
|
||||
void removeStoredPermissions();
|
||||
|
||||
private slots:
|
||||
void showProxyLocationFileDialog();
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::BrowserOptionDialog> m_ui;
|
||||
};
|
||||
|
||||
#endif // BROWSEROPTIONDIALOG_H
|
377
src/browser/BrowserOptionDialog.ui
Executable file
@ -0,0 +1,377 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BrowserOptionDialog</class>
|
||||
<widget class="QWidget" name="BrowserOptionDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>523</width>
|
||||
<height>456</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="MessageWidget" name="browserGlobalWarningWidget" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableBrowserSupport">
|
||||
<property name="toolTip">
|
||||
<string>This is required for accessing your databases with KeePassXC-Browser</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable KeepassXC browser integration</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="browsersGroupBox">
|
||||
<property name="title">
|
||||
<string>Enable integration for these browsers:</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="horizontalSpacing">
|
||||
<number>40</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="chromeSupport">
|
||||
<property name="text">
|
||||
<string>&Google Chrome</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="firefoxSupport">
|
||||
<property name="text">
|
||||
<string>&Firefox</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>179</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="chromiumSupport">
|
||||
<property name="text">
|
||||
<string>&Chromium</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="vivaldiSupport">
|
||||
<property name="text">
|
||||
<string>&Vivaldi</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>4</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showNotification">
|
||||
<property name="text">
|
||||
<string extracomment="Credentials mean login data requested via browser extension">Show a &notification when credentials are requested</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="unlockDatabase">
|
||||
<property name="text">
|
||||
<string>Re&quest to unlock the database if it is locked</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="matchUrlScheme">
|
||||
<property name="toolTip">
|
||||
<string>Only entries with the same scheme (http://, https://, ...) are returned.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Match URL scheme (e.g., https://...)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="bestMatchOnly">
|
||||
<property name="toolTip">
|
||||
<string>Only returns the best matches for a specific URL instead of all entries for the whole domain.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Return only best-matching credentials</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sortByTitle">
|
||||
<property name="text">
|
||||
<string extracomment="Credentials mean login data requested via browser extension">Sort &matching credentials by title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sortByUsername">
|
||||
<property name="text">
|
||||
<string extracomment="Credentials mean login data requested via browser extension">Sort matching credentials by &username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeSharedEncryptionKeys">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Disconnect all browsers</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeStoredPermissions">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Forget all remembered &permissions</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Advanced</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="MessageWidget" name="warningWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alwaysAllowAccess">
|
||||
<property name="text">
|
||||
<string extracomment="Credentials mean login data requested via browser extension">Never &ask before accessing credentials</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alwaysAllowUpdate">
|
||||
<property name="text">
|
||||
<string extracomment="Credentials mean login data requested via browser extension">Never ask before &updating credentials</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="searchInAllDatabases">
|
||||
<property name="toolTip">
|
||||
<string>Only the selected database has to be connected with a client.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string extracomment="Credentials mean login data requested via browser extension">Searc&h in all opened databases for matching credentials</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="supportKphFields">
|
||||
<property name="toolTip">
|
||||
<string>Automatically creating or updating string fields is not supported.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Return advanced string fields which start with "KPH: "</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="updateBinaryPath">
|
||||
<property name="toolTip">
|
||||
<string>Updates KeePassXC or keepassxc-proxy binary path automatically to native messaging scripts on startup.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Update &native messaging manifest files at startup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="supportBrowserProxy">
|
||||
<property name="toolTip">
|
||||
<string>Support a proxy application between KeePassXC and browser extension.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use a &proxy application between KeePassXC and browser extension</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useCustomProxy">
|
||||
<property name="toolTip">
|
||||
<string>Use a custom proxy location if you installed a proxy manually.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string comment="Meant is the proxy for KeePassXC-Browser">Use a &custom proxy location</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="customProxyLocation">
|
||||
<property name="maxLength">
|
||||
<number>999</number>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="customProxyLocationBrowseButton">
|
||||
<property name="text">
|
||||
<string extracomment="Button for opening file dialog">Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MessageWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/MessageWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
744
src/browser/BrowserService.cpp
Normal file
@ -0,0 +1,744 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QInputDialog>
|
||||
#include <QProgressDialog>
|
||||
#include <QMessageBox>
|
||||
#include "BrowserService.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "BrowserEntryConfig.h"
|
||||
#include "BrowserAccessControlDialog.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/EntrySearcher.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/Uuid.h"
|
||||
#include "core/PasswordGenerator.h"
|
||||
#include "gui/MainWindow.h"
|
||||
|
||||
|
||||
// de887cc3-0363-43b8-974b-5911b8816224
|
||||
static const unsigned char KEEPASSXCBROWSER_UUID_DATA[] = {
|
||||
0xde, 0x88, 0x7c, 0xc3, 0x03, 0x63, 0x43, 0xb8,
|
||||
0x97, 0x4b, 0x59, 0x11, 0xb8, 0x81, 0x62, 0x24
|
||||
};
|
||||
static const Uuid KEEPASSXCBROWSER_UUID = Uuid(QByteArray::fromRawData(reinterpret_cast<const char *>(KEEPASSXCBROWSER_UUID_DATA), sizeof(KEEPASSXCBROWSER_UUID_DATA)));
|
||||
static const char KEEPASSXCBROWSER_NAME[] = "KeePassXC-Browser Settings";
|
||||
static const char ASSOCIATE_KEY_PREFIX[] = "Public Key: ";
|
||||
static const char KEEPASSXCBROWSER_GROUP_NAME[] = "KeePassXC-Browser Passwords";
|
||||
static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
|
||||
|
||||
BrowserService::BrowserService(DatabaseTabWidget* parent) :
|
||||
m_dbTabWidget(parent),
|
||||
m_dialogActive(false)
|
||||
{
|
||||
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), this, SLOT(databaseLocked(DatabaseWidget*)));
|
||||
connect(m_dbTabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), this, SLOT(databaseUnlocked(DatabaseWidget*)));
|
||||
connect(m_dbTabWidget, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), this, SLOT(activateDatabaseChanged(DatabaseWidget*)));
|
||||
}
|
||||
|
||||
bool BrowserService::isDatabaseOpened() const
|
||||
{
|
||||
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
|
||||
if (!dbWidget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode;
|
||||
|
||||
}
|
||||
|
||||
bool BrowserService::openDatabase(bool triggerUnlock)
|
||||
{
|
||||
if (!BrowserSettings::unlockDatabase()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
|
||||
if (!dbWidget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (triggerUnlock) {
|
||||
KEEPASSXC_MAIN_WINDOW->bringToFront();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BrowserService::lockDatabase()
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "lockDatabase", Qt::BlockingQueuedConnection);
|
||||
}
|
||||
|
||||
DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
|
||||
if (!dbWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
|
||||
dbWidget->lock();
|
||||
}
|
||||
}
|
||||
|
||||
QString BrowserService::getDatabaseRootUuid()
|
||||
{
|
||||
Database* db = getDatabase();
|
||||
if (!db) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
Group* rootGroup = db->rootGroup();
|
||||
if (!rootGroup) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
return rootGroup->uuid().toHex();
|
||||
}
|
||||
|
||||
QString BrowserService::getDatabaseRecycleBinUuid()
|
||||
{
|
||||
Database* db = getDatabase();
|
||||
if (!db) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
Group* recycleBin = db->metadata()->recycleBin();
|
||||
if (!recycleBin) {
|
||||
return QString();
|
||||
}
|
||||
return recycleBin->uuid().toHex();
|
||||
}
|
||||
|
||||
Entry* BrowserService::getConfigEntry(bool create)
|
||||
{
|
||||
Entry* entry = nullptr;
|
||||
Database* db = getDatabase();
|
||||
if (!db) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
entry = db->resolveEntry(KEEPASSXCBROWSER_UUID);
|
||||
if (!entry && create) {
|
||||
entry = new Entry();
|
||||
entry->setTitle(QLatin1String(KEEPASSXCBROWSER_NAME));
|
||||
entry->setUuid(KEEPASSXCBROWSER_UUID);
|
||||
entry->setAutoTypeEnabled(false);
|
||||
entry->setGroup(db->rootGroup());
|
||||
return entry;
|
||||
}
|
||||
|
||||
if (entry && entry->group() == db->metadata()->recycleBin()) {
|
||||
if (!create) {
|
||||
return nullptr;
|
||||
} else {
|
||||
entry->setGroup(db->rootGroup());
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
QString BrowserService::storeKey(const QString& key)
|
||||
{
|
||||
QString id;
|
||||
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "storeKey", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QString, id),
|
||||
Q_ARG(const QString&, key));
|
||||
return id;
|
||||
}
|
||||
|
||||
Entry* config = getConfigEntry(true);
|
||||
if (!config) {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool contains;
|
||||
QMessageBox::StandardButton dialogResult = QMessageBox::No;
|
||||
|
||||
do {
|
||||
QInputDialog keyDialog;
|
||||
keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
|
||||
keyDialog.setLabelText(tr("You have received an association request for the above key.\n\n"
|
||||
"If you would like to allow it access to your KeePassXC database,\n"
|
||||
"give it a unique name to identify and accept it."));
|
||||
keyDialog.setOkButtonText(tr("Save and allow access"));
|
||||
keyDialog.setWindowFlags(keyDialog.windowFlags() | Qt::WindowStaysOnTopHint);
|
||||
keyDialog.show();
|
||||
keyDialog.activateWindow();
|
||||
keyDialog.raise();
|
||||
auto ok = keyDialog.exec();
|
||||
|
||||
id = keyDialog.textValue();
|
||||
|
||||
if (ok != QDialog::Accepted || id.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
contains = config->attributes()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
|
||||
if (contains) {
|
||||
dialogResult = QMessageBox::warning(nullptr, tr("KeePassXC: Overwrite existing key?"),
|
||||
tr("A shared encryption key with the name \"%1\" "
|
||||
"already exists.\nDo you want to overwrite it?")
|
||||
.arg(id),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
}
|
||||
} while (contains && dialogResult == QMessageBox::No);
|
||||
|
||||
config->attributes()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key, true);
|
||||
return id;
|
||||
}
|
||||
|
||||
QString BrowserService::getKey(const QString& id)
|
||||
{
|
||||
Entry* config = getConfigEntry();
|
||||
if (!config) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
return config->attributes()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
|
||||
}
|
||||
|
||||
QJsonArray BrowserService::findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm)
|
||||
{
|
||||
QJsonArray result;
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "findMatchingEntries", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QJsonArray, result),
|
||||
Q_ARG(const QString&, id),
|
||||
Q_ARG(const QString&, url),
|
||||
Q_ARG(const QString&, submitUrl),
|
||||
Q_ARG(const QString&, realm));
|
||||
return result;
|
||||
}
|
||||
|
||||
const bool alwaysAllowAccess = BrowserSettings::alwaysAllowAccess();
|
||||
const QString host = QUrl(url).host();
|
||||
const QString submitHost = QUrl(submitUrl).host();
|
||||
|
||||
// Check entries for authorization
|
||||
QList<Entry*> pwEntriesToConfirm;
|
||||
QList<Entry*> pwEntries;
|
||||
for (Entry* entry : searchEntries(url)) {
|
||||
switch (checkAccess(entry, host, submitHost, realm)) {
|
||||
case Denied:
|
||||
continue;
|
||||
|
||||
case Unknown:
|
||||
if (alwaysAllowAccess) {
|
||||
pwEntries.append(entry);
|
||||
} else {
|
||||
pwEntriesToConfirm.append(entry);
|
||||
}
|
||||
break;
|
||||
|
||||
case Allowed:
|
||||
pwEntries.append(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm entries
|
||||
if (confirmEntries(pwEntriesToConfirm, url, host, submitHost, realm)) {
|
||||
pwEntries.append(pwEntriesToConfirm);
|
||||
}
|
||||
|
||||
if (pwEntries.isEmpty()) {
|
||||
return QJsonArray();
|
||||
}
|
||||
|
||||
// Sort results
|
||||
pwEntries = sortEntries(pwEntries, host, submitUrl);
|
||||
|
||||
// Fill the list
|
||||
for (Entry* entry : pwEntries) {
|
||||
result << prepareEntry(entry);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void BrowserService::addEntry(const QString&, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm)
|
||||
{
|
||||
Group* group = findCreateAddEntryGroup();
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = new Entry();
|
||||
entry->setUuid(Uuid::random());
|
||||
entry->setTitle(QUrl(url).host());
|
||||
entry->setUrl(url);
|
||||
entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
||||
entry->setUsername(login);
|
||||
entry->setPassword(password);
|
||||
entry->setGroup(group);
|
||||
|
||||
const QString host = QUrl(url).host();
|
||||
const QString submitHost = QUrl(submitUrl).host();
|
||||
BrowserEntryConfig config;
|
||||
config.allow(host);
|
||||
|
||||
if (!submitHost.isEmpty()) {
|
||||
config.allow(submitHost);
|
||||
}
|
||||
if (!realm.isEmpty()) {
|
||||
config.setRealm(realm);
|
||||
}
|
||||
config.save(entry);
|
||||
}
|
||||
|
||||
void BrowserService::updateEntry(const QString& id, const QString& uuid, const QString& login, const QString& password, const QString& url)
|
||||
{
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "updateEntry", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(const QString&, id),
|
||||
Q_ARG(const QString&, uuid),
|
||||
Q_ARG(const QString&, login),
|
||||
Q_ARG(const QString&, password),
|
||||
Q_ARG(const QString&, url));
|
||||
}
|
||||
|
||||
Database* db = getDatabase();
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = db->resolveEntry(Uuid::fromHex(uuid));
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString username = entry->username();
|
||||
if (username.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (username.compare(login, Qt::CaseSensitive) != 0 || entry->password().compare(password, Qt::CaseSensitive) != 0) {
|
||||
QMessageBox::StandardButton dialogResult = QMessageBox::No;
|
||||
if (!BrowserSettings::alwaysAllowUpdate()) {
|
||||
dialogResult = QMessageBox::warning(0, tr("KeePassXC: Update Entry"),
|
||||
tr("Do you want to update the information in %1 - %2?")
|
||||
.arg(QUrl(url).host()).arg(username),
|
||||
QMessageBox::Yes|QMessageBox::No);
|
||||
}
|
||||
|
||||
if (BrowserSettings::alwaysAllowUpdate() || dialogResult == QMessageBox::Yes) {
|
||||
entry->beginUpdate();
|
||||
entry->setUsername(login);
|
||||
entry->setPassword(password);
|
||||
entry->endUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QList<Entry*> BrowserService::searchEntries(Database* db, const QString& hostname)
|
||||
{
|
||||
QList<Entry*> entries;
|
||||
Group* rootGroup = db->rootGroup();
|
||||
if (!rootGroup) {
|
||||
return entries;
|
||||
}
|
||||
|
||||
for (Entry* entry : EntrySearcher().search(hostname, rootGroup, Qt::CaseInsensitive)) {
|
||||
QString title = entry->title();
|
||||
QString url = entry->url();
|
||||
|
||||
// Filter to match hostname in Title and Url fields
|
||||
if ((!title.isEmpty() && hostname.contains(title))
|
||||
|| (!url.isEmpty() && hostname.contains(url))
|
||||
|| (matchUrlScheme(title) && hostname.endsWith(QUrl(title).host()))
|
||||
|| (matchUrlScheme(url) && hostname.endsWith(QUrl(url).host())) ) {
|
||||
entries.append(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
QList<Entry*> BrowserService::searchEntries(const QString& text)
|
||||
{
|
||||
// Get the list of databases to search
|
||||
QList<Database*> databases;
|
||||
if (BrowserSettings::searchInAllDatabases()) {
|
||||
const int count = m_dbTabWidget->count();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (DatabaseWidget* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
|
||||
if (Database* db = dbWidget->database()) {
|
||||
databases << db;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (Database* db = getDatabase()) {
|
||||
databases << db;
|
||||
}
|
||||
|
||||
// Search entries matching the hostname
|
||||
QString hostname = QUrl(text).host();
|
||||
QList<Entry*> entries;
|
||||
do {
|
||||
for (Database* db : databases) {
|
||||
entries << searchEntries(db, hostname);
|
||||
}
|
||||
} while (entries.isEmpty() && removeFirstDomain(hostname));
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
void BrowserService::removeSharedEncryptionKeys()
|
||||
{
|
||||
if (!isDatabaseOpened()) {
|
||||
QMessageBox::critical(0, tr("KeePassXC: Database locked!"),
|
||||
tr("The active database is locked!\n"
|
||||
"Please unlock the selected database or choose another one which is unlocked."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = getConfigEntry();
|
||||
if (!entry) {
|
||||
QMessageBox::information(0, tr("KeePassXC: Settings not available!"),
|
||||
tr("The active database does not contain a settings entry."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList keysToRemove;
|
||||
for (const QString& key : entry->attributes()->keys()) {
|
||||
if (key.startsWith(ASSOCIATE_KEY_PREFIX)) {
|
||||
keysToRemove << key;
|
||||
}
|
||||
}
|
||||
|
||||
if (keysToRemove.isEmpty()) {
|
||||
QMessageBox::information(0, tr("KeePassXC: No keys found"),
|
||||
tr("No shared encryption keys found in KeePassXC Settings."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
entry->beginUpdate();
|
||||
for (const QString& key : keysToRemove) {
|
||||
entry->attributes()->remove(key);
|
||||
}
|
||||
entry->endUpdate();
|
||||
|
||||
const int count = keysToRemove.count();
|
||||
QMessageBox::information(0, tr("KeePassXC: Removed keys from database"),
|
||||
tr("Successfully removed %n encryption key(s) from KeePassXC settings.", "", count),
|
||||
QMessageBox::Ok);
|
||||
|
||||
}
|
||||
|
||||
void BrowserService::removeStoredPermissions()
|
||||
{
|
||||
if (!isDatabaseOpened()) {
|
||||
QMessageBox::critical(0, tr("KeePassXC: Database locked!"),
|
||||
tr("The active database is locked!\n"
|
||||
"Please unlock the selected database or choose another one which is unlocked."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
Database* db = m_dbTabWidget->currentDatabaseWidget()->database();
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Entry*> entries = db->rootGroup()->entriesRecursive();
|
||||
|
||||
QProgressDialog progress(tr("Removing stored permissions…"), tr("Abort"), 0, entries.count());
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
|
||||
uint counter = 0;
|
||||
for (Entry* entry : entries) {
|
||||
if (progress.wasCanceled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry->attributes()->contains(KEEPASSXCBROWSER_NAME)) {
|
||||
entry->beginUpdate();
|
||||
entry->attributes()->remove(KEEPASSXCBROWSER_NAME);
|
||||
entry->endUpdate();
|
||||
++counter;
|
||||
}
|
||||
progress.setValue(progress.value() + 1);
|
||||
}
|
||||
progress.reset();
|
||||
|
||||
if (counter > 0) {
|
||||
QMessageBox::information(0, tr("KeePassXC: Removed permissions"),
|
||||
tr("Successfully removed permissions from %n entry(s).", "", counter),
|
||||
QMessageBox::Ok);
|
||||
} else {
|
||||
QMessageBox::information(0, tr("KeePassXC: No entry with permissions found!"),
|
||||
tr("The active database does not contain an entry with permissions."),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& entryUrl)
|
||||
{
|
||||
QUrl url(entryUrl);
|
||||
if (url.scheme().isEmpty()) {
|
||||
url.setScheme("http");
|
||||
}
|
||||
|
||||
const QString submitUrl = url.toString(QUrl::StripTrailingSlash);
|
||||
const QString baseSubmitUrl = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
QMultiMap<int, const Entry*> priorities;
|
||||
for (const Entry* entry : pwEntries) {
|
||||
priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl), entry);
|
||||
}
|
||||
|
||||
QString field = BrowserSettings::sortByTitle() ? "Title" : "UserName";
|
||||
std::sort(pwEntries.begin(), pwEntries.end(), [&priorities, &field](const Entry* left, const Entry* right) {
|
||||
int res = priorities.key(left) - priorities.key(right);
|
||||
if (res == 0) {
|
||||
return QString::localeAwareCompare(left->attributes()->value(field), right->attributes()->value(field)) < 0;
|
||||
}
|
||||
return res < 0;
|
||||
});
|
||||
|
||||
return pwEntries;
|
||||
}
|
||||
|
||||
bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm, const QString& url, const QString& host, const QString& submitHost, const QString& realm)
|
||||
{
|
||||
if (pwEntriesToConfirm.isEmpty() || m_dialogActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_dialogActive = true;
|
||||
BrowserAccessControlDialog accessControlDialog;
|
||||
accessControlDialog.setUrl(url);
|
||||
accessControlDialog.setItems(pwEntriesToConfirm);
|
||||
|
||||
int res = accessControlDialog.exec();
|
||||
if (accessControlDialog.remember()) {
|
||||
for (Entry* entry : pwEntriesToConfirm) {
|
||||
BrowserEntryConfig config;
|
||||
config.load(entry);
|
||||
if (res == QDialog::Accepted) {
|
||||
config.allow(host);
|
||||
if (!submitHost.isEmpty() && host != submitHost)
|
||||
config.allow(submitHost);
|
||||
} else if (res == QDialog::Rejected) {
|
||||
config.deny(host);
|
||||
if (!submitHost.isEmpty() && host != submitHost) {
|
||||
config.deny(submitHost);
|
||||
}
|
||||
}
|
||||
if (!realm.isEmpty()) {
|
||||
config.setRealm(realm);
|
||||
}
|
||||
config.save(entry);
|
||||
}
|
||||
}
|
||||
|
||||
m_dialogActive = false;
|
||||
if (res == QDialog::Accepted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject BrowserService::prepareEntry(const Entry* entry)
|
||||
{
|
||||
QJsonObject res;
|
||||
res["login"] = entry->resolveMultiplePlaceholders(entry->username());
|
||||
res["password"] = entry->resolveMultiplePlaceholders(entry->password());
|
||||
res["name"] = entry->resolveMultiplePlaceholders(entry->title());
|
||||
res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuid().toHex());
|
||||
|
||||
if (BrowserSettings::supportKphFields()) {
|
||||
const EntryAttributes* attr = entry->attributes();
|
||||
QJsonArray stringFields;
|
||||
for (const QString& key : attr->keys()) {
|
||||
if (key.startsWith(QLatin1String("KPH: "))) {
|
||||
QJsonObject sField;
|
||||
sField[key] = entry->resolveMultiplePlaceholders(attr->value(key));
|
||||
stringFields << sField;
|
||||
}
|
||||
}
|
||||
res["stringFields"] = stringFields;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
BrowserService::Access BrowserService::checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm)
|
||||
{
|
||||
BrowserEntryConfig config;
|
||||
if (!config.load(entry)) {
|
||||
return Unknown;
|
||||
}
|
||||
if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) {
|
||||
return Allowed;
|
||||
}
|
||||
if ((config.isDenied(host)) || (!submitHost.isEmpty() && config.isDenied(submitHost))) {
|
||||
return Denied;
|
||||
}
|
||||
if (!realm.isEmpty() && config.realm() != realm) {
|
||||
return Denied;
|
||||
}
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
Group* BrowserService::findCreateAddEntryGroup()
|
||||
{
|
||||
Database* db = getDatabase();
|
||||
if (!db) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Group* rootGroup = db->rootGroup();
|
||||
if (!rootGroup) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QString groupName = QLatin1String(KEEPASSXCBROWSER_GROUP_NAME); //TODO: setting to decide where new keys are created
|
||||
|
||||
for (const Group* g : rootGroup->groupsRecursive(true)) {
|
||||
if (g->name() == groupName) {
|
||||
return db->resolveGroup(g->uuid());
|
||||
}
|
||||
}
|
||||
|
||||
Group* group = new Group();
|
||||
group->setUuid(Uuid::random());
|
||||
group->setName(groupName);
|
||||
group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
||||
group->setParent(rootGroup);
|
||||
return group;
|
||||
}
|
||||
|
||||
int BrowserService::sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const
|
||||
{
|
||||
QUrl url(entry->url());
|
||||
if (url.scheme().isEmpty()) {
|
||||
url.setScheme("http");
|
||||
}
|
||||
const QString entryURL = url.toString(QUrl::StripTrailingSlash);
|
||||
const QString baseEntryURL = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
if (submitUrl == entryURL) {
|
||||
return 100;
|
||||
}
|
||||
if (submitUrl.startsWith(entryURL) && entryURL != host && baseSubmitUrl != entryURL) {
|
||||
return 90;
|
||||
}
|
||||
if (submitUrl.startsWith(baseEntryURL) && entryURL != host && baseSubmitUrl != baseEntryURL) {
|
||||
return 80;
|
||||
}
|
||||
if (entryURL == host) {
|
||||
return 70;
|
||||
}
|
||||
if (entryURL == baseSubmitUrl) {
|
||||
return 60;
|
||||
}
|
||||
if (entryURL.startsWith(submitUrl)) {
|
||||
return 50;
|
||||
}
|
||||
if (entryURL.startsWith(baseSubmitUrl) && baseSubmitUrl != host) {
|
||||
return 40;
|
||||
}
|
||||
if (submitUrl.startsWith(entryURL)) {
|
||||
return 30;
|
||||
}
|
||||
if (submitUrl.startsWith(baseEntryURL)) {
|
||||
return 20;
|
||||
}
|
||||
if (entryURL.startsWith(host)) {
|
||||
return 10;
|
||||
}
|
||||
if (host.startsWith(entryURL)) {
|
||||
return 5;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool BrowserService::matchUrlScheme(const QString& url)
|
||||
{
|
||||
QUrl address(url);
|
||||
return !address.scheme().isEmpty();
|
||||
}
|
||||
|
||||
bool BrowserService::removeFirstDomain(QString& hostname)
|
||||
{
|
||||
int pos = hostname.indexOf(".");
|
||||
if (pos < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't remove the second-level domain if it's the only one
|
||||
if (hostname.count(".") > 1) {
|
||||
hostname = hostname.mid(pos + 1);
|
||||
return !hostname.isEmpty();
|
||||
}
|
||||
|
||||
// Nothing removed
|
||||
return false;
|
||||
}
|
||||
|
||||
Database* BrowserService::getDatabase()
|
||||
{
|
||||
if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
|
||||
if (Database* db = dbWidget->database()) {
|
||||
return db;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
|
||||
{
|
||||
if (dbWidget) {
|
||||
emit databaseLocked();
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget)
|
||||
{
|
||||
if (dbWidget) {
|
||||
emit databaseUnlocked();
|
||||
}
|
||||
}
|
||||
|
||||
void BrowserService::activateDatabaseChanged(DatabaseWidget* dbWidget)
|
||||
{
|
||||
if (dbWidget) {
|
||||
auto currentMode = dbWidget->currentMode();
|
||||
if (currentMode == DatabaseWidget::ViewMode || currentMode == DatabaseWidget::EditMode) {
|
||||
emit databaseUnlocked();
|
||||
} else {
|
||||
emit databaseLocked();
|
||||
}
|
||||
}
|
||||
}
|
82
src/browser/BrowserService.h
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Francois Ferrand
|
||||
* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERSERVICE_H
|
||||
#define BROWSERSERVICE_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QObject>
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "core/Entry.h"
|
||||
|
||||
enum { max_length = 16*1024 };
|
||||
|
||||
class BrowserService : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BrowserService(DatabaseTabWidget* parent);
|
||||
|
||||
bool isDatabaseOpened() const;
|
||||
bool openDatabase(bool triggerUnlock);
|
||||
QString getDatabaseRootUuid();
|
||||
QString getDatabaseRecycleBinUuid();
|
||||
Entry* getConfigEntry(bool create = false);
|
||||
QString getKey(const QString& id);
|
||||
void addEntry(const QString& id, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm);
|
||||
QList<Entry*> searchEntries(Database* db, const QString& hostname);
|
||||
QList<Entry*> searchEntries(const QString& text);
|
||||
void removeSharedEncryptionKeys();
|
||||
void removeStoredPermissions();
|
||||
|
||||
public slots:
|
||||
QJsonArray findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm);
|
||||
QString storeKey(const QString& key);
|
||||
void updateEntry(const QString& id, const QString& uuid, const QString& login, const QString& password, const QString& url);
|
||||
void databaseLocked(DatabaseWidget* dbWidget);
|
||||
void databaseUnlocked(DatabaseWidget* dbWidget);
|
||||
void activateDatabaseChanged(DatabaseWidget* dbWidget);
|
||||
void lockDatabase();
|
||||
|
||||
signals:
|
||||
void databaseLocked();
|
||||
void databaseUnlocked();
|
||||
void databaseChanged();
|
||||
|
||||
private:
|
||||
enum Access { Denied, Unknown, Allowed};
|
||||
|
||||
private:
|
||||
QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& submitUrl);
|
||||
bool confirmEntries(QList<Entry*>& pwEntriesToConfirm, const QString& url, const QString& host, const QString& submitHost, const QString& realm);
|
||||
QJsonObject prepareEntry(const Entry* entry);
|
||||
Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
|
||||
Group* findCreateAddEntryGroup();
|
||||
int sortPriority(const Entry* entry, const QString &host, const QString& submitUrl, const QString& baseSubmitUrl) const;
|
||||
bool matchUrlScheme(const QString& url);
|
||||
bool removeFirstDomain(QString& hostname);
|
||||
Database* getDatabase();
|
||||
|
||||
private:
|
||||
DatabaseTabWidget* const m_dbTabWidget;
|
||||
bool m_dialogActive;
|
||||
};
|
||||
|
||||
#endif // BROWSERSERVICE_H
|