Merge branch 'develop' into feature/autoopen

This commit is contained in:
alterratz 2017-11-28 17:29:05 +01:00 committed by GitHub
commit 468519cd46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
161 changed files with 7988 additions and 1026 deletions

View File

@ -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

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
CMakeLists.txt.*
build*/
cmake-build-*/
release*/
.idea/
*.iml
@ -7,3 +8,5 @@ release*/
\.vscode/
*.swp
.DS_Store

View File

@ -34,6 +34,7 @@ fi
APP="$1"
LOWERAPP="$(echo "$APP" | tr '[:upper:]' '[:lower:]')"
VERSION="$2"
export ARCH=x86_64
mkdir -p $APP.AppDir
wget -q https://github.com/AppImage/AppImages/raw/master/functions.sh -O ./functions.sh
@ -44,6 +45,8 @@ 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
cd $APP.AppDir
@ -53,7 +56,7 @@ rm -R ./usr/local
rmdir ./opt 2> /dev/null
# bundle Qt platform plugins and themes
QXCB_PLUGIN="$(find /usr/lib -name 'libqxcb.so' 2> /dev/null)"
QXCB_PLUGIN="$(find /usr/lib* -name 'libqxcb.so' 2> /dev/null)"
if [ "$QXCB_PLUGIN" == "" ]; then
QXCB_PLUGIN="$(find /opt/qt*/plugins -name 'libqxcb.so' 2> /dev/null)"
fi
@ -65,33 +68,34 @@ get_apprun
copy_deps
delete_blacklisted
# remove dbus and systemd libs as they are not blacklisted
find . -name libdbus-1.so.3 -exec rm {} \;
find . -name libsystemd.so.0 -exec rm {} \;
get_desktop
get_icon
cat << EOF > ./usr/bin/keepassxc_env
#!/usr/bin/env bash
#export QT_QPA_PLATFORMTHEME=gtk2
export LD_LIBRARY_PATH="..$(dirname ${QT_PLUGIN_PATH})/lib:\${LD_LIBRARY_PATH}"
export QT_PLUGIN_PATH="..${QT_PLUGIN_PATH}"
export QT_PLUGIN_PATH="..${QT_PLUGIN_PATH}:\${KPXC_QT_PLUGIN_PATH}"
# unset XDG_DATA_DIRS to make tray icon work in Ubuntu Unity
# see https://github.com/probonopd/AppImageKit/issues/351
# see https://github.com/AppImage/AppImageKit/issues/351
unset XDG_DATA_DIRS
exec keepassxc "\$@"
if [ "\${1}" == "cli" ]; then
shift
exec keepassxc-cli "\$@"
else
exec keepassxc "\$@"
fi
EOF
chmod +x ./usr/bin/keepassxc_env
sed -i 's/Exec=keepassxc/Exec=keepassxc_env/' keepassxc.desktop
get_desktopintegration $LOWERAPP
GLIBC_NEEDED=$(glibc_needed)
sed -i 's/Exec=keepassxc/Exec=keepassxc_env/' org.${LOWERAPP}.${APP}.desktop
get_desktopintegration "org.${LOWERAPP}.${APP}"
cd ..
generate_type2_appimage
GLIBC_NEEDED=$(glibc_needed)
NO_GLIBC_VERSION=true
mv ../out/*.AppImage ..
rmdir ../out > /dev/null 2>&1
generate_type2_appimage -u "gh-releases-zsync|keepassxreboot|keepassxc|latest|KeePassXC-*-${ARCH}.AppImage.zsync"
mv ../out/*.AppImage* ../
rm -rf ../out

View File

@ -1,3 +1,20 @@
2.2.2 (2017-10-22)
=========================
- Fixed entries with empty URLs being reported to KeePassHTTP clients [#1031]
- Fixed YubiKey detection and enabled CLI tool for AppImage binary [#1100]
- Added AppStream description [#1082]
- Improved TOTP compatibility and added new Base32 implementation [#1069]
- Fixed error handling when processing invalid cipher stream [#1099]
- Fixed double warning display when opening a database [#1037]
- Fixed unlocking databases with --pw-stdin [#1087]
- Added ability to override QT_PLUGIN_PATH environment variable for AppImages [#1079]
- Fixed transform seed not being regenerated when saving the database [#1068]
- Fixed only one YubiKey slot being polled [#1048]
- Corrected an issue with entry icons while merging [#1008]
- Corrected desktop and tray icons in Snap package [#1030]
- Fixed screen lock and Google fallback settings [#1029]
2.2.1 (2017-10-01)
=========================

View File

@ -36,24 +36,34 @@ 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)
option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
option(WITH_XC_HTTP "Include KeePassHTTP and Custom Icon Downloads." OFF)
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
# 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 "1")
set(KEEPASSXC_VERSION_PATCH "2")
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
# Special flag for snap builds
set(KEEPASSXC_SNAP_BUILD OFF CACHE BOOL "Set whether this is a build for snap or not")
# Distribution info
set(KEEPASSXC_DIST True)
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)
elseif(KEEPASSXC_DIST_TYPE STREQUAL "AppImage")
set(KEEPASSXC_DIST_APPIMAGE True)
elseif(KEEPASSXC_DIST_TYPE STREQUAL "Other")
unset(KEEPASSXC_DIST)
endif()
if("${CMAKE_C_COMPILER}" MATCHES "clang$" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_COMPILER_IS_CLANG 1)
@ -87,7 +97,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")
@ -101,16 +111,20 @@ 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)
@ -207,17 +221,36 @@ 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()
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 /usr/local/opt/qt5/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.
@ -231,17 +264,8 @@ 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

View File

@ -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

View File

@ -16,47 +16,48 @@
FROM ubuntu:14.04
RUN set -x \
&& apt-get update \
&& apt-get install --yes software-properties-common
ENV QT5_VERSION=59
ENV QT5_PPA_VERSION=${QT5_VERSION}2
RUN set -x \
&& add-apt-repository ppa:george-edison55/cmake-3.x
ENV QT_VERSION=qt59
&& apt-get update -y \
&& apt-get -y install software-properties-common
RUN set -x \
&& add-apt-repository --yes ppa:beineri/opt-${QT_VERSION}-trusty
&& add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty \
&& add-apt-repository ppa:phoerious/keepassxc
RUN set -x \
&& apt-get update \
&& apt-get install --yes \
&& apt-get update -y \
&& apt-get upgrade -y
# build and runtime dependencies
RUN set -x \
&& apt-get install -y \
cmake3 \
g++ \
cmake \
libgcrypt20-dev \
${QT_VERSION}base \
${QT_VERSION}tools \
${QT_VERSION}x11extras \
qt${QT5_VERSION}base \
qt${QT5_VERSION}tools \
qt${QT5_VERSION}x11extras \
zlib1g-dev \
libxi-dev \
libxtst-dev \
zlib1g-dev \
mesa-common-dev \
libyubikey-dev \
libykpers-1-dev \
xvfb \
wget \
file \
fuse \
python
libykpers-1-dev
ENV CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake
ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib
RUN set -x \
&& apt-get install --yes mesa-common-dev
&& echo /opt/qt${QT_VERSION}/lib > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf
# AppImage dependencies
RUN set -x \
&& apt-get install -y \
libfuse2 \
wget
VOLUME /keepassxc/src
VOLUME /keepassxc/out
WORKDIR /keepassxc
ENV CMAKE_PREFIX_PATH=/opt/${QT_VERSION}/lib/cmake
ENV LD_LIBRARY_PATH=/opt/${QT_VERSION}/lib
RUN set -x \
&& echo /opt/${QT_VERSION}/lib > /etc/ld.so.conf.d/${QT_VERSION}.conf

View File

@ -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 at 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
==================
@ -28,10 +30,9 @@ The following libraries are required:
Prepare the Building Environment
================================
* 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
* [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 +40,68 @@ 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_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

View File

@ -1,10 +1,20 @@
# <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
[![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)
KeePass Cross-platform Community Edition
## 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.
## 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.
## 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, macOS)
@ -18,66 +28,40 @@ KeePass Cross-platform Community Edition
- Password strength meter
- Using website favicons as entry icons
- Merging of databases
- Automatic reload when the database was changed externally
- KeePassHTTP support for use with [PassIFox](https://addons.mozilla.org/en-us/firefox/addon/passifox/) in Mozilla Firefox, [chromeIPass](https://chrome.google.com/webstore/detail/chromeipass/ompiailgknfdndiefoaoiligalphfdae) in Google Chrome or Chromium and [passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari.
- Automatic reload when the database changed on disk
- 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)
- 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](./BUILD-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!

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
docs/KeePassXC-Confirm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
docs/KeePassXC-Connect.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

47
docs/QUICKSTART.md Normal file
View 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">

View File

@ -119,9 +119,11 @@ EOF
Sign previously compiled release packages
Options:
-f, --files Files to sign (required)
-g, --gpg-key GPG key used to sign the files (default: '${GPG_KEY}')
-h, --help Show this help
-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
fi
}
@ -257,19 +259,18 @@ checkChangeLog() {
grep -qPzo "${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n=+\n" CHANGELOG
if [ $? -ne 0 ]; then
exitError "CHANGELOG does not contain any information about the '${RELEASE_NAME}' release!"
exitError "'CHANGELOG' has not been updated to the '${RELEASE_NAME}' release!"
fi
}
checkTransifexCommandExists() {
command -v tx > /dev/null
if [ 0 -ne $? ]; then
exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'"
checkAppStreamInfo() {
if [ ! -f share/linux/org.keepassxc.KeePassXC.appdata.xml ]; then
exitError "No AppStream info file found!"
fi
command -v lupdate-qt5 > /dev/null
if [ 0 -ne $? ]; then
exitError "Qt Linguist tool (lupdate-qt5) is not installed! Please install using 'apt install qttools5-dev-tools'"
grep -qPzo "<release version=\"${RELEASE_NAME}\" date=\"\d{4}-\d{2}-\d{2}\">" share/linux/org.keepassxc.KeePassXC.appdata.xml
if [ $? -ne 0 ]; then
exitError "'share/linux/org.keepassxc.KeePassXC.appdata.xml' has not been updated to the '${RELEASE_NAME}' release!"
fi
}
@ -281,7 +282,24 @@ checkSnapcraft() {
grep -qPzo "version: ${RELEASE_NAME}" snapcraft.yaml
if [ $? -ne 0 ]; then
exitError "snapcraft.yaml has not been updated to the '${RELEASE_NAME}' release!"
exitError "'snapcraft.yaml' has not been updated to the '${RELEASE_NAME}' release!"
fi
}
checkTransifexCommandExists() {
command -v tx > /dev/null
if [ 0 -ne $? ]; then
exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'"
fi
}
checkQt5LUpdateExists() {
command -v lupdate > /dev/null
if [ 0 -eq $? ] && ! $(lupdate -version | grep -q "lupdate version 5\."); then
command -v lupdate-qt5 > /dev/null
if [ 0 -ne $? ]; then
exitError "Qt Linguist tool (lupdate-qt5) is not installed! Please install using 'apt install qttools5-dev-tools'"
fi
fi
}
@ -296,6 +314,7 @@ performChecks() {
logInfo "Validating toolset and repository..."
checkTransifexCommandExists
checkQt5LUpdateExists
checkGitRepository
checkReleaseDoesNotExist
checkWorkingTreeClean
@ -309,6 +328,7 @@ performChecks() {
checkVersionInCMake
checkChangeLog
checkAppStreamInfo
checkSnapcraft
logInfo "\e[1m\e[32mAll checks passed!\e[0m"
@ -528,10 +548,10 @@ build() {
checkWorkingTreeClean
OUTPUT_DIR="$(realpath "$OUTPUT_DIR")"
logInfo "Checking out release tag '${TAG_NAME}'..."
git checkout "$TAG_NAME"
logInfo "Creating output directory..."
mkdir -p "$OUTPUT_DIR"
@ -598,7 +618,8 @@ build() {
# Building on Linux without Docker container
logInfo "Configuring build..."
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off $CMAKE_OPTIONS \
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" "$SRC_DIR"
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
-DKEEPASSXC_DIST_TYPE=AppImage "$SRC_DIR"
logInfo "Compiling sources..."
make $MAKE_OPTIONS
@ -615,7 +636,7 @@ build() {
logInfo "Launching Docker container to compile sources..."
docker run --name "$DOCKER_CONTAINER_NAME" --rm \
--cap-add SYS_ADMIN --device /dev/fuse \
--cap-add SYS_ADMIN --security-opt apparmor:unconfined --device /dev/fuse \
-e "CC=${CC}" -e "CXX=${CXX}" \
-v "$(realpath "$SRC_DIR"):/keepassxc/src:ro" \
-v "$(realpath "$OUTPUT_DIR"):/keepassxc/out:rw" \
@ -644,6 +665,8 @@ build() {
# -----------------------------------------------------------------------
sign() {
SIGN_FILES=()
SIGNTOOL="signtool"
SIGNTOOL_KEY=""
while [ $# -ge 1 ]; do
local arg="$1"
@ -657,6 +680,14 @@ sign() {
-g|--gpg-key)
GPG_KEY="$2"
shift ;;
--signtool)
SIGNTOOL="$2"
shift ;;
--signtool-key)
SIGNTOOL_KEY="$2"
shift ;;
-h|--help)
printUsage "sign"
@ -675,13 +706,30 @@ sign() {
printUsage "sign"
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
if [ ! -f "$f" ]; then
exitError "File '${f}' does not exist!"
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}'..."
logInfo "Signing file '${f}' using release key..."
gpg --output "${f}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$f"
if [ 0 -ne $? ]; then

View File

@ -30,7 +30,8 @@ if(UNIX AND NOT APPLE)
install(DIRECTORY icons/application/ DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor
FILES_MATCHING PATTERN "application-x-keepassxc.png" PATTERN "application-x-keepassxc.svgz"
PATTERN "status" EXCLUDE PATTERN "actions" EXCLUDE PATTERN "categories" EXCLUDE)
install(FILES linux/keepassxc.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES linux/org.keepassxc.KeePassXC.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES linux/org.keepassxc.KeePassXC.appdata.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(FILES linux/keepassxc.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages)
endif(UNIX AND NOT APPLE)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 958 B

Binary file not shown.

View File

@ -1,5 +1,4 @@
[General]
ShowToolbar=true
RememberLastDatabases=true
RememberLastKeyFiles=true
OpenPreviousDatabasesOnStartup=true

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>
<mime-type type="application/x-keepass2">
<comment>KeePass 2 database</comment>
<comment>KeePass 2 Database</comment>
<glob pattern="*.kdbx"/>
<icon name="application-x-keepassxc"/>
</mime-type>

View File

@ -0,0 +1,209 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2017 KeePassXC Team <team@keepassxc.org> -->
<component type="desktop-application">
<id>org.keepassxc.KeePassXC.desktop</id>
<name>KeePassXC</name>
<metadata_license>CC-BY-3.0</metadata_license>
<project_license>GPL-3.0+</project_license>
<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
personal data management. It has a light interface, is cross-platform and
published under the terms of the GNU General Public License.
</p>
</description>
<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>
<caption>Icon Selection for Entry</caption>
</screenshot>
<screenshot>
<image>https://keepassxc.org/images/screenshots/linux/screen_006.png</image>
<caption>Password Generator</caption>
</screenshot>
</screenshots>
<releases>
<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>
<li>Added AppStream description [#1082]</li>
<li>Improved TOTP compatibility and added new Base32 implementation [#1069]</li>
<li>Fixed error handling when processing invalid cipher stream [#1099]</li>
<li>Fixed double warning display when opening a database [#1037]</li>
<li>Fixed unlocking databases with --pw-stdin [#1087]</li>
<li>Added ability to override QT_PLUGIN_PATH environment variable for AppImages [#1079]</li>
<li>Fixed transform seed not being regenerated when saving the database [#1068]</li>
<li>Fixed only one YubiKey slot being polled [#1048]</li>
<li>Corrected an issue with entry icons while merging [#1008]</li>
<li>Corrected desktop and tray icons in Snap package [#1030]</li>
<li>Fixed screen lock and Google fallback settings [#1029]</li>
</ul>
</description>
</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>
<li>Corrected multiple Yubikey issues [#880]</li>
<li>Fixed single instance preventing load on occasion [#997]</li>
<li>Keep entry history when merging databases [#970]</li>
<li>Prevent data loss if passwords were mismatched [#1007]</li>
<li>Fixed crash after merge [#941]</li>
<li>Added configurable auto-type default delay [#703]</li>
<li>Unlock database dialog window comes to front [#663]</li>
<li>Translation and compiling fixes</li>
</ul>
</description>
</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>
<li>Added CSV import tool [#146, #490]</li>
<li>Added KeePassXC CLI tool [#254]</li>
<li>Added diceware password generator [#373]</li>
<li>Added support for entry references [#370, #378]</li>
<li>Added support for Twofish encryption [#167]</li>
<li>Enabled DEP and ASLR for in-memory protection [#371]</li>
<li>Enabled single instance mode [#510]</li>
<li>Enabled portable mode [#645]</li>
<li>Enabled database lock on screensaver and session lock [#545]</li>
<li>Redesigned welcome screen with common features and recent databases [#292]</li>
<li>Multiple updates to search behavior [#168, #213, #374, #471, #603, #654]</li>
<li>Added auto-type fields {CLEARFIELD}, {SPACE}, {{}, {}} [#267, #427, #480]</li>
<li>Fixed auto-type errors on Linux [#550]</li>
<li>Prompt user prior to executing a cmd:// URL [#235]</li>
<li>Entry attributes can be protected (hidden) [#220]</li>
<li>Added extended ascii to password generator [#538]</li>
<li>Added new database icon to toolbar [#289]</li>
<li>Added context menu entry to empty recycle bin in databases [#520]</li>
<li>Added "apply" button to entry and group edit windows [#624]</li>
<li>Added macOS tray icon and enabled minimize on close [#583]</li>
<li>Fixed issues with unclean shutdowns [#170, #580]</li>
<li>Changed keyboard shortcut to create new database to CTRL+SHIFT+N [#515]</li>
<li>Compare window title to entry URLs [#556]</li>
<li>Implemented inline error messages [#162]</li>
<li>Ignore group expansion and other minor changes when making database "dirty" [#464]</li>
<li>Updated license and copyright information on souce files [#632]</li>
<li>Added contributors list to about dialog [#629]</li>
</ul>
</description>
</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>
</ul>
</description>
</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>
<li>Set file meta properties in Windows executable [#330]</li>
<li>Suppress error message when auto-reloading a locked database [#345]</li>
<li>Improve usability of question dialog when database is already locked by a different instance [#346]</li>
<li>Fix compiler warnings in QHttp library [#351]</li>
<li>Use unified toolbar on Mac OS X [#361]</li>
<li>Fix an issue on X11 where the main window would be raised instead of closed on Alt+F4 [#362]</li>
</ul>
</description>
</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>
<li>Prevent Qt from degrading Wifi network performance on certain platforms [#318]</li>
<li>Visually refine user interface on OS X and other platforms [#299]</li>
<li>Remove unusable tray icon setting on OS X [#293]</li>
<li>Fix compositing glitches on Ubuntu and prevent flashing when minimizing to the tray at startup [#307]</li>
<li>Fix AppImage tray icon on Ubuntu [#277, #273]</li>
<li>Fix global menu disappearing after restoring KeePassXC from the tray on Ubuntu [#276]</li>
<li>Fix result order in entry search [#320]</li>
<li>Enable HiDPI scaling on supported platforms [#315]</li>
<li>Remove empty directories from installation target [#282]</li>
</ul>
</description>
</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>
<li>Corrected crashes in favicon download and password generator [#233, #226]</li>
<li>Increase font size of password meter [#228]</li>
<li>Fixed compatibility with Qt 5.8 [#211]</li>
<li>Use consistent button heights in password generator [#229]</li>
</ul>
</description>
</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>
<li>Support autotype on Windows and OS X [#42, #60, #63]</li>
<li>Add delay feature to autotype [#76, #77]</li>
<li>Add password strength meter [#84, #92]</li>
<li>Add option for automatically locking the database when minimizing the window [#57]</li>
<li>Add feature to download favicons and use them as entry icons [#30]</li>
<li>Automatically reload and merge database when the file changed on disk [#22, #33, #93]</li>
<li>Add tool for merging two databases [#22, #47, #143]</li>
<li>Add --pw-stdin commandline option to unlock the database by providing a password on STDIN [#54]</li>
<li>Add utility script for reading the database password from KWallet [#55]</li>
<li>Fix some KeePassHTTP settings not being remembered [#34, #65]</li>
<li>Make search box persistent [#15, #67, #157]</li>
<li>Enhance search feature by scoping the search to selected group [#16, #118]</li>
<li>Improve interaction between search field and entry list [#131, #141]</li>
<li>Add stand-alone password-generator [#18, #92]</li>
<li>Don't require password repetition when password is visible [#27, #92]</li>
<li>Add support for entry attributes in autotype sequences [#107]</li>
<li>Always focus password field when opening the database unlock widget [#116, #117]</li>
<li>Fix compilation errors on various platforms [#53, #126, #130]</li>
<li>Restructure and improve kdbx-extract utility [#160]</li>
</ul>
</description>
</release>
</releases>
</component>

View File

@ -1,13 +1,16 @@
[Desktop Entry]
Name=KeePassXC
GenericName=Community Password Manager
GenericName=Password Manager
GenericName[de]=Passwortverwaltung
GenericName[es]=Gestor de contraseñas
GenericName[fr]=Gestionnaire de mot de passe
GenericName[ru]=менеджер паролей
Comment=Community-driven port of the Windows application KeePass Password Safe
Exec=keepassxc %f
TryExec=keepassxc
Icon=keepassxc
Terminal=false
Type=Application
Categories=Qt;Utility;
Version=1.0
Categories=Utility;Security;Qt;
MimeType=application/x-keepass2;

View File

@ -67,6 +67,10 @@ Jádro systému: %3 %4</translation>
<source>Include the following information whenever you report a bug:</source>
<translation>K hlášení chyby vždy připojte následující údaje:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation>Distribuce: %1</translation>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1095,7 +1099,7 @@ Chcete ji přesto otevřít?</translation>
</message>
<message>
<source>Custom icon already exists</source>
<translation type="unfinished"/>
<translation>Tato vlastní ikona existuje</translation>
</message>
</context>
<context>
@ -1318,6 +1322,17 @@ Můžete ho importovat pomocí Databáze → Importovat databázi ve formátu Ke
Jedná se o jednosměrný převod. Databázi, vzniklou z importu, nepůjde otevřít ve staré verzi KeePassX 0.4.</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation>Nedaří se vyvolat výzva-odpověď.</translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Nedaří se spočítat hlavní klíč</translation>
</message>
</context>
<context>
<name>Main</name>
<message>
@ -1338,7 +1353,7 @@ Jedná se o jednosměrný převod. Databázi, vzniklou z importu, nepůjde otev
</message>
<message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
<translation type="unfinished"/>
<translation>Existující uzamykací soubor, zajišťující spuštění pouze jedné instance, není platný. Spouští se nová instance.</translation>
</message>
</context>
<context>

View File

@ -67,6 +67,10 @@ Kerne: %3 %4</translation>
<source>Include the following information whenever you report a bug:</source>
<translation>Inkludér følgende information når du indrapporterer en fejl:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1313,6 +1317,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
<translation type="unfinished"/>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"/>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Kan ikke beregne hovednøgle</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ Kernel: %3 %4</translation>
<source>Include the following information whenever you report a bug:</source>
<translation>Geben Sie folgende Informationen an, wenn Sie einen Bug melden:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation>Distribution: %1</translation>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1092,7 +1096,7 @@ Möchten Sie diese dennoch öffnen?</translation>
</message>
<message>
<source>Custom icon already exists</source>
<translation type="unfinished"/>
<translation>Es gibt bereits ein eigenes Symbol </translation>
</message>
</context>
<context>
@ -1315,6 +1319,17 @@ Zum Importieren gehen Sie auf Datenbank &gt; &apos;KeePass 1-Datenbank importier
Dieser Vorgang ist nur in eine Richtung möglich. Die importierte Datenbank kann später nicht mehr mit der alten KeePassX-Version 0.4 geöffnet werden.</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation>Fehler beim Ausführen des Challenge-Response-Verfahrens</translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Berechnung des Hauptschlüssels gescheitert</translation>
</message>
</context>
<context>
<name>Main</name>
<message>
@ -1335,7 +1350,7 @@ Dieser Vorgang ist nur in eine Richtung möglich. Die importierte Datenbank kann
</message>
<message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
<translation type="unfinished"/>
<translation>Vorhandene einmal-Sperrdatei ist ungültig. Starte neuen Vorgang. </translation>
</message>
</context>
<context>

View File

@ -67,6 +67,10 @@ Kernel: %3 %4</source>
<source>Include the following information whenever you report a bug:</source>
<translation>Συμπεριλάβετε τις ακόλουθες πληροφορίες όποτε αναφέρετε κάποιο σφάλμα:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1311,6 +1315,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
<translation type="unfinished"/>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"/>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Σε θέση να υπολογίσει το κύριο κλειδί</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -66,6 +66,10 @@ Kernel: %3 %4</source>
<source>Include the following information whenever you report a bug:</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1309,6 +1313,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ Núcleo: %3 %4</translation>
<source>Include the following information whenever you report a bug:</source>
<translation>Incluya la información siguiente cuando informe sobre un error:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1317,6 +1321,17 @@ Puede importarla haciendo clic en Base de datos &gt; &apos;Importar base de dato
Esta migración es en único sentido. No podrá abrir la base de datos importada con la vieja versión 0.4 de KeePassX.</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation>No se pudo hacer el desafío/respuesta:</translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>No se puede calcular la clave maestra</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -65,6 +65,10 @@ Kernel: %3 %4</source>
<source>Include the following information whenever you report a bug:</source>
<translation type="unfinished"/>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1300,6 +1304,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
<translation type="unfinished"/>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"/>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Ezin izan da gako nagusia kalkulatu</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ Kernel: %3 %4</translation>
<source>Include the following information whenever you report a bug:</source>
<translation>Sisällytä seuraavat tiedot aina kun ilmoitat ongelmasta:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1314,6 +1318,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
<translation type="unfinished"/>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"/>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Pääavaimen laskeminen ei onnistu</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ Kernel: %3 %4</translation>
<source>Include the following information whenever you report a bug:</source>
<translation>Inclure l&apos;information suivante lorsque vous signaler un bug:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1318,6 +1322,17 @@ Vous pouvez l&apos;importer en cliquant sur Base de données&gt;&apos;Importer u
Il s&apos;agit d&apos;une migration à sens unique. Vous ne pourrez pas ouvrir la base de données importée avec l&apos;ancienne version de KeePassX 0.4.</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation>Impossible de lancer une challenge-réponse.</translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Impossible de calculer la clé maître</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ Kernel: %3 %4</translation>
<source>Include the following information whenever you report a bug:</source>
<translation>Minden hibajelentésnél legyenek mellékelve ezek az információk:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation>Disztribúció: %1</translation>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1094,7 +1098,7 @@ Mindenképp megnyitja?</translation>
</message>
<message>
<source>Custom icon already exists</source>
<translation type="unfinished"/>
<translation>Az egyéni ikon már létezik</translation>
</message>
</context>
<context>
@ -1317,6 +1321,17 @@ Be lehet importálni az Adatbázis &gt; „KeePass 1 adatbázis importálása…
Ez egyirányú migráció. Nem lehet majd megnyitni az importált adatbázist a régi KeePassX 0.4 verzióval.</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation>Nem lehet kiutalni a kihívás-választ.</translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Nem lehet kiszámítani a mesterkulcsot</translation>
</message>
</context>
<context>
<name>Main</name>
<message>
@ -1337,7 +1352,7 @@ Ez egyirányú migráció. Nem lehet majd megnyitni az importált adatbázist a
</message>
<message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
<translation type="unfinished"/>
<translation>A meglévő egypéldányos zárolási fájl érvénytelen. Új példány indítása.</translation>
</message>
</context>
<context>

View File

@ -67,6 +67,10 @@ Kernel: %3 %4</translation>
<source>Include the following information whenever you report a bug:</source>
<translation>Sertakan informasi berikut setiap Anda melaporkan bug:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1317,6 +1321,17 @@ Anda bisa mengimpornya dengan mengklik Basis Data &gt; &apos;Impor basis data Ke
Ini adalah migrasi satu arah. Anda tidak akan bisa membuka basis data yang diimpor dengan versi lama KeePassX 0.4.</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"/>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Tidak bisa mengkalkulasi kunci utama</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

File diff suppressed because it is too large Load Diff

View File

@ -67,6 +67,10 @@ CPU アーキテクチャ: %2
<source>Include the following information whenever you report a bug:</source>
<translation>:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1094,7 +1098,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Custom icon already exists</source>
<translation type="unfinished"/>
<translation></translation>
</message>
</context>
<context>
@ -1317,6 +1321,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
KeePassX 0.4 </translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation></translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation></translation>
</message>
</context>
<context>
<name>Main</name>
<message>
@ -1337,7 +1352,7 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
</message>
<message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
<translation type="unfinished"/>
<translation></translation>
</message>
</context>
<context>

View File

@ -64,6 +64,10 @@ Kernel: %3 %4</source>
<source>Include the following information whenever you report a bug:</source>
<translation type="unfinished"/>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1307,6 +1311,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
<translation type="unfinished"/>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"/>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Басты парольді есептеу мүмкін емес</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ CPU 아키텍처: %2
<source>Include the following information whenever you report a bug:</source>
<translation> :</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1315,6 +1319,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
. KeePassX 0.4 .</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation> .</translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation> </translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ Branduolys: %3 %4</translation>
<source>Include the following information whenever you report a bug:</source>
<translation>Pranešdami apie klaidą, visuomet pateikite ir š informaciją:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation>Platinimas: %1</translation>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1094,7 +1098,7 @@ Ar vis tiek norite ją atverti?</translation>
</message>
<message>
<source>Custom icon already exists</source>
<translation type="unfinished"/>
<translation>Tinkinta piktograma jau yra</translation>
</message>
</context>
<context>
@ -1317,6 +1321,17 @@ Jūs galite ją importuoti, nuspausdami Duomenų bazė &gt; &quot;Importuoti Kee
Tai yra vienakryptis perkėlimas. Jūs negalėsite atverti importuotos duomenų bazės, naudodami senąją KeePassX 0.4 versija.</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation>Nepavyko išduoti iššūkio atsakymo.</translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Nepavyko apskaičiuoti pagrindinio rakto</translation>
</message>
</context>
<context>
<name>Main</name>
<message>
@ -1337,7 +1352,7 @@ Tai yra vienakryptis perkėlimas. Jūs negalėsite atverti importuotos duomenų
</message>
<message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
<translation type="unfinished"/>
<translation>Esamas vieno egzemplioriaus užrakto failas yra neteisingas. Paleidžiamas naujas egzempliorius.</translation>
</message>
</context>
<context>

View File

@ -64,6 +64,10 @@ Kernel: %3 %4</source>
<source>Include the following information whenever you report a bug:</source>
<translation type="unfinished"/>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1310,6 +1314,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
<translation type="unfinished"/>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"/>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Niet mogelijk om hoofdsleutel te berekenen</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ Jądro: %3 %4</translation>
<source>Include the following information whenever you report a bug:</source>
<translation>Uwzględnij następujące informacje, gdy zgłaszasz błąd:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation>Dystrybucja: %1</translation>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1095,7 +1099,7 @@ Czy chcesz ją otworzyć mimo to?</translation>
</message>
<message>
<source>Custom icon already exists</source>
<translation type="unfinished"/>
<translation>Ikona niestandardowa już istnieje</translation>
</message>
</context>
<context>
@ -1318,6 +1322,17 @@ Możesz zaimportować ją przez wybranie Baza danych &gt; &apos;Importuj bazę d
Jest to migracja w jedną stronę. Nie będzie można otworzyć importowanej bazy danych za pomocą starej wersji KeePassX 0.4.</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation>Nie można wywołać wyzwania-odpowiedzi.</translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Nie mogę wyliczyć głównego klucza</translation>
</message>
</context>
<context>
<name>Main</name>
<message>
@ -1338,7 +1353,7 @@ Jest to migracja w jedną stronę. Nie będzie można otworzyć importowanej baz
</message>
<message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
<translation type="unfinished"/>
<translation>Istniejący plik blokady pojedynczego wystąpienia jest nieprawidłowy. Uruchamianie nowego wystąpienia.</translation>
</message>
</context>
<context>

View File

@ -67,6 +67,10 @@ Kernel: %3 %4</translation>
<source>Include the following information whenever you report a bug:</source>
<translation>Inclua as informações abaixo quando reportar um erro:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1314,6 +1318,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
<translation type="unfinished"/>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"/>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Não foi possível calcular a chave mestre</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ Kernel: %3 %4</translation>
<source>Include the following information whenever you report a bug:</source>
<translation>Inclua as seguintes informações sempre que reportar um erro:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation>Distribuição: %1</translation>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1095,7 +1099,7 @@ Ainda assim deseja abrir a base de dados?</translation>
</message>
<message>
<source>Custom icon already exists</source>
<translation type="unfinished"/>
<translation> existe um ícone personalizado</translation>
</message>
</context>
<context>
@ -1318,6 +1322,17 @@ Pode importá-lo clicando em Base de dados - &gt; &apos;Importar base de dados d
Esta é uma migração unidirecional. Não será possível abrir a base de dados importada com a versão 0.4 do KeePassX.</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation>Incapaz de emitir a pergunta de segurança.</translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Impossível de calcular a chave-mestre</translation>
</message>
</context>
<context>
<name>Main</name>
<message>
@ -1338,7 +1353,7 @@ Esta é uma migração unidirecional. Não será possível abrir a base de dados
</message>
<message>
<source>Existing single-instance lock file is invalid. Launching new instance.</source>
<translation type="unfinished"/>
<translation>O ficheiro de bloqueio da instância única é inválido. A iniciar nova instância.</translation>
</message>
</context>
<context>

View File

@ -67,6 +67,10 @@ Kernel: %3 %4</source>
<source>Include the following information whenever you report a bug:</source>
<translation>Включите следующую информацию, когда сообщаете об ошибке:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1318,6 +1322,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
Это одностороннее перемещение. Вы не сможете открыть импортированную базу данных на старой версии KeePassX 0,4.</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation>Не удалось выполнить запрос ответа.</translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Невозможно вычислить мастер-пароль</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -64,6 +64,10 @@ Kernel: %3 %4</source>
<source>Include the following information whenever you report a bug:</source>
<translation type="unfinished"/>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1305,6 +1309,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
<translation type="unfinished"/>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"/>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Izračun glavnega ključa ni uspel</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -24,7 +24,8 @@
<message>
<source>Version %1
</source>
<translation type="unfinished"/>
<translation>Version %1
</translation>
</message>
<message>
<source>Revision: %1</source>
@ -64,6 +65,10 @@ Kernel: %3 %4</source>
<source>Include the following information whenever you report a bug:</source>
<translation type="unfinished"/>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -196,7 +201,7 @@ Please select whether you want to allow access.</source>
</message>
<message>
<source>Empty password</source>
<translation type="unfinished"/>
<translation>Tomt lösenord</translation>
</message>
<message>
<source>Changing master key failed: no YubiKey inserted.</source>
@ -254,7 +259,7 @@ Please select whether you want to allow access.</source>
</message>
<message>
<source>Comments start with</source>
<translation type="unfinished"/>
<translation>Kommentarer inleds med</translation>
</message>
<message>
<source>First record has field names</source>
@ -282,7 +287,7 @@ Please select whether you want to allow access.</source>
</message>
<message>
<source>Empty fieldname </source>
<translation type="unfinished"/>
<translation>Tomt fältnamn </translation>
</message>
<message>
<source>column </source>
@ -337,7 +342,7 @@ Please select whether you want to allow access.</source>
</message>
<message>
<source> rows, </source>
<translation type="unfinished"/>
<translation> rader, </translation>
</message>
<message>
<source> columns</source>
@ -622,7 +627,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Open CSV file</source>
<translation type="unfinished"/>
<translation>Öppna CSV fil</translation>
</message>
</context>
<context>
@ -677,7 +682,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Searching...</source>
<translation type="unfinished"/>
<translation>Söker...</translation>
</message>
<message>
<source>No current database.</source>
@ -689,11 +694,11 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Search Results (%1)</source>
<translation type="unfinished"/>
<translation>Sökresultat (%1)</translation>
</message>
<message>
<source>No Results</source>
<translation type="unfinished"/>
<translation>Inget resultat</translation>
</message>
<message>
<source>Execute command?</source>
@ -818,7 +823,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Confirm Remove</source>
<translation type="unfinished"/>
<translation>Bekräfta borttagning</translation>
</message>
<message>
<source>Are you sure you want to remove this attribute?</source>
@ -865,7 +870,7 @@ Do you want to open it anyway?</source>
</message>
<message>
<source>Protect</source>
<translation type="unfinished"/>
<translation>Skydda</translation>
</message>
<message>
<source>Reveal</source>
@ -1307,6 +1312,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
<translation type="unfinished"/>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"/>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Kunde inte räkna nu master-nyckeln</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ MİB mimarisi: %2
<source>Include the following information whenever you report a bug:</source>
<translation>Bir hata bildirirken şu bilgileri ekleyin:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1313,6 +1317,17 @@ Veri tabanı &gt; &apos;KeePass1 veri tabanı içe aktar...&apos;a tıklayarak i
Bu tek yönlü bir yer değiştirmedir. İçe aktarılan veri tabanını eski KeePassX 0.4 sürümüyle açamayacaksınız.</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"/>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Ana anahtar hesaplanamıyor</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ Kernel: %3 %4</source>
<source>Include the following information whenever you report a bug:</source>
<translation>Коли Ви повідомляєте про ваду, завжди долучайте таку інформацію:</translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1318,6 +1322,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
Перетворення можливе лише в одному напрямку. Ви не зможете відкрити імпортоване сховище старою версією KeePassX 0.4.</translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation>Неможливо видати виклик-відповідь.</translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation>Неможливо вирахувати головний ключ</translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ CPU 架构:%2
<source>Include the following information whenever you report a bug:</source>
<translation> bug </translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1316,6 +1320,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
KeePassX 0.4 </translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation></translation>
</message>
<message>
<source>Unable to calculate master key</source>
<translation></translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -67,6 +67,10 @@ Kernel: %3 %4</source>
<source>Include the following information whenever you report a bug:</source>
<translation> Bug </translation>
</message>
<message>
<source>Distribution: %1</source>
<translation type="unfinished"/>
</message>
</context>
<context>
<name>AccessControlDialog</name>
@ -1317,6 +1321,17 @@ This is a one-way migration. You won&apos;t be able to open the imported databas
KeePassX 0.4 </translation>
</message>
</context>
<context>
<name>KeePass2Writer</name>
<message>
<source>Unable to issue challenge-response.</source>
<translation type="unfinished"/>
</message>
<message>
<source>Unable to calculate master key</source>
<translation></translation>
</message>
</context>
<context>
<name>Main</name>
<message>

View File

@ -1,18 +1,18 @@
name: keepassxc
version: 2.2.1
version: 2.2.2
grade: stable
summary: community driven port of the windows application “Keepass Password Safe”
summary: Community-driven port of the Windows application “KeePass Password Safe”
description: |
KeePassXC is an application for people with extremely high demands on secure
personal data management. It has a light interface, is cross platform and
is published under the terms of the GNU General Public License.
personal data management. It has a light interface, is cross-platform and
published under the terms of the GNU General Public License.
confinement: strict
apps:
keepassxc:
command: desktop-launch keepassxc
plugs: [unity7, x11, opengl, gsettings, home, network, network-bind, removable-media, raw-usb]
desktop: usr/share/applications/keepassxc.desktop
desktop: usr/share/applications/org.keepassxc.KeePassXC.desktop
cli:
command: keepassxc-cli
plugs: [gsettings, home, removable-media, raw-usb]
@ -24,7 +24,7 @@ parts:
configflags:
- -DCMAKE_BUILD_TYPE=Release
- -DCMAKE_INSTALL_PREFIX=/usr
- -DKEEPASSXC_SNAP_BUILD=ON
- -DKEEPASSXC_DIST_TYPE=Snap
- -DWITH_TESTS=OFF
- -DWITH_XC_AUTOTYPE=ON
- -DWITH_XC_HTTP=ON
@ -42,7 +42,7 @@ parts:
- libyubikey-dev
- libykpers-1-dev
install: |
sed -i 's|Icon=keepassxc|Icon=${SNAP}/usr/share/icons/hicolor/256x256/apps/keepassxc.png|g' $SNAPCRAFT_PART_INSTALL/usr/share/applications/keepassxc.desktop
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
after: [desktop-qt5]
# Redefine desktop-qt5 stage packages to work with Ubuntu 17.04

View File

@ -68,7 +68,6 @@ set(keepassx_SOURCES
core/Uuid.cpp
core/Base32.h
core/Base32.cpp
core/Optional.h
cli/Utils.cpp
cli/Utils.h
crypto/Crypto.cpp
@ -99,6 +98,7 @@ set(keepassx_SOURCES
gui/DatabaseTabWidget.cpp
gui/DatabaseWidget.cpp
gui/DatabaseWidgetStateSync.cpp
gui/DetailsWidget.cpp
gui/DialogyWidget.cpp
gui/DragTabBar.cpp
gui/EditWidget.cpp
@ -155,6 +155,8 @@ 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")
@ -177,6 +179,7 @@ set(keepassx_SOURCES_MAINEXE
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(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
add_subdirectory(http)
if(WITH_XC_HTTP)
@ -186,6 +189,11 @@ 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
@ -222,6 +230,7 @@ set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUIL
target_link_libraries(keepassx_core
${keepasshttp_LIB}
${autotype_LIB}
${sshagent_LIB}
${YUBIKEY_LIBRARIES}
${ZXCVBN_LIBRARIES}
Qt5::Core
@ -234,6 +243,9 @@ target_link_libraries(keepassx_core
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)
@ -264,13 +276,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}")
@ -284,16 +290,17 @@ if(APPLE AND WITH_APP_BUNDLE)
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")

View File

@ -256,6 +256,13 @@ void AutoType::resetInAutoType()
m_inAutoType = false;
}
void AutoType::raiseWindow()
{
#if defined(Q_OS_MAC)
m_plugin->raiseOwnWindow();
#endif
}
void AutoType::unloadPlugin()
{
if (m_executor) {
@ -563,7 +570,8 @@ QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitl
bool match = false;
const QList<AutoTypeAssociations::Association> assocList = entry->autoTypeAssociations()->getAll();
for (const AutoTypeAssociations::Association& assoc : assocList) {
if (windowMatches(windowTitle, assoc.window)) {
const QString window = entry->resolveMultiplePlaceholders(assoc.window);
if (windowMatches(windowTitle, window)) {
if (!assoc.sequence.isEmpty()) {
sequence = assoc.sequence;
}

View File

@ -51,6 +51,7 @@ public:
public slots:
void performGlobalAutoType(const QList<Database*>& dbList);
void raiseWindow();
signals:
void globalShortcutTriggered();

View File

@ -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()

View File

@ -15,8 +15,12 @@
#cmakedefine WITH_XC_HTTP
#cmakedefine WITH_XC_AUTOTYPE
#cmakedefine WITH_XC_YUBIKEY
#cmakedefine WITH_XC_SSHAGENT
#cmakedefine KEEPASSXC_SNAP_BUILD
#cmakedefine KEEPASSXC_DIST
#cmakedefine KEEPASSXC_DIST_TYPE "@KEEPASSXC_DIST_TYPE@"
#cmakedefine KEEPASSXC_DIST_SNAP
#cmakedefine KEEPASSXC_DIST_APPIMAGE
#cmakedefine HAVE_PR_SET_DUMPABLE 1
#cmakedefine HAVE_RLIMIT_CORE 1

View File

@ -15,7 +15,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Conforms to RFC 4648. For details, see: https://tools.ietf.org/html/rfc4648
/* Conforms to RFC 4648. For details, see: https://tools.ietf.org/html/rfc4648
* Use the functions Base32::addPadding/1, Base32::removePadding/1 or
* Base32::sanitizeInput/1 to fix input or output for a particular
* applications (e.g. to use with Google Authenticator).
*/
#include "Base32.h"
@ -36,18 +40,21 @@ constexpr quint8 ASCII_a = static_cast<quint8>('a');
constexpr quint8 ASCII_z = static_cast<quint8>('z');
constexpr quint8 ASCII_EQ = static_cast<quint8>('=');
Optional<QByteArray> Base32::decode(const QByteArray& encodedData)
QVariant Base32::decode(const QByteArray& encodedData)
{
if (encodedData.size() <= 0)
return Optional<QByteArray>("");
if (encodedData.size() <= 0) {
return QVariant::fromValue(QByteArray(""));
}
if (encodedData.size() % 8 != 0)
return Optional<QByteArray>();
if (encodedData.size() % 8 != 0) {
return QVariant();
}
int nPads = 0;
for (int i = -1; i > -7; --i) {
if ('=' == encodedData[encodedData.size()+i])
if ('=' == encodedData[encodedData.size() + i]) {
++nPads;
}
}
int specialOffset;
@ -75,10 +82,9 @@ Optional<QByteArray> Base32::decode(const QByteArray& encodedData)
specialOffset = 0;
}
Q_ASSERT(encodedData.size() > 0);
const int nQuantums = encodedData.size() / 8;
const int nBytes = (nQuantums - 1) * 5 + nSpecialBytes;
const int nQuanta = encodedData.size() / 8;
const int nBytes = nSpecialBytes > 0 ? (nQuanta - 1) * 5 + nSpecialBytes : nQuanta * 5;
QByteArray data(nBytes, Qt::Uninitialized);
@ -89,19 +95,20 @@ Optional<QByteArray> Base32::decode(const QByteArray& encodedData)
quint64 quantum = 0;
int nQuantumBytes = 5;
for (int n = 0; n < 8; n++) {
quint8 ch = static_cast<quint8>(encodedData[i++]);
for (int n = 0; n < 8; ++n) {
auto ch = static_cast<quint8>(encodedData[i++]);
if ((ASCII_A <= ch && ch <= ASCII_Z) || (ASCII_a <= ch && ch <= ASCII_z)) {
ch -= ASCII_A;
if (ch >= ALPH_POS_2)
if (ch >= ALPH_POS_2) {
ch -= ASCII_a - ASCII_A;
}
} else {
if (ch >= ASCII_2 && ch <= ASCII_7) {
if (ASCII_2 <= ch && ch <= ASCII_7) {
ch -= ASCII_2;
ch += ALPH_POS_2;
} else {
if (ASCII_EQ == ch) {
if(i == encodedData.size()) {
if (i == encodedData.size()) {
// finished with special quantum
quantum >>= specialOffset;
nQuantumBytes = nSpecialBytes;
@ -109,7 +116,7 @@ Optional<QByteArray> Base32::decode(const QByteArray& encodedData)
continue;
} else {
// illegal character
return Optional<QByteArray>();
return QVariant();
}
}
}
@ -120,26 +127,30 @@ Optional<QByteArray> Base32::decode(const QByteArray& encodedData)
const int offset = (nQuantumBytes - 1) * 8;
quint64 mask = quint64(0xFF) << offset;
for (int n = offset; n >= 0; n -= 8) {
char c = static_cast<char>((quantum & mask) >> n);
data[o++] = c;
for (int n = offset; n >= 0 && o < nBytes; n -= 8) {
data[o++] = static_cast<char>((quantum & mask) >> n);
mask >>= 8;
}
}
return Optional<QByteArray>(data);
Q_ASSERT(encodedData.size() == i);
Q_ASSERT(nBytes == o);
return QVariant::fromValue(data);
}
QByteArray Base32::encode(const QByteArray& data)
{
if (data.size() < 1)
if (data.size() < 1) {
return QByteArray();
}
const int nBits = data.size() * 8;
const int rBits = nBits % 40; // in {0, 8, 16, 24, 32}
const int nQuantums = nBits / 40 + (rBits > 0 ? 1 : 0);
QByteArray encodedData(nQuantums * 8, Qt::Uninitialized);
const int nQuanta = nBits / 40 + (rBits > 0 ? 1 : 0);
const int nBytes = nQuanta * 8;
QByteArray encodedData(nBytes, Qt::Uninitialized);
int i = 0;
int o = 0;
int n;
@ -157,6 +168,7 @@ QByteArray Base32::encode(const QByteArray& data)
int index;
for (n = 35; n >= 0; n -= 5) {
index = (quantum & mask) >> n;
Q_ASSERT(0 <= index && index <= 31);
encodedData[o++] = alphabet[index];
mask >>= 5;
}
@ -164,13 +176,14 @@ QByteArray Base32::encode(const QByteArray& data)
// < 40-bits of input at final input group
if (i < data.size()) {
Q_ASSERT(rBits > 0);
Q_ASSERT(8 <= rBits && rBits <= 32);
quantum = 0;
for (n = rBits - 8; n >= 0; n -= 8)
for (n = rBits - 8; n >= 0; n -= 8) {
quantum |= static_cast<quint64>(data[i++]) << n;
}
switch (rBits) {
case 8: // expand to 10 bits
case 8: // expand to 10 bits
quantum <<= 2;
mask = MASK_10BIT;
n = 5;
@ -186,7 +199,7 @@ QByteArray Base32::encode(const QByteArray& data)
n = 20;
break;
default: // expand to 35 bits
Q_ASSERT(rBits == 32);
Q_ASSERT(32 == rBits);
quantum <<= 3;
mask = MASK_35BIT;
n = 30;
@ -194,17 +207,89 @@ QByteArray Base32::encode(const QByteArray& data)
while (n >= 0) {
int index = (quantum & mask) >> n;
Q_ASSERT(0 <= index && index <= 31);
encodedData[o++] = alphabet[index];
mask >>= 5;
n -= 5;
}
// add pad characters
while (o < encodedData.size())
while (o < encodedData.size()) {
encodedData[o++] = '=';
}
}
Q_ASSERT(encodedData.size() == o);
Q_ASSERT(data.size() == i);
Q_ASSERT(nBytes == o);
return encodedData;
}
QByteArray Base32::addPadding(const QByteArray& encodedData)
{
if (encodedData.size() <= 0 || encodedData.size() % 8 == 0) {
return encodedData;
}
const int rBytes = encodedData.size() % 8;
// rBytes must be a member of {2, 4, 5, 7}
if (1 == rBytes || 3 == rBytes || 6 == rBytes) {
return encodedData;
}
QByteArray newEncodedData(encodedData);
for (int nPads = 8 - rBytes; nPads > 0; --nPads) {
newEncodedData.append('=');
}
return newEncodedData;
}
QByteArray Base32::removePadding(const QByteArray& encodedData)
{
if (encodedData.size() <= 0 || encodedData.size() % 8 != 0) {
return encodedData; // return same bad input
}
int nPads = 0;
for (int i = -1; i > -7; --i) {
if ('=' == encodedData[encodedData.size() + i]) {
++nPads;
}
}
QByteArray newEncodedData(encodedData);
newEncodedData.remove(encodedData.size() - nPads, nPads);
newEncodedData.resize(encodedData.size() - nPads);
return newEncodedData;
}
QByteArray Base32::sanitizeInput(const QByteArray& encodedData)
{
if (encodedData.size() <= 0) {
return encodedData;
}
QByteArray newEncodedData(encodedData.size(), Qt::Uninitialized);
int i = 0;
for (auto ch : encodedData) {
switch (ch) {
case '0':
newEncodedData[i++] = 'O';
break;
case '1':
newEncodedData[i++] = 'L';
break;
case '8':
newEncodedData[i++] = 'B';
break;
default:
if (('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z') || ('2' <= ch && ch <= '7')) {
newEncodedData[i++] = ch;
}
}
}
newEncodedData.resize(i);
return addPadding(newEncodedData);
}

View File

@ -15,22 +15,28 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Conforms to RFC 4648. For details, see: https://tools.ietf.org/html/rfc4648
/* Conforms to RFC 4648. For details, see: https://tools.ietf.org/html/rfc4648
* Use the functions Base32::addPadding/1, Base32::removePadding/1 or
* Base32::sanitizeInput/1 to fix input or output for a particular
* applications (e.g. to use with Google Authenticator).
*/
#ifndef BASE32_H
#define BASE32_H
#include "Optional.h"
#include <QtCore/qglobal.h>
#include <QByteArray>
#include <QVariant>
#include <QtCore/qglobal.h>
class Base32
{
public:
Base32() =default;
Q_REQUIRED_RESULT static Optional<QByteArray> decode(const QByteArray&);
Base32() = default;
Q_REQUIRED_RESULT static QVariant decode(const QByteArray&);
Q_REQUIRED_RESULT static QByteArray encode(const QByteArray&);
Q_REQUIRED_RESULT static QByteArray addPadding(const QByteArray&);
Q_REQUIRED_RESULT static QByteArray removePadding(const QByteArray&);
Q_REQUIRED_RESULT static QByteArray sanitizeInput(const QByteArray&);
};
#endif //BASE32_H
#endif // BASE32_H

View File

@ -114,7 +114,6 @@ void Config::init(const QString& fileName)
m_defaults.insert("AutoSaveAfterEveryChange", false);
m_defaults.insert("AutoReloadOnChange", true);
m_defaults.insert("AutoSaveOnExit", false);
m_defaults.insert("ShowToolbar", true);
m_defaults.insert("SearchLimitGroup", false);
m_defaults.insert("MinimizeOnCopy", false);
m_defaults.insert("UseGroupIconOnEntryCreation", false);
@ -131,6 +130,7 @@ void Config::init(const QString& fileName)
m_defaults.insert("security/lockdatabasescreenlock", true);
m_defaults.insert("security/passwordsrepeat", false);
m_defaults.insert("security/passwordscleartext", false);
m_defaults.insert("security/hidepassworddetails", true);
m_defaults.insert("security/autotypeask", true);
m_defaults.insert("security/IconDownloadFallbackToGoogle", false);
m_defaults.insert("GUI/Language", "system");

View File

@ -260,6 +260,25 @@ bool Database::hasKey() const
return m_data.hasKey;
}
bool Database::transformKeyWithSeed(const QByteArray& transformSeed)
{
Q_ASSERT(hasKey());
bool ok;
QString errorString;
QByteArray transformedMasterKey =
m_data.key.transform(transformSeed, transformRounds(), &ok, &errorString);
if (!ok) {
return false;
}
m_data.transformSeed = transformSeed;
m_data.transformedMasterKey = transformedMasterKey;
return true;
}
bool Database::verifyKey(const CompositeKey& key) const
{
Q_ASSERT(hasKey());

View File

@ -106,6 +106,7 @@ public:
*/
bool setKey(const CompositeKey& key);
bool hasKey() const;
bool transformKeyWithSeed(const QByteArray& transformSeed);
bool verifyKey(const CompositeKey& key) const;
void recycleEntry(Entry* entry);
void recycleGroup(Group* group);

View File

@ -25,7 +25,11 @@
#include "core/Metadata.h"
#include "totp/totp.h"
#include <QRegularExpression>
const int Entry::DefaultIconNumber = 0;
const int Entry::ResolveMaximumDepth = 10;
Entry::Entry()
: m_attributes(new EntryAttributes(this))
@ -38,8 +42,8 @@ Entry::Entry()
m_data.iconNumber = DefaultIconNumber;
m_data.autoTypeEnabled = true;
m_data.autoTypeObfuscation = 0;
m_data.totpStep = QTotp::defaultStep;
m_data.totpDigits = QTotp::defaultDigits;
m_data.totpStep = Totp::defaultStep;
m_data.totpDigits = Totp::defaultDigits;
connect(m_attributes, SIGNAL(modified()), this, SIGNAL(modified()));
connect(m_attributes, SIGNAL(defaultKeyModified()), SLOT(emitDataChanged()));
@ -241,7 +245,15 @@ QString Entry::url() const
QString Entry::webUrl() const
{
return resolveUrl(m_attributes->value(EntryAttributes::URLKey));
QString url = resolveMultiplePlaceholders(m_attributes->value(EntryAttributes::URLKey));
return resolveUrl(url);
}
QString Entry::displayUrl() const
{
QString url = maskPasswordPlaceholders(m_attributes->value(EntryAttributes::URLKey));
url = resolveMultiplePlaceholders(url);
return resolveUrl(url);
}
QString Entry::username() const
@ -305,7 +317,7 @@ QString Entry::totp() const
if (hasTotp()) {
QString seed = totpSeed();
quint64 time = QDateTime::currentDateTime().toTime_t();
QString output = QTotp::generateTotp(seed.toLatin1(), time, m_data.totpDigits, m_data.totpStep);
QString output = Totp::generateTotp(seed.toLatin1(), time, m_data.totpDigits, m_data.totpStep);
return QString(output);
} else {
@ -316,18 +328,30 @@ QString Entry::totp() const
void Entry::setTotp(const QString& seed, quint8& step, quint8& digits)
{
if (step == 0) {
step = QTotp::defaultStep;
step = Totp::defaultStep;
}
if (digits == 0) {
digits = QTotp::defaultDigits;
digits = Totp::defaultDigits;
}
QString data;
const Totp::Encoder & enc = Totp::encoders.value(digits, Totp::defaultEncoder);
if (m_attributes->hasKey("otp")) {
m_attributes->set("otp", QString("key=%1&step=%2&size=%3").arg(seed).arg(step).arg(digits), true);
data = QString("key=%1&step=%2&size=%3").arg(seed).arg(step).arg(enc.digits == 0 ? digits : enc.digits);
if (!enc.name.isEmpty()) {
data.append("&enocder=").append(enc.name);
}
m_attributes->set("otp", data, true);
} else {
m_attributes->set("TOTP Seed", seed, true);
m_attributes->set("TOTP Settings", QString("%1;%2").arg(step).arg(digits));
if (!enc.shortName.isEmpty()) {
data = QString("%1;%2").arg(step).arg(enc.shortName);
} else {
data = QString("%1;%2").arg(step).arg(digits);
}
m_attributes->set("TOTP Settings", data);
}
}
@ -341,19 +365,24 @@ QString Entry::totpSeed() const
secret = m_attributes->value("TOTP Seed");
}
m_data.totpDigits = QTotp::defaultDigits;
m_data.totpStep = QTotp::defaultStep;
m_data.totpDigits = Totp::defaultDigits;
m_data.totpStep = Totp::defaultStep;
if (m_attributes->hasKey("TOTP Settings")) {
QRegExp rx("(\\d+);(\\d)", Qt::CaseInsensitive, QRegExp::RegExp);
int pos = rx.indexIn(m_attributes->value("TOTP Settings"));
if (pos > -1) {
m_data.totpStep = rx.cap(1).toUInt();
m_data.totpDigits = rx.cap(2).toUInt();
// this regex must be kept in sync with the set of allowed short names Totp::shortNameToEncoder
QRegularExpression rx(QString("(\\d+);((?:\\d+)|S)"));
QRegularExpressionMatch m = rx.match(m_attributes->value("TOTP Settings"));
if (m.hasMatch()) {
m_data.totpStep = m.captured(1).toUInt();
if (Totp::shortNameToEncoder.contains(m.captured(2))) {
m_data.totpDigits = Totp::shortNameToEncoder[m.captured(2)];
} else {
m_data.totpDigits = m.captured(2).toUInt();
}
}
}
return QTotp::parseOtpString(secret, m_data.totpDigits, m_data.totpStep);
return Totp::parseOtpString(secret, m_data.totpDigits, m_data.totpStep);
}
quint8 Entry::totpStep() const
@ -670,6 +699,108 @@ void Entry::updateModifiedSinceBegin()
m_modifiedSinceBegin = true;
}
QString Entry::resolveMultiplePlaceholdersRecursive(const QString &str, int maxDepth) const
{
if (maxDepth <= 0) {
qWarning("Maximum depth of replacement has been reached. Entry uuid: %s", qPrintable(uuid().toHex()));
return str;
}
QString result = str;
QRegExp placeholderRegEx("(\\{[^\\}]+\\})", Qt::CaseInsensitive, QRegExp::RegExp2);
placeholderRegEx.setMinimal(true);
int pos = 0;
while ((pos = placeholderRegEx.indexIn(str, pos)) != -1) {
const QString found = placeholderRegEx.cap(1);
result.replace(found, resolvePlaceholderRecursive(found, maxDepth - 1));
pos += placeholderRegEx.matchedLength();
}
if (result != str) {
result = resolveMultiplePlaceholdersRecursive(result, maxDepth - 1);
}
return result;
}
QString Entry::resolvePlaceholderRecursive(const QString &placeholder, int maxDepth) const
{
const PlaceholderType typeOfPlaceholder = placeholderType(placeholder);
switch (typeOfPlaceholder) {
case PlaceholderType::NotPlaceholder:
return placeholder;
case PlaceholderType::Unknown:
qWarning("Can't resolve placeholder %s for entry with uuid %s", qPrintable(placeholder),
qPrintable(uuid().toHex()));
return placeholder;
case PlaceholderType::Title:
return title();
case PlaceholderType::UserName:
return username();
case PlaceholderType::Password:
return password();
case PlaceholderType::Notes:
return notes();
case PlaceholderType::Totp:
return totp();
case PlaceholderType::Url:
return url();
case PlaceholderType::UrlWithoutScheme:
case PlaceholderType::UrlScheme:
case PlaceholderType::UrlHost:
case PlaceholderType::UrlPort:
case PlaceholderType::UrlPath:
case PlaceholderType::UrlQuery:
case PlaceholderType::UrlFragment:
case PlaceholderType::UrlUserInfo:
case PlaceholderType::UrlUserName:
case PlaceholderType::UrlPassword: {
const QString strUrl = resolveMultiplePlaceholdersRecursive(url(), maxDepth - 1);
return resolveUrlPlaceholder(strUrl, typeOfPlaceholder);
}
case PlaceholderType::CustomAttribute: {
const QString key = placeholder.mid(3, placeholder.length() - 4); // {S:attr} => mid(3, len - 4)
return attributes()->hasKey(key) ? attributes()->value(key) : QString();
}
case PlaceholderType::Reference: {
// resolving references in format: {REF:<WantedField>@I:<uuid of referenced entry>}
// using format from http://keepass.info/help/base/fieldrefs.html at the time of writing,
// but supporting lookups of standard fields and references by UUID only
QString result;
QRegExp* referenceRegExp = m_attributes->referenceRegExp();
if (referenceRegExp->indexIn(placeholder) != -1) {
constexpr int wantedFieldIndex = 1;
constexpr int referencedUuidIndex = 2;
const Uuid referencedUuid(QByteArray::fromHex(referenceRegExp->cap(referencedUuidIndex).toLatin1()));
const Entry* refEntry = m_group->database()->resolveEntry(referencedUuid);
if (refEntry) {
const QString wantedField = referenceRegExp->cap(wantedFieldIndex).toLower();
if (wantedField == "t") {
result = refEntry->title();
} else if (wantedField == "u") {
result = refEntry->username();
} else if (wantedField == "p") {
result = refEntry->password();
} else if (wantedField == "a") {
result = refEntry->url();
} else if (wantedField == "n") {
result = refEntry->notes();
}
// Referencing fields of other entries only works with standard fields, not with custom user strings.
// If you want to reference a custom user string, you need to place a redirection in a standard field
// of the entry with the custom string, using {S:<Name>}, and reference the standard field.
result = refEntry->resolveMultiplePlaceholdersRecursive(result, maxDepth - 1);
}
}
return result;
}
}
return placeholder;
}
Group* Entry::group()
{
return m_group;
@ -736,67 +867,82 @@ QString Entry::maskPasswordPlaceholders(const QString &str) const
QString Entry::resolveMultiplePlaceholders(const QString& str) const
{
QString result = str;
QRegExp tmplRegEx("(\\{.*\\})", Qt::CaseInsensitive, QRegExp::RegExp2);
tmplRegEx.setMinimal(true);
QStringList tmplList;
int pos = 0;
while ((pos = tmplRegEx.indexIn(str, pos)) != -1) {
QString found = tmplRegEx.cap(1);
result.replace(found,resolvePlaceholder(found));
pos += tmplRegEx.matchedLength();
}
return result;
return resolveMultiplePlaceholdersRecursive(str, ResolveMaximumDepth);
}
QString Entry::resolvePlaceholder(const QString& str) const
QString Entry::resolvePlaceholder(const QString& placeholder) const
{
QString result = str;
return resolvePlaceholderRecursive(placeholder, ResolveMaximumDepth);
}
const QList<QString> keyList = attributes()->keys();
for (const QString& key : keyList) {
Qt::CaseSensitivity cs = Qt::CaseInsensitive;
QString k = key;
QString Entry::resolveUrlPlaceholder(const QString &str, Entry::PlaceholderType placeholderType) const
{
if (str.isEmpty())
return QString();
if (!EntryAttributes::isDefaultAttribute(key)) {
cs = Qt::CaseSensitive;
k.prepend("{S:");
} else {
k.prepend("{");
}
k.append("}");
if (result.compare(k,cs)==0) {
result.replace(result,attributes()->value(key));
break;
}
const QUrl qurl(str);
switch (placeholderType) {
case PlaceholderType::UrlWithoutScheme:
return qurl.toString(QUrl::RemoveScheme | QUrl::FullyDecoded);
case PlaceholderType::UrlScheme:
return qurl.scheme();
case PlaceholderType::UrlHost:
return qurl.host();
case PlaceholderType::UrlPort:
return QString::number(qurl.port());
case PlaceholderType::UrlPath:
return qurl.path();
case PlaceholderType::UrlQuery:
return qurl.query();
case PlaceholderType::UrlFragment:
return qurl.fragment();
case PlaceholderType::UrlUserInfo:
return qurl.userInfo();
case PlaceholderType::UrlUserName:
return qurl.userName();
case PlaceholderType::UrlPassword:
return qurl.password();
default: {
Q_ASSERT_X(false, "Entry::resolveUrlPlaceholder", "Bad url placeholder type");
break;
}
}
// resolving references in format: {REF:<WantedField>@I:<uuid of referenced entry>}
// using format from http://keepass.info/help/base/fieldrefs.html at the time of writing,
// but supporting lookups of standard fields and references by UUID only
return QString();
}
QRegExp* tmpRegExp = m_attributes->referenceRegExp();
if (tmpRegExp->indexIn(result) != -1) {
// cap(0) contains the whole reference
// cap(1) contains which field is wanted
// cap(2) contains the uuid of the referenced entry
Entry* tmpRefEntry = m_group->database()->resolveEntry(Uuid(QByteArray::fromHex(tmpRegExp->cap(2).toLatin1())));
if (tmpRefEntry) {
// entry found, get the relevant field
QString tmpRefField = tmpRegExp->cap(1).toLower();
if (tmpRefField == "t") result.replace(tmpRegExp->cap(0), tmpRefEntry->title(), Qt::CaseInsensitive);
else if (tmpRefField == "u") result.replace(tmpRegExp->cap(0), tmpRefEntry->username(), Qt::CaseInsensitive);
else if (tmpRefField == "p") result.replace(tmpRegExp->cap(0), tmpRefEntry->password(), Qt::CaseInsensitive);
else if (tmpRefField == "a") result.replace(tmpRegExp->cap(0), tmpRefEntry->url(), Qt::CaseInsensitive);
else if (tmpRefField == "n") result.replace(tmpRegExp->cap(0), tmpRefEntry->notes(), Qt::CaseInsensitive);
}
Entry::PlaceholderType Entry::placeholderType(const QString &placeholder) const
{
if (!placeholder.startsWith(QLatin1Char('{')) || !placeholder.endsWith(QLatin1Char('}'))) {
return PlaceholderType::NotPlaceholder;
} else if (placeholder.startsWith(QLatin1Literal("{S:"))) {
return PlaceholderType::CustomAttribute;
} else if (placeholder.startsWith(QLatin1Literal("{REF:"))) {
return PlaceholderType::Reference;
}
return result;
static const QMap<QString, PlaceholderType> placeholders {
{ QStringLiteral("{TITLE}"), PlaceholderType::Title },
{ QStringLiteral("{USERNAME}"), PlaceholderType::UserName },
{ QStringLiteral("{PASSWORD}"), PlaceholderType::Password },
{ QStringLiteral("{NOTES}"), PlaceholderType::Notes },
{ QStringLiteral("{TOTP}"), PlaceholderType::Totp },
{ QStringLiteral("{URL}"), PlaceholderType::Url },
{ QStringLiteral("{URL:RMVSCM}"), PlaceholderType::UrlWithoutScheme },
{ QStringLiteral("{URL:WITHOUTSCHEME}"), PlaceholderType::UrlWithoutScheme },
{ QStringLiteral("{URL:SCM}"), PlaceholderType::UrlScheme },
{ QStringLiteral("{URL:SCHEME}"), PlaceholderType::UrlScheme },
{ QStringLiteral("{URL:HOST}"), PlaceholderType::UrlHost },
{ QStringLiteral("{URL:PORT}"), PlaceholderType::UrlPort },
{ QStringLiteral("{URL:PATH}"), PlaceholderType::UrlPath },
{ QStringLiteral("{URL:QUERY}"), PlaceholderType::UrlQuery },
{ QStringLiteral("{URL:FRAGMENT}"), PlaceholderType::UrlFragment },
{ QStringLiteral("{URL:USERINFO}"), PlaceholderType::UrlUserInfo },
{ QStringLiteral("{URL:USERNAME}"), PlaceholderType::UrlUserName },
{ QStringLiteral("{URL:PASSWORD}"), PlaceholderType::UrlPassword }
};
return placeholders.value(placeholder.toUpper(), PlaceholderType::Unknown);
}
QString Entry::resolveUrl(const QString& url) const

View File

@ -79,6 +79,7 @@ public:
QString title() const;
QString url() const;
QString webUrl() const;
QString displayUrl() const;
QString username() const;
QString password() const;
QString notes() const;
@ -96,6 +97,7 @@ public:
const EntryAttachments* attachments() const;
static const int DefaultIconNumber;
static const int ResolveMaximumDepth;
void setUuid(const Uuid& uuid);
void setIcon(int iconNumber);
@ -134,6 +136,29 @@ public:
};
Q_DECLARE_FLAGS(CloneFlags, CloneFlag)
enum class PlaceholderType {
NotPlaceholder,
Unknown,
Title,
UserName,
Password,
Notes,
Totp,
Url,
UrlWithoutScheme,
UrlScheme,
UrlHost,
UrlPort,
UrlPath,
UrlQuery,
UrlFragment,
UrlUserInfo,
UrlUserName,
UrlPassword,
Reference,
CustomAttribute
};
/**
* Creates a duplicate of this entry except that the returned entry isn't
* part of any group.
@ -145,6 +170,8 @@ public:
QString maskPasswordPlaceholders(const QString& str) const;
QString resolveMultiplePlaceholders(const QString& str) const;
QString resolvePlaceholder(const QString& str) const;
QString resolveUrlPlaceholder(const QString &str, PlaceholderType placeholderType) const;
PlaceholderType placeholderType(const QString& placeholder) const;
QString resolveUrl(const QString& url) const;
/**
@ -174,6 +201,9 @@ private slots:
void updateModifiedSinceBegin();
private:
QString resolveMultiplePlaceholdersRecursive(const QString& str, int maxDepth) const;
QString resolvePlaceholderRecursive(const QString& placeholder, int maxDepth) const;
const Database* database() const;
template <class T> bool set(T& property, const T& value);

View File

@ -17,6 +17,8 @@
#include "EntryAttachments.h"
#include <QStringList>
EntryAttachments::EntryAttachments(QObject* parent)
: QObject(parent)
{
@ -71,7 +73,8 @@ void EntryAttachments::set(const QString& key, const QByteArray& value)
void EntryAttachments::remove(const QString& key)
{
if (!m_attachments.contains(key)) {
Q_ASSERT(false);
Q_ASSERT_X(false, "EntryAttachments::remove",
qPrintable(QString("Can't find attachment for key %1").arg(key)));
return;
}
@ -83,6 +86,31 @@ void EntryAttachments::remove(const QString& key)
emit modified();
}
void EntryAttachments::remove(const QStringList &keys)
{
if (keys.isEmpty()) {
return;
}
bool isModified = false;
for (const QString &key: keys) {
if (!m_attachments.contains(key)) {
Q_ASSERT_X(false, "EntryAttachments::remove",
qPrintable(QString("Can't find attachment for key %1").arg(key)));
continue;
}
isModified = true;
emit aboutToBeRemoved(key);
m_attachments.remove(key);
emit removed(key);
}
if (isModified) {
emit modified();
}
}
void EntryAttachments::clear()
{
if (m_attachments.isEmpty()) {

View File

@ -21,6 +21,8 @@
#include <QMap>
#include <QObject>
class QStringList;
class EntryAttachments : public QObject
{
Q_OBJECT
@ -33,6 +35,7 @@ public:
QByteArray value(const QString& key) const;
void set(const QString& key, const QByteArray& value);
void remove(const QString& key);
void remove(const QStringList& keys);
void clear();
void copyDataFrom(const EntryAttachments* other);
bool operator==(const EntryAttachments& other) const;

View File

@ -91,7 +91,7 @@ QString FilePath::pluginPath(const QString& name)
QIcon FilePath::applicationIcon()
{
#ifdef KEEPASSXC_SNAP_BUILD
#ifdef KEEPASSXC_DIST_SNAP
return icon("apps", "keepassxc", false);
#else
return icon("apps", "keepassxc");
@ -100,7 +100,7 @@ QIcon FilePath::applicationIcon()
QIcon FilePath::trayIconLocked()
{
#ifdef KEEPASSXC_SNAP_BUILD
#ifdef KEEPASSXC_DIST_SNAP
return icon("apps", "keepassxc-locked", false);
#else
return icon("apps", "keepassxc-locked");
@ -109,7 +109,7 @@ QIcon FilePath::trayIconLocked()
QIcon FilePath::trayIconUnlocked()
{
#ifdef KEEPASSXC_SNAP_BUILD
#ifdef KEEPASSXC_DIST_SNAP
return icon("apps", "keepassxc-unlocked", false);
#else
return icon("apps", "keepassxc-unlocked");

View File

@ -26,6 +26,11 @@
const int Group::DefaultIconNumber = 48;
const int Group::RecycleBinIconNumber = 43;
Group::CloneFlags Group::DefaultCloneFlags = static_cast<Group::CloneFlags>(
Group::CloneNewUuid | Group::CloneResetTimeInfo | Group::CloneIncludeEntries);
Entry::CloneFlags Group::DefaultEntryCloneFlags = static_cast<Entry::CloneFlags>(
Entry::CloneNewUuid | Entry::CloneResetTimeInfo);
Group::Group()
: m_updateTimeinfo(true)
{
@ -77,8 +82,7 @@ template <class P, class V> inline bool Group::set(P& property, const V& value)
updateTimeinfo();
emit modified();
return true;
}
else {
} else {
return false;
}
}
@ -435,6 +439,23 @@ void Group::setParent(Database* db)
QObject::setParent(db);
}
QStringList Group::hierarchy()
{
QStringList hierarchy;
Group* group = this;
Group* parent = m_parent;
hierarchy.prepend(group->name());
while (parent) {
group = group->parentGroup();
parent = group->parentGroup();
hierarchy.prepend(group->name());
}
return hierarchy;
}
Database* Group::database()
{
return m_db;
@ -665,38 +686,59 @@ void Group::merge(const Group* other)
Entry* existingEntry = rootGroup->findEntryByUuid(entry->uuid());
// This entry does not exist at all. Create it.
if (!existingEntry) {
// This entry does not exist at all. Create it.
qDebug("New entry %s detected. Creating it.", qPrintable(entry->title()));
entry->clone(Entry::CloneIncludeHistory)->setGroup(this);
// Entry is already present in the database. Update it.
} else {
// Entry is already present in the database. Update it.
bool locationChanged = existingEntry->timeInfo().locationChanged() < entry->timeInfo().locationChanged();
if (locationChanged && existingEntry->group() != this) {
existingEntry->setGroup(this);
qDebug("Location changed for entry %s. Updating it", qPrintable(existingEntry->title()));
}
resolveConflict(existingEntry, entry);
resolveEntryConflict(existingEntry, entry);
}
}
// merge groups recursively
const QList<Group*> dbChildren = other->children();
for (Group* group : dbChildren) {
// groups are searched by name instead of uuid
if (findChildByName(group->name())) {
findChildByName(group->name())->merge(group);
} else {
Group* existingGroup = rootGroup->findChildByUuid(group->uuid());
if (!existingGroup) {
qDebug("New group %s detected. Creating it.", qPrintable(group->name()));
Group* newGroup = group->clone(Entry::CloneNoFlags, true);
Group* newGroup = group->clone(Entry::CloneNoFlags, Group::CloneNoFlags);
newGroup->setParent(this);
newGroup->merge(group);
} else {
bool locationChanged = existingGroup->timeInfo().locationChanged() < group->timeInfo().locationChanged();
if (locationChanged && existingGroup->parent() != this) {
existingGroup->setParent(this);
qDebug("Location changed for group %s. Updating it", qPrintable(existingGroup->name()));
}
resolveGroupConflict(existingGroup, group);
existingGroup->merge(group);
}
}
emit modified();
}
Group* Group::findChildByUuid(const Uuid& uuid)
{
Q_ASSERT(!uuid.isNull());
for (Group* group : groupsRecursive(true)) {
if (group->uuid() == uuid) {
return group;
}
}
return nullptr;
}
Group* Group::findChildByName(const QString& name)
{
for (Group* group : asConst(m_children)) {
@ -708,16 +750,21 @@ Group* Group::findChildByName(const QString& name)
return nullptr;
}
Group* Group::clone(Entry::CloneFlags entryFlags, bool shallow) const
Group* Group::clone(Entry::CloneFlags entryFlags, Group::CloneFlags groupFlags) const
{
Group* clonedGroup = new Group();
clonedGroup->setUpdateTimeinfo(false);
clonedGroup->setUuid(Uuid::random());
if (groupFlags & Group::CloneNewUuid) {
clonedGroup->setUuid(Uuid::random());
} else {
clonedGroup->setUuid(this->uuid());
}
clonedGroup->m_data = m_data;
if (!shallow) {
if (groupFlags & Group::CloneIncludeEntries) {
const QList<Entry*> entryList = entries();
for (Entry* entry : entryList) {
Entry* clonedEntry = entry->clone(entryFlags);
@ -726,18 +773,20 @@ Group* Group::clone(Entry::CloneFlags entryFlags, bool shallow) const
const QList<Group*> childrenGroups = children();
for (Group* groupChild : childrenGroups) {
Group* clonedGroupChild = groupChild->clone(entryFlags);
Group* clonedGroupChild = groupChild->clone(entryFlags, groupFlags);
clonedGroupChild->setParent(clonedGroup);
}
}
clonedGroup->setUpdateTimeinfo(true);
if (groupFlags & Group::CloneResetTimeInfo) {
QDateTime now = QDateTime::currentDateTimeUtc();
clonedGroup->m_data.timeInfo.setCreationTime(now);
clonedGroup->m_data.timeInfo.setLastModificationTime(now);
clonedGroup->m_data.timeInfo.setLastAccessTime(now);
clonedGroup->m_data.timeInfo.setLocationChanged(now);
QDateTime now = QDateTime::currentDateTimeUtc();
clonedGroup->m_data.timeInfo.setCreationTime(now);
clonedGroup->m_data.timeInfo.setLastModificationTime(now);
clonedGroup->m_data.timeInfo.setLastAccessTime(now);
clonedGroup->m_data.timeInfo.setLocationChanged(now);
}
return clonedGroup;
}
@ -891,7 +940,7 @@ bool Group::resolveAutoTypeEnabled() const
}
}
void Group::resolveConflict(Entry* existingEntry, Entry* otherEntry)
void Group::resolveEntryConflict(Entry* existingEntry, Entry* otherEntry)
{
const QDateTime timeExisting = existingEntry->timeInfo().lastModificationTime();
const QDateTime timeOther = otherEntry->timeInfo().lastModificationTime();
@ -929,6 +978,26 @@ void Group::resolveConflict(Entry* existingEntry, Entry* otherEntry)
}
}
void Group::resolveGroupConflict(Group* existingGroup, Group* otherGroup)
{
const QDateTime timeExisting = existingGroup->timeInfo().lastModificationTime();
const QDateTime timeOther = otherGroup->timeInfo().lastModificationTime();
// only if the other group is newer, update the existing one.
if (timeExisting < timeOther) {
qDebug("Updating group %s.", qPrintable(existingGroup->name()));
existingGroup->setName(otherGroup->name());
existingGroup->setNotes(otherGroup->notes());
if (otherGroup->iconNumber() == 0) {
existingGroup->setIcon(otherGroup->iconUuid());
} else {
existingGroup->setIcon(otherGroup->iconNumber());
}
existingGroup->setExpiryTime(otherGroup->timeInfo().expiryTime());
}
}
QStringList Group::locate(QString locateTerm, QString currentPath)
{
Q_ASSERT(!locateTerm.isNull());

View File

@ -37,6 +37,14 @@ public:
enum TriState { Inherit, Enable, Disable };
enum MergeMode { ModeInherit, KeepBoth, KeepNewer, KeepExisting };
enum CloneFlag {
CloneNoFlags = 0,
CloneNewUuid = 1, // generate a random uuid for the clone
CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time
CloneIncludeEntries = 4, // clone the group entries
};
Q_DECLARE_FLAGS(CloneFlags, CloneFlag)
struct GroupData
{
QString name;
@ -78,8 +86,11 @@ public:
static const int DefaultIconNumber;
static const int RecycleBinIconNumber;
static CloneFlags DefaultCloneFlags;
static Entry::CloneFlags DefaultEntryCloneFlags;
Group* findChildByName(const QString& name);
Group* findChildByUuid(const Uuid& uuid);
Entry* findEntry(QString entryId);
Entry* findEntryByUuid(const Uuid& uuid);
Entry* findEntryByPath(QString entryPath, QString basePath = QString(""));
@ -106,6 +117,7 @@ public:
Group* parentGroup();
const Group* parentGroup() const;
void setParent(Group* parent, int index = -1);
QStringList hierarchy();
Database* database();
const Database* database() const;
@ -118,14 +130,13 @@ public:
QList<Group*> groupsRecursive(bool includeSelf);
QSet<Uuid> customIconsRecursive() const;
/**
* Creates a duplicate of this group including all child entries and groups (if not shallow).
* The exceptions are that the returned group doesn't have a parent group
* and all TimeInfo attributes are set to the current time.
* Creates a duplicate of this group.
* Note that you need to copy the custom icons manually when inserting the
* new group into another database.
*/
Group* clone(Entry::CloneFlags entryFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo,
bool shallow = false) const;
Group* clone(Entry::CloneFlags entryFlags = DefaultEntryCloneFlags,
CloneFlags groupFlags = DefaultCloneFlags) const;
void copyDataFrom(const Group* other);
void merge(const Group* other);
QString print(bool recursive = false, int depth = 0);
@ -159,7 +170,8 @@ private:
void removeEntry(Entry* entry);
void setParent(Database* db);
void markOlderEntry(Entry* entry);
void resolveConflict(Entry* existingEntry, Entry* otherEntry);
void resolveEntryConflict(Entry* existingEntry, Entry* otherEntry);
void resolveGroupConflict(Group* existingGroup, Group* otherGroup);
void recSetDatabase(Database* db);
void cleanupParent();
@ -182,4 +194,6 @@ private:
friend void Entry::setGroup(Group* group);
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Group::CloneFlags)
#endif // KEEPASSX_GROUP_H

View File

@ -0,0 +1,94 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MacPasteboard.h"
QString MacPasteboard::convertorName() { return QLatin1String("MacPasteboard"); }
QString MacPasteboard::flavorFor(const QString& mimetype) {
if (mimetype == QLatin1String("text/plain")) {
return QLatin1String("public.utf8-plain-text");
} else if (mimetype == QLatin1String("application/x-nspasteboard-concealed-type")) {
return QLatin1String("org.nspasteboard.ConcealedType");
}
int i = mimetype.indexOf(QLatin1String("charset="));
if (i >= 0) {
QString cs(mimetype.mid(i + 8).toLower());
i = cs.indexOf(QLatin1Char(';'));
if (i >= 0) {
cs = cs.left(i);
}
if (cs == QLatin1String("system")) {
return QLatin1String("public.utf8-plain-text");
} else if (cs == QLatin1String("iso-10646-ucs-2") ||
cs == QLatin1String("utf16")) {
return QLatin1String("public.utf16-plain-text");
}
}
return QString();
}
QString MacPasteboard::mimeFor(QString flavor) {
if (flavor == QLatin1String("public.utf8-plain-text"))
return QLatin1String("text/plain");
if (flavor == QLatin1String("org.nspasteboard.ConcealedType"))
return QLatin1String("application/x-nspasteboard-concealed-type");
if (flavor == QLatin1String("public.utf16-plain-text"))
return QLatin1String("text/plain;charset=utf16");
return QString();
}
bool MacPasteboard::canConvert(const QString& mimetype, QString flavor) {
Q_UNUSED(mimetype);
Q_UNUSED(flavor);
return true;
}
QVariant MacPasteboard::convertToMime(const QString& mimetype, QList<QByteArray> data, QString flavor) {
if (data.count() > 1)
qWarning("QMime::convertToMime: Cannot handle multiple member data");
const QByteArray& firstData = data.first();
QVariant ret;
if (flavor == QLatin1String("public.utf8-plain-text")) {
ret = QString::fromUtf8(firstData);
} else if (flavor == QLatin1String("org.nspasteboard.ConcealedType")) {
ret = QString::fromUtf8(firstData);
} else if (flavor == QLatin1String("public.utf16-plain-text")) {
ret = QTextCodec::codecForName("UTF-16")->toUnicode(firstData);
} else {
qWarning("QMime::convertToMime: unhandled mimetype: %s",
qPrintable(mimetype));
}
return ret;
}
QList<QByteArray> MacPasteboard::convertFromMime(const QString&, QVariant data, QString flavor) {
QList<QByteArray> ret;
QString string = data.toString();
if (flavor == QLatin1String("public.utf8-plain-text"))
ret.append(string.toUtf8());
else if (flavor == QLatin1String("org.nspasteboard.ConcealedType"))
ret.append(string.toUtf8());
else if (flavor == QLatin1String("public.utf16-plain-text"))
ret.append(QTextCodec::codecForName("UTF-16")->fromUnicode(string));
return ret;
}

37
src/core/MacPasteboard.h Normal file
View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_MACPASTEBOARD_H
#define KEEPASSXC_MACPASTEBOARD_H
#include <QMacPasteboardMime>
#include <QTextCodec>
class MacPasteboard : public QMacPasteboardMime
{
public:
explicit MacPasteboard() : QMacPasteboardMime(MIME_ALL) {}
QString convertorName() override;
bool canConvert(const QString &mime, QString flav) override;
QString mimeFor(QString flav) override;
QString flavorFor(const QString &mime) override;
QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav) override;
QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav) override;
};
#endif // KEEPASSXC_MACPASTEBOARD_H

View File

@ -1,93 +0,0 @@
/*
* 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 OPTIONAL_H
#define OPTIONAL_H
/*
* This utility class is for providing basic support for an option type.
* It can be replaced by std::optional (C++17) or
* std::experimental::optional (C++11) when they become fully supported
* by all the compilers.
*/
template <typename T>
class Optional
{
public:
// None
Optional() :
m_hasValue(false),
m_value()
{ };
// Some T
Optional(const T& value) :
m_hasValue(true),
m_value(value)
{ };
// Copy
Optional(const Optional& other) :
m_hasValue(other.m_hasValue),
m_value(other.m_value)
{ };
const Optional& operator=(const Optional& other)
{
m_hasValue = other.m_hasValue;
m_value = other.m_value;
return *this;
}
bool operator==(const Optional& other) const
{
if(m_hasValue)
return other.m_hasValue && m_value == other.m_value;
else
return !other.m_hasValue;
}
bool operator!=(const Optional& other) const
{
return !(*this == other);
}
bool hasValue() const
{
return m_hasValue;
}
T valueOr(const T& other) const
{
return m_hasValue ? m_value : other;
}
Optional static makeOptional(const T& value)
{
return Optional(value);
}
private:
bool m_hasValue;
T m_value;
};
#endif // OPTIONAL_H

View File

@ -20,6 +20,7 @@
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusReply>
#include <QProcessEnvironment>
ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent):
ScreenLockListenerPrivate(parent)
@ -34,7 +35,7 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent):
"ActiveChanged", // signal name
this, //receiver
SLOT(freedesktopScreenSaver(bool)));
sessionBus.connect(
"org.gnome.SessionManager", // service
"/org/gnome/SessionManager/Presence", // path
@ -51,6 +52,15 @@ ScreenLockListenerDBus::ScreenLockListenerDBus(QWidget *parent):
this, //receiver
SLOT(logindPrepareForSleep(bool)));
QString sessionId = QProcessEnvironment::systemEnvironment().value("XDG_SESSION_ID");
systemBus.connect(
"", // service
QString("/org/freedesktop/login1/session/") + sessionId, // path
"org.freedesktop.login1.Session", // interface
"Lock", // signal name
this, //receiver
SLOT(unityLocked()));
sessionBus.connect(
"com.canonical.Unity", // service
"/com/canonical/Unity/Session", // path
@ -84,4 +94,4 @@ void ScreenLockListenerDBus::freedesktopScreenSaver(bool status)
if (status) {
emit screenLocked();
}
}
}

View File

@ -74,6 +74,11 @@ bool SymmetricCipher::reset()
return m_backend->reset();
}
int SymmetricCipher::keySize() const
{
return m_backend->keySize();
}
int SymmetricCipher::blockSize() const
{
return m_backend->blockSize();

View File

@ -38,6 +38,7 @@ public:
enum Mode
{
Cbc,
Ctr,
Ecb,
Stream
};
@ -69,6 +70,7 @@ public:
}
bool reset();
int keySize() const;
int blockSize() const;
QString errorString() const;

View File

@ -33,6 +33,7 @@ public:
Q_REQUIRED_RESULT virtual bool processInPlace(QByteArray& data, quint64 rounds) = 0;
virtual bool reset() = 0;
virtual int keySize() const = 0;
virtual int blockSize() const = 0;
virtual QString errorString() const = 0;

View File

@ -26,7 +26,6 @@ SymmetricCipherGcrypt::SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, Sy
, m_algo(gcryptAlgo(algo))
, m_mode(gcryptMode(mode))
, m_direction(direction)
, m_blockSize(-1)
{
}
@ -62,6 +61,9 @@ int SymmetricCipherGcrypt::gcryptMode(SymmetricCipher::Mode mode)
case SymmetricCipher::Cbc:
return GCRY_CIPHER_MODE_CBC;
case SymmetricCipher::Ctr:
return GCRY_CIPHER_MODE_CTR;
case SymmetricCipher::Stream:
return GCRY_CIPHER_MODE_STREAM;
@ -92,14 +94,6 @@ bool SymmetricCipherGcrypt::init()
return false;
}
size_t blockSizeT;
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, nullptr, &blockSizeT);
if (error != 0) {
setErrorString(error);
return false;
}
m_blockSize = blockSizeT;
return true;
}
@ -119,7 +113,13 @@ bool SymmetricCipherGcrypt::setKey(const QByteArray& key)
bool SymmetricCipherGcrypt::setIv(const QByteArray& iv)
{
m_iv = iv;
gcry_error_t error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
gcry_error_t error;
if (m_mode == GCRY_CIPHER_MODE_CTR) {
error = gcry_cipher_setctr(m_ctx, m_iv.constData(), m_iv.size());
} else {
error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
}
if (error != 0) {
setErrorString(error);
@ -148,9 +148,10 @@ QByteArray SymmetricCipherGcrypt::process(const QByteArray& data, bool* ok)
if (error != 0) {
setErrorString(error);
*ok = false;
} else {
*ok = true;
}
*ok = true;
return result;
}
@ -227,9 +228,28 @@ bool SymmetricCipherGcrypt::reset()
return true;
}
int SymmetricCipherGcrypt::keySize() const
{
gcry_error_t error;
size_t keySizeT;
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_KEYLEN, nullptr, &keySizeT);
if (error != 0)
return -1;
return keySizeT;
}
int SymmetricCipherGcrypt::blockSize() const
{
return m_blockSize;
gcry_error_t error;
size_t blockSizeT;
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, nullptr, &blockSizeT);
if (error != 0)
return -1;
return blockSizeT;
}
QString SymmetricCipherGcrypt::errorString() const

View File

@ -39,6 +39,7 @@ public:
Q_REQUIRED_RESULT bool processInPlace(QByteArray& data, quint64 rounds);
bool reset();
int keySize() const;
int blockSize() const;
QString errorString() const;
@ -54,7 +55,6 @@ private:
const SymmetricCipher::Direction m_direction;
QByteArray m_key;
QByteArray m_iv;
int m_blockSize;
QString m_errorString;
};

View File

@ -45,6 +45,7 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
m_error = false;
m_errorStr.clear();
QByteArray transformSeed = randomGen()->randomArray(32);
QByteArray masterSeed = randomGen()->randomArray(32);
QByteArray encryptionIV = randomGen()->randomArray(16);
QByteArray protectedStreamKey = randomGen()->randomArray(32);
@ -52,7 +53,12 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
QByteArray endOfHeader = "\r\n\r\n";
if (db->challengeMasterSeed(masterSeed) == false) {
raiseError("Unable to issue challenge-response.");
raiseError(tr("Unable to issue challenge-response."));
return;
}
if (!db->transformKeyWithSeed(transformSeed)) {
raiseError(tr("Unable to calculate master key"));
return;
}

View File

@ -18,6 +18,8 @@
#ifndef KEEPASSX_KEEPASS2WRITER_H
#define KEEPASSX_KEEPASS2WRITER_H
#include <QCoreApplication>
#include "format/KeePass2.h"
#include "keys/CompositeKey.h"
@ -26,6 +28,8 @@ class QIODevice;
class KeePass2Writer
{
Q_DECLARE_TR_FUNCTIONS(KeePass2Writer)
public:
KeePass2Writer();
void writeDatabase(QIODevice* device, Database* db);

View File

@ -55,13 +55,17 @@ AboutDialog::AboutDialog(QWidget* parent)
QString debugInfo = "KeePassXC - ";
debugInfo.append(tr("Version %1\n").arg(KEEPASSX_VERSION));
if (!commitHash.isEmpty()) {
debugInfo.append(tr("Revision: %1").arg(commitHash).append("\n\n"));
debugInfo.append(tr("Revision: %1").arg(commitHash.left(7)).append("\n"));
}
debugInfo.append(QString("%1\n- Qt %2\n- %3\n\n")
.arg(tr("Libraries:"),
QString::fromLocal8Bit(qVersion()),
Crypto::backendVersion()));
#ifdef KEEPASSXC_DIST
debugInfo.append(tr("Distribution: %1").arg(KEEPASSXC_DIST_TYPE).append("\n"));
#endif
debugInfo.append("\n").append(QString("%1\n- Qt %2\n- %3\n\n")
.arg(tr("Libraries:"))
.arg(QString::fromLocal8Bit(qVersion()))
.arg(Crypto::backendVersion()));
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
debugInfo.append(tr("Operating system: %1\nCPU architecture: %2\nKernel: %3 %4")
@ -83,6 +87,9 @@ AboutDialog::AboutDialog(QWidget* parent)
#ifdef WITH_XC_YUBIKEY
extensions += "\n- YubiKey";
#endif
#ifdef WITH_XC_SSHAGENT
extensions += "\n- SSH Agent";
#endif
if (extensions.isEmpty())
extensions = " None";

View File

@ -28,6 +28,9 @@ Clipboard* Clipboard::m_instance(nullptr);
Clipboard::Clipboard(QObject* parent)
: QObject(parent)
, m_timer(new QTimer(this))
#ifdef Q_OS_MAC
, m_pasteboard(new MacPasteboard)
#endif
{
m_timer->setSingleShot(true);
connect(m_timer, SIGNAL(timeout()), SLOT(clearClipboard()));
@ -38,10 +41,17 @@ void Clipboard::setText(const QString& text)
{
QClipboard* clipboard = QApplication::clipboard();
#ifdef Q_OS_MAC
QMimeData* mime = new QMimeData;
mime->setText(text);
mime->setData("application/x-nspasteboard-concealed-type", text.toUtf8());
clipboard->setMimeData(mime, QClipboard::Clipboard);
#else
clipboard->setText(text, QClipboard::Clipboard);
if (clipboard->supportsSelection()) {
clipboard->setText(text, QClipboard::Selection);
}
#endif
if (config()->get("security/clearclipboard").toBool()) {
int timeout = config()->get("security/clearclipboardtimeout").toInt();

View File

@ -19,6 +19,9 @@
#define KEEPASSX_CLIPBOARD_H
#include <QObject>
#ifdef Q_OS_MAC
#include "core/MacPasteboard.h"
#endif
class QTimer;
@ -43,6 +46,9 @@ private:
static Clipboard* m_instance;
QTimer* m_timer;
#ifdef Q_OS_MAC
QScopedPointer<MacPasteboard> m_pasteboard;
#endif
QString m_lastCopied;
};

View File

@ -95,11 +95,16 @@ void DatabaseOpenWidget::showEvent(QShowEvent* event)
m_ui->editPassword->setFocus();
#ifdef WITH_XC_YUBIKEY
connect(YubiKey::instance(), SIGNAL(detected(int,bool)), SLOT(yubikeyDetected(int,bool)), Qt::QueuedConnection);
connect(YubiKey::instance(), SIGNAL(detectComplete()), SLOT(yubikeyDetectComplete()), Qt::QueuedConnection);
connect(YubiKey::instance(), SIGNAL(notFound()), SLOT(noYubikeyFound()), Qt::QueuedConnection);
// showEvent() may be called twice, so make sure we are only polling once
if (!m_yubiKeyBeingPolled) {
connect(YubiKey::instance(), SIGNAL(detected(int, bool)), SLOT(yubikeyDetected(int, bool)),
Qt::QueuedConnection);
connect(YubiKey::instance(), SIGNAL(detectComplete()), SLOT(yubikeyDetectComplete()), Qt::QueuedConnection);
connect(YubiKey::instance(), SIGNAL(notFound()), SLOT(noYubikeyFound()), Qt::QueuedConnection);
pollYubikey();
pollYubikey();
m_yubiKeyBeingPolled = true;
}
#endif
}
@ -110,6 +115,7 @@ void DatabaseOpenWidget::hideEvent(QHideEvent* event)
#ifdef WITH_XC_YUBIKEY
// Don't listen to any Yubikey events if we are hidden
disconnect(YubiKey::instance(), 0, this, 0);
m_yubiKeyBeingPolled = false;
#endif
}
@ -162,7 +168,10 @@ void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
void DatabaseOpenWidget::openDatabase()
{
KeePass2Reader reader;
CompositeKey masterKey = databaseKey();
QSharedPointer<CompositeKey> masterKey = databaseKey();
if (masterKey.isNull()) {
return;
}
QFile file(m_filename);
if (!file.open(QIODevice::ReadOnly)) {
@ -174,7 +183,7 @@ void DatabaseOpenWidget::openDatabase()
delete m_db;
}
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
m_db = reader.readDatabase(&file, masterKey);
m_db = reader.readDatabase(&file, *masterKey);
QApplication::restoreOverrideCursor();
if (m_db) {
@ -182,20 +191,19 @@ void DatabaseOpenWidget::openDatabase()
m_ui->messageWidget->animatedHide();
}
emit editFinished(true);
}
else {
m_ui->messageWidget->showMessage(tr("Unable to open the database.")
.append("\n").append(reader.errorString()), MessageWidget::Error);
} else {
m_ui->messageWidget->showMessage(tr("Unable to open the database.").append("\n").append(reader.errorString()),
MessageWidget::Error);
m_ui->editPassword->clear();
}
}
CompositeKey DatabaseOpenWidget::databaseKey()
QSharedPointer<CompositeKey> DatabaseOpenWidget::databaseKey()
{
CompositeKey masterKey;
auto masterKey = QSharedPointer<CompositeKey>::create();
if (m_ui->checkPassword->isChecked()) {
masterKey.addKey(PasswordKey(m_ui->editPassword->text()));
masterKey->addKey(PasswordKey(m_ui->editPassword->text()));
}
QHash<QString, QVariant> lastKeyFiles = config()->get("LastKeyFiles").toHash();
@ -206,11 +214,11 @@ CompositeKey DatabaseOpenWidget::databaseKey()
QString keyFilename = m_ui->comboKeyFile->currentText();
QString errorMsg;
if (!key.load(keyFilename, &errorMsg)) {
m_ui->messageWidget->showMessage(tr("Can't open key file").append(":\n")
.append(errorMsg), MessageWidget::Error);
return CompositeKey();
m_ui->messageWidget->showMessage(tr("Can't open key file").append(":\n").append(errorMsg),
MessageWidget::Error);
return QSharedPointer<CompositeKey>();
}
masterKey.addKey(key);
masterKey->addKey(key);
lastKeyFiles[m_filename] = keyFilename;
} else {
lastKeyFiles.remove(m_filename);
@ -237,9 +245,9 @@ CompositeKey DatabaseOpenWidget::databaseKey()
// read blocking mode from LSB and slot index number from second LSB
bool blocking = comboPayload & 1;
int slot = comboPayload >> 1;
auto key = QSharedPointer<YkChallengeResponseKey>(new YkChallengeResponseKey(slot, blocking));
masterKey.addChallengeResponseKey(key);
int slot = comboPayload >> 1;
auto key = QSharedPointer<YkChallengeResponseKey>(new YkChallengeResponseKey(slot, blocking));
masterKey->addChallengeResponseKey(key);
}
#endif
@ -269,6 +277,9 @@ void DatabaseOpenWidget::activateChallengeResponse()
void DatabaseOpenWidget::browseKeyFile()
{
QString filters = QString("%1 (*);;%2 (*.key)").arg(tr("All files"), tr("Key files"));
if (!config()->get("RememberLastKeyFiles").toBool()) {
fileDialog()->setNextForgetDialog();
}
QString filename = fileDialog()->getOpenFileName(this, tr("Select key file"), QString(), filters);
if (!filename.isEmpty()) {
@ -309,10 +320,12 @@ void DatabaseOpenWidget::yubikeyDetectComplete()
m_ui->checkChallengeResponse->setEnabled(true);
m_ui->buttonRedetectYubikey->setEnabled(true);
m_ui->yubikeyProgress->setVisible(false);
m_yubiKeyBeingPolled = false;
}
void DatabaseOpenWidget::noYubikeyFound()
{
m_ui->buttonRedetectYubikey->setEnabled(true);
m_ui->yubikeyProgress->setVisible(false);
m_yubiKeyBeingPolled = false;
}

View File

@ -52,7 +52,7 @@ signals:
protected:
void showEvent(QShowEvent* event) override;
void hideEvent(QHideEvent* event) override;
CompositeKey databaseKey();
QSharedPointer<CompositeKey> databaseKey();
protected slots:
virtual void openDatabase();
@ -73,6 +73,7 @@ protected:
QString m_filename;
private:
bool m_yubiKeyBeingPolled = false;
Q_DISABLE_COPY(DatabaseOpenWidget)
};

View File

@ -129,12 +129,11 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
while (i.hasNext()) {
i.next();
if (i.value().canonicalFilePath == canonicalFilePath) {
if (pw.isEmpty() && keyFile.isEmpty()) {
setCurrentIndex(databaseIndex(i.key()));
if (!i.value().dbWidget->dbHasKey() && !(pw.isNull() && keyFile.isEmpty())) {
// If the database is locked and a pw or keyfile is provided, unlock it
i.value().dbWidget->switchToOpenDatabase(i.value().filePath, pw, keyFile);
} else {
if (!i.key()->hasKey()) {
i.value().dbWidget->switchToOpenDatabase(canonicalFilePath, pw, keyFile);
}
setCurrentIndex(databaseIndex(i.key()));
}
return;
}
@ -210,7 +209,7 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
updateLastDatabases(dbStruct.filePath);
if (!pw.isNull() || !keyFile.isEmpty()) {
if (!(pw.isNull() && keyFile.isEmpty())) {
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, pw, keyFile);
}
else {
@ -299,8 +298,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
if (!saveDatabase(db)) {
return false;
}
}
else {
} else if (dbStruct.dbWidget->currentMode() != DatabaseWidget::LockedMode) {
QMessageBox::StandardButton result =
MessageBox::question(
this, tr("Save changes?"),
@ -308,10 +306,9 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
if (result == QMessageBox::Yes) {
if (!saveDatabase(db)) {
return false;
return false;
}
}
else if (result == QMessageBox::Cancel) {
} else if (result == QMessageBox::Cancel) {
return false;
}
}
@ -356,8 +353,13 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
{
DatabaseManagerStruct& dbStruct = m_dbList[db];
if (dbStruct.saveToFilename) {
if (dbStruct.dbWidget->currentMode() == DatabaseWidget::LockedMode) {
// Never allow saving a locked database; it causes corruption
// We return true since a save is not required
return true;
}
if (dbStruct.saveToFilename) {
dbStruct.dbWidget->blockAutoReload(true);
QString errorMessage = db->saveToFile(dbStruct.canonicalFilePath);
dbStruct.dbWidget->blockAutoReload(false);
@ -376,7 +378,6 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
MessageWidget::Error);
return false;
}
} else {
return saveDatabaseAs(db);
}
@ -540,6 +541,16 @@ bool DatabaseTabWidget::readOnly(int index)
return indexDatabaseManagerStruct(index).readOnly;
}
bool DatabaseTabWidget::canSave(int index)
{
if (index == -1) {
index = currentIndex();
}
const DatabaseManagerStruct& dbStruct = indexDatabaseManagerStruct(index);
return !dbStruct.saveToFilename || (dbStruct.modified && !dbStruct.readOnly);
}
bool DatabaseTabWidget::isModified(int index)
{
if (index == -1) {

View File

@ -78,6 +78,7 @@ public slots:
void changeMasterKey();
void changeDatabaseSettings();
bool readOnly(int index = -1);
bool canSave(int index = -1);
bool isModified(int index = -1);
void performGlobalAutoType();
void lockDatabases();

View File

@ -47,6 +47,7 @@
#include "gui/TotpDialog.h"
#include "gui/DatabaseOpenWidget.h"
#include "gui/DatabaseSettingsWidget.h"
#include "gui/DetailsWidget.h"
#include "gui/KeePass1OpenWidget.h"
#include "gui/MessageBox.h"
#include "gui/UnlockDatabaseWidget.h"
@ -56,12 +57,19 @@
#include "gui/group/EditGroupWidget.h"
#include "gui/group/GroupView.h"
#include "config-keepassx.h"
#ifdef WITH_XC_SSHAGENT
#include "sshagent/SSHAgent.h"
#endif
DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
: QStackedWidget(parent)
, m_db(db)
, m_newGroup(nullptr)
, m_newEntry(nullptr)
, m_newParent(nullptr)
, m_importingCsv(false)
{
m_mainWidget = new QWidget(this);
@ -72,12 +80,15 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
QLayout* layout = new QHBoxLayout();
mainLayout->addWidget(m_messageWidget);
mainLayout->addLayout(layout);
m_splitter = new QSplitter(m_mainWidget);
m_splitter->setChildrenCollapsible(false);
m_mainSplitter = new QSplitter(m_mainWidget);
m_mainSplitter->setChildrenCollapsible(false);
m_detailSplitter = new QSplitter(m_mainWidget);
m_detailSplitter->setOrientation(Qt::Vertical);
m_detailSplitter->setChildrenCollapsible(true);
QWidget* rightHandSideWidget = new QWidget(m_splitter);
QWidget* rightHandSideWidget = new QWidget(m_mainSplitter);
m_groupView = new GroupView(db, m_splitter);
m_groupView = new GroupView(db, m_mainSplitter);
m_groupView->setObjectName("groupView");
m_groupView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_groupView, SIGNAL(customContextMenuRequested(QPoint)),
@ -99,10 +110,18 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
"border: 2px solid rgb(190, 190, 190);"
"border-radius: 5px;");
m_detailsView = new DetailsWidget(this);
QVBoxLayout* vLayout = new QVBoxLayout(rightHandSideWidget);
vLayout->setMargin(0);
vLayout->addWidget(m_searchingLabel);
vLayout->addWidget(m_entryView);
vLayout->addWidget(m_detailSplitter);
m_detailSplitter->addWidget(m_entryView);
m_detailSplitter->addWidget(m_detailsView);
m_detailSplitter->setStretchFactor(0, 80);
m_detailSplitter->setStretchFactor(1, 20);
m_searchingLabel->setVisible(false);
@ -110,13 +129,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
setTabOrder(m_entryView, m_groupView);
m_splitter->addWidget(m_groupView);
m_splitter->addWidget(rightHandSideWidget);
m_mainSplitter->addWidget(m_groupView);
m_mainSplitter->addWidget(rightHandSideWidget);
m_splitter->setStretchFactor(0, 30);
m_splitter->setStretchFactor(1, 70);
m_mainSplitter->setStretchFactor(0, 30);
m_mainSplitter->setStretchFactor(1, 70);
layout->addWidget(m_splitter);
layout->addWidget(m_mainSplitter);
m_mainWidget->setLayout(mainLayout);
m_editEntryWidget = new EditEntryWidget();
@ -125,13 +144,14 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
m_editGroupWidget = new EditGroupWidget();
m_editGroupWidget->setObjectName("editGroupWidget");
m_changeMasterKeyWidget = new ChangeMasterKeyWidget();
m_changeMasterKeyWidget->setObjectName("changeMasterKeyWidget");
m_changeMasterKeyWidget->headlineLabel()->setText(tr("Change master key"));
m_csvImportWizard = new CsvImportWizard();
m_csvImportWizard->setObjectName("csvImportWizard");
QFont headlineLabelFont = m_changeMasterKeyWidget->headlineLabel()->font();
headlineLabelFont.setBold(true);
headlineLabelFont.setPointSize(headlineLabelFont.pointSize() + 2);
m_changeMasterKeyWidget->headlineLabel()->setFont(headlineLabelFont);
m_csvImportWizard = new CsvImportWizard();
m_csvImportWizard->setObjectName("csvImportWizard");
m_databaseSettingsWidget = new DatabaseSettingsWidget();
m_databaseSettingsWidget->setObjectName("databaseSettingsWidget");
m_databaseOpenWidget = new DatabaseOpenWidget();
@ -156,7 +176,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
addWidget(m_keepass1OpenWidget);
addWidget(m_unlockDatabaseWidget);
connect(m_splitter, SIGNAL(splitterMoved(int,int)), SIGNAL(splitterSizesChanged()));
connect(m_mainSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(mainSplitterSizesChanged()));
connect(m_detailSplitter, SIGNAL(splitterMoved(int,int)), SIGNAL(detailSplitterSizesChanged()));
connect(m_entryView->header(), SIGNAL(sectionResized(int,int,int)), SIGNAL(entryColumnSizesChanged()));
connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(onGroupChanged(Group*)));
connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged()));
@ -180,6 +201,12 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
connect(&m_fileWatchUnblockTimer, SIGNAL(timeout()), this, SLOT(unblockAutoReload()));
connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged()));
connect(m_groupView, SIGNAL(groupPressed(Group*)), SLOT(emitPressedGroup(Group*)));
connect(m_groupView, SIGNAL(groupChanged(Group*)), SLOT(emitPressedGroup(Group*)));
connect(m_entryView, SIGNAL(entryPressed(Entry*)), SLOT(emitPressedEntry(Entry*)));
connect(m_entryView, SIGNAL(entrySelectionChanged()), SLOT(emitPressedEntry()));
connect(m_editEntryWidget, SIGNAL(editFinished(bool)), SLOT(emitPressedEntry()));
m_databaseModified = false;
m_fileWatchTimer.setSingleShot(true);
@ -189,6 +216,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
m_searchCaseSensitive = false;
m_searchLimitGroup = config()->get("SearchLimitGroup", false).toBool();
#ifdef WITH_XC_SSHAGENT
if (config()->get("SSHAgent", false).toBool()) {
connect(this, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), SSHAgent::instance(), SLOT(databaseModeChanged(DatabaseWidget::Mode)));
connect(this, SIGNAL(closeRequest()), SSHAgent::instance(), SLOT(databaseModeChanged()));
}
#endif
setCurrentWidget(m_mainWidget);
}
@ -233,14 +267,24 @@ bool DatabaseWidget::isEditWidgetModified() const
}
}
QList<int> DatabaseWidget::splitterSizes() const
QList<int> DatabaseWidget::mainSplitterSizes() const
{
return m_splitter->sizes();
return m_mainSplitter->sizes();
}
void DatabaseWidget::setSplitterSizes(const QList<int>& sizes)
void DatabaseWidget::setMainSplitterSizes(const QList<int>& sizes)
{
m_splitter->setSizes(sizes);
m_mainSplitter->setSizes(sizes);
}
QList<int> DatabaseWidget::detailSplitterSizes() const
{
return m_detailSplitter->sizes();
}
void DatabaseWidget::setDetailSplitterSizes(const QList<int> &sizes)
{
m_detailSplitter->setSizes(sizes);
}
QList<int> DatabaseWidget::entryHeaderViewSizes() const
@ -373,6 +417,8 @@ void DatabaseWidget::setupTotp()
setupTotpDialog->setSeed(currentEntry->totpSeed());
setupTotpDialog->setStep(currentEntry->totpStep());
setupTotpDialog->setDigits(currentEntry->totpDigits());
// now that all settings are set, decide whether it's default, steam or custom
setupTotpDialog->setSettings(currentEntry->totpDigits());
}
setupTotpDialog->open();
@ -753,6 +799,12 @@ void DatabaseWidget::switchToGroupEdit(Group* group, bool create)
void DatabaseWidget::updateMasterKey(bool accepted)
{
if (m_importingCsv) {
setCurrentWidget(m_csvImportWizard);
m_csvImportWizard->keyFinished(accepted, m_changeMasterKeyWidget->newMasterKey());
return;
}
if (accepted) {
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey());
@ -825,7 +877,7 @@ void DatabaseWidget::unlockDatabase(bool accepted)
return;
}
Database *db = Q_NULLPTR;
Database* db = nullptr;
if (sender() == m_unlockDatabaseDialog) {
db = m_unlockDatabaseDialog->database();
} else if (sender() == m_unlockDatabaseWidget) {
@ -886,6 +938,7 @@ void DatabaseWidget::switchToMasterKeyChange(bool disableCancel)
m_changeMasterKeyWidget->clearForms();
m_changeMasterKeyWidget->setCancelEnabled(!disableCancel);
setCurrentWidget(m_changeMasterKeyWidget);
m_importingCsv = false;
}
void DatabaseWidget::switchToDatabaseSettings()
@ -921,9 +974,11 @@ void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString
void DatabaseWidget::switchToImportCsv(const QString& fileName)
{
updateFilename(fileName);
switchToMasterKeyChange();
m_csvImportWizard->load(fileName, m_db);
setCurrentWidget(m_csvImportWizard);
m_changeMasterKeyWidget->clearForms();
m_changeMasterKeyWidget->setCancelEnabled(false);
setCurrentWidget(m_changeMasterKeyWidget);
m_importingCsv = true;
}
void DatabaseWidget::switchToOpenMergeDatabase(const QString& fileName)
@ -1050,6 +1105,32 @@ void DatabaseWidget::emitEntryContextMenuRequested(const QPoint& pos)
emit entryContextMenuRequested(m_entryView->viewport()->mapToGlobal(pos));
}
void DatabaseWidget::emitPressedEntry()
{
Entry* currentEntry = m_entryView->currentEntry();
emitPressedEntry(currentEntry);
}
void DatabaseWidget::emitPressedEntry(Entry* currentEntry)
{
if (!currentEntry) {
// if no entry is pressed, leave in details the last entry
return;
}
emit pressedEntry(currentEntry);
}
void DatabaseWidget::emitPressedGroup(Group* currentGroup)
{
if (!currentGroup) {
// if no group is pressed, leave in details the last group
return;
}
emit pressedGroup(currentGroup);
}
bool DatabaseWidget::dbHasKey() const
{
return m_db->hasKey();
@ -1134,8 +1215,13 @@ void DatabaseWidget::onWatchedFileChanged()
void DatabaseWidget::reloadDatabaseFile()
{
if (m_db == nullptr)
if (!m_db || currentMode() == DatabaseWidget::LockedMode) {
return;
}
if (currentMode() == DatabaseWidget::LockedMode) {
return;
}
if (! config()->get("AutoReloadOnChange").toBool()) {
// Ask if we want to reload the db
@ -1325,6 +1411,12 @@ void DatabaseWidget::showUnlockDialog()
{
m_unlockDatabaseDialog->clearForms();
m_unlockDatabaseDialog->setDBFilename(m_filename);
#if defined(Q_OS_MAC)
autoType()->raiseWindow();
Tools::wait(500);
#endif
m_unlockDatabaseDialog->show();
m_unlockDatabaseDialog->activateWindow();
}
@ -1334,9 +1426,10 @@ void DatabaseWidget::closeUnlockDialog()
m_unlockDatabaseDialog->close();
}
void DatabaseWidget::showMessage(const QString& text, MessageWidget::MessageType type)
void DatabaseWidget::showMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton, int autoHideTimeout)
{
m_messageWidget->showMessage(text, type);
m_messageWidget->setCloseButtonVisible(showClosebutton);
m_messageWidget->showMessage(text, type, autoHideTimeout);
}
void DatabaseWidget::hideMessage()

View File

@ -47,6 +47,7 @@ class QSplitter;
class QLabel;
class UnlockDatabaseWidget;
class MessageWidget;
class DetailsWidget;
class UnlockDatabaseDialog;
class QFileSystemWatcher;
@ -87,8 +88,10 @@ public:
bool isGroupSelected() const;
bool isInEditMode() const;
bool isEditWidgetModified() const;
QList<int> splitterSizes() const;
void setSplitterSizes(const QList<int>& sizes);
QList<int> mainSplitterSizes() const;
void setMainSplitterSizes(const QList<int>& sizes);
QList<int> detailSplitterSizes() const;
void setDetailSplitterSizes(const QList<int>& sizes);
QList<int> entryHeaderViewSizes() const;
void setEntryViewHeaderSizes(const QList<int>& sizes);
void clearAllWidgets();
@ -115,12 +118,15 @@ signals:
void databaseMerged(Database* mergedDb);
void groupContextMenuRequested(const QPoint& globalPos);
void entryContextMenuRequested(const QPoint& globalPos);
void pressedEntry(Entry* selectedEntry);
void pressedGroup(Group* selectedGroup);
void unlockedDatabase();
void listModeAboutToActivate();
void listModeActivated();
void searchModeAboutToActivate();
void searchModeActivated();
void splitterSizesChanged();
void mainSplitterSizesChanged();
void detailSplitterSizesChanged();
void entryColumnSizesChanged();
void updateSearch(QString text);
@ -166,7 +172,8 @@ public slots:
void setSearchLimitGroup(bool state);
void endSearch();
void showMessage(const QString& text, MessageWidget::MessageType type);
void showMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton = true,
int autoHideTimeout = MessageWidget::DefaultAutoHideTimeout);
void hideMessage();
private slots:
@ -178,6 +185,9 @@ private slots:
void switchToGroupEdit(Group* entry, bool create);
void emitGroupContextMenuRequested(const QPoint& pos);
void emitEntryContextMenuRequested(const QPoint& pos);
void emitPressedEntry();
void emitPressedEntry(Entry* currentEntry);
void emitPressedGroup(Group* currentGroup);
void updateMasterKey(bool accepted);
void openDatabase(bool accepted);
void mergeDatabase(bool accepted);
@ -207,7 +217,8 @@ private:
KeePass1OpenWidget* m_keepass1OpenWidget;
UnlockDatabaseWidget* m_unlockDatabaseWidget;
UnlockDatabaseDialog* m_unlockDatabaseDialog;
QSplitter* m_splitter;
QSplitter* m_mainSplitter;
QSplitter* m_detailSplitter;
GroupView* m_groupView;
EntryView* m_entryView;
QLabel* m_searchingLabel;
@ -218,12 +229,16 @@ private:
Uuid m_groupBeforeLock;
Uuid m_entryBeforeLock;
MessageWidget* m_messageWidget;
DetailsWidget* m_detailsView;
// Search state
QString m_lastSearchText;
bool m_searchCaseSensitive;
bool m_searchLimitGroup;
// CSV import state
bool m_importingCsv;
// Autoreload
QFileSystemWatcher m_fileWatcher;
QTimer m_fileWatchTimer;

View File

@ -25,14 +25,16 @@ DatabaseWidgetStateSync::DatabaseWidgetStateSync(QObject* parent)
, m_activeDbWidget(nullptr)
, m_blockUpdates(false)
{
m_splitterSizes = variantToIntList(config()->get("GUI/SplitterState"));
m_mainSplitterSizes = variantToIntList(config()->get("GUI/SplitterState"));
m_detailSplitterSizes = variantToIntList(config()->get("GUI/DetailSplitterState"));
m_columnSizesList = variantToIntList(config()->get("GUI/EntryListColumnSizes"));
m_columnSizesSearch = variantToIntList(config()->get("GUI/EntrySearchColumnSizes"));
}
DatabaseWidgetStateSync::~DatabaseWidgetStateSync()
{
config()->set("GUI/SplitterState", intListToVariant(m_splitterSizes));
config()->set("GUI/SplitterState", intListToVariant(m_mainSplitterSizes));
config()->set("GUI/DetailSplitterState", intListToVariant(m_detailSplitterSizes));
config()->set("GUI/EntryListColumnSizes", intListToVariant(m_columnSizesList));
config()->set("GUI/EntrySearchColumnSizes", intListToVariant(m_columnSizesSearch));
}
@ -48,17 +50,25 @@ void DatabaseWidgetStateSync::setActive(DatabaseWidget* dbWidget)
if (m_activeDbWidget) {
m_blockUpdates = true;
if (!m_splitterSizes.isEmpty())
m_activeDbWidget->setSplitterSizes(m_splitterSizes);
if (!m_mainSplitterSizes.isEmpty()) {
m_activeDbWidget->setMainSplitterSizes(m_mainSplitterSizes);
}
if (m_activeDbWidget->isInSearchMode())
if (!m_detailSplitterSizes.isEmpty()) {
m_activeDbWidget->setDetailSplitterSizes(m_detailSplitterSizes);
}
if (m_activeDbWidget->isInSearchMode()) {
restoreSearchView();
else
} else {
restoreListView();
}
m_blockUpdates = false;
connect(m_activeDbWidget, SIGNAL(splitterSizesChanged()),
connect(m_activeDbWidget, SIGNAL(mainSplitterSizesChanged()),
SLOT(updateSplitterSizes()));
connect(m_activeDbWidget, SIGNAL(detailSplitterSizesChanged()),
SLOT(updateSplitterSizes()));
connect(m_activeDbWidget, SIGNAL(entryColumnSizesChanged()),
SLOT(updateColumnSizes()));
@ -102,7 +112,8 @@ void DatabaseWidgetStateSync::updateSplitterSizes()
return;
}
m_splitterSizes = m_activeDbWidget->splitterSizes();
m_mainSplitterSizes = m_activeDbWidget->mainSplitterSizes();
m_detailSplitterSizes = m_activeDbWidget->detailSplitterSizes();
}
void DatabaseWidgetStateSync::updateColumnSizes()

View File

@ -46,7 +46,8 @@ private:
DatabaseWidget* m_activeDbWidget;
bool m_blockUpdates;
QList<int> m_splitterSizes;
QList<int> m_mainSplitterSizes;
QList<int> m_detailSplitterSizes;
QList<int> m_columnSizesList;
QList<int> m_columnSizesSearch;
};

340
src/gui/DetailsWidget.cpp Normal file
View File

@ -0,0 +1,340 @@
/*
* 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
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DetailsWidget.h"
#include "ui_DetailsWidget.h"
#include <QDebug>
#include <QTimer>
#include "core/Config.h"
#include "core/FilePath.h"
#include "core/TimeInfo.h"
#include "gui/Clipboard.h"
#include "gui/DatabaseWidget.h"
DetailsWidget::DetailsWidget(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::DetailsWidget())
, m_locked(false)
, m_currentEntry(nullptr)
, m_currentGroup(nullptr)
, m_timer(nullptr)
, m_attributesWidget(nullptr)
, m_autotypeWidget(nullptr)
, m_selectedTabEntry(0)
, m_selectedTabGroup(0)
{
m_ui->setupUi(this);
connect(parent, SIGNAL(pressedEntry(Entry*)), SLOT(getSelectedEntry(Entry*)));
connect(parent, SIGNAL(pressedGroup(Group*)), SLOT(getSelectedGroup(Group*)));
connect(parent, SIGNAL(currentModeChanged(DatabaseWidget::Mode)), SLOT(setDatabaseMode(DatabaseWidget::Mode)));
m_ui->totpButton->setIcon(filePath()->icon("actions", "chronometer"));
m_ui->closeButton->setIcon(filePath()->icon("actions", "dialog-close"));
connect(m_ui->totpButton, SIGNAL(toggled(bool)), SLOT(showTotp(bool)));
connect(m_ui->closeButton, SIGNAL(toggled(bool)), SLOT(hideDetails()));
connect(m_ui->tabWidget, SIGNAL(tabBarClicked(int)), SLOT(updateTabIndex(int)));
this->hide();
}
DetailsWidget::~DetailsWidget()
{
}
void DetailsWidget::getSelectedEntry(Entry* selectedEntry)
{
if (!selectedEntry) {
hideDetails();
return;
}
m_currentEntry = selectedEntry;
if (!config()->get("GUI/HideDetailsView").toBool()) {
this->show();
}
m_ui->stackedWidget->setCurrentIndex(EntryPreview);
if (m_ui->tabWidget->count() < 4) {
m_ui->tabWidget->insertTab(static_cast<int>(AttributesTab), m_attributesWidget, "Attributes");
m_ui->tabWidget->insertTab(static_cast<int>(AutotypeTab), m_autotypeWidget, "Autotype");
}
m_ui->tabWidget->setTabEnabled(AttributesTab, false);
m_ui->tabWidget->setTabEnabled(NotesTab, false);
m_ui->tabWidget->setTabEnabled(AutotypeTab, false);
m_ui->totpButton->hide();
m_ui->totpWidget->hide();
m_ui->totpButton->setChecked(false);
auto icon = m_currentEntry->iconPixmap();
if (icon.width() > 16 || icon.height() > 16) {
icon = icon.scaled(16, 16);
}
m_ui->entryIcon->setPixmap(icon);
QString title = QString(" / ");
Group* entry_group = m_currentEntry->group();
if (entry_group) {
QStringList hierarchy = entry_group->hierarchy();
hierarchy.removeFirst();
title += hierarchy.join(" / ");
if (hierarchy.size() > 0) {
title += " / ";
}
}
title.append(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->title()));
m_ui->titleLabel->setText(title);
m_ui->usernameLabel->setText(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->username()));
if (!config()->get("security/hidepassworddetails").toBool()) {
m_ui->passwordLabel->setText(
shortPassword(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password())));
m_ui->passwordLabel->setToolTip(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->password()));
} else {
m_ui->passwordLabel->setText("****");
}
QString url = m_currentEntry->webUrl();
if (!url.isEmpty()) {
// URL is well formed and can be opened in a browser
// create a new display url that masks password placeholders
// the actual link will use the password
url = QString("<a href=\"%1\">%2</a>").arg(url).arg(shortUrl(m_currentEntry->displayUrl()));
m_ui->urlLabel->setOpenExternalLinks(true);
} else {
// Fallback to the raw url string
url = shortUrl(m_currentEntry->resolveMultiplePlaceholders(m_currentEntry->url()));
m_ui->urlLabel->setOpenExternalLinks(false);
}
m_ui->urlLabel->setText(url);
TimeInfo entryTime = m_currentEntry->timeInfo();
if (entryTime.expires()) {
m_ui->expirationLabel->setText(entryTime.expiryTime().toString(Qt::DefaultLocaleShortDate));
} else {
m_ui->expirationLabel->setText(tr("Never"));
}
if (m_currentEntry->hasTotp()) {
m_step = m_currentEntry->totpStep();
if (nullptr != m_timer) {
m_timer->stop();
}
m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()), this, SLOT(updateTotp()));
updateTotp();
m_timer->start(m_step * 10);
m_ui->totpButton->show();
}
QString notes = m_currentEntry->notes();
if (!notes.isEmpty()) {
m_ui->tabWidget->setTabEnabled(NotesTab, true);
m_ui->notesEdit->setText(m_currentEntry->resolveMultiplePlaceholders(notes));
}
QStringList customAttributes = m_currentEntry->attributes()->customKeys();
if (customAttributes.size() > 0) {
m_ui->tabWidget->setTabEnabled(AttributesTab, true);
m_ui->attributesEdit->clear();
QString attributesText = QString();
for (const QString& key : customAttributes) {
QString value = m_currentEntry->attributes()->value(key);
if (m_currentEntry->attributes()->isProtected(key)) {
value = "<i>" + tr("[PROTECTED]") + "</i>";
}
attributesText.append(QString("<b>%1</b>: %2<br/>").arg(key, value));
}
m_ui->attributesEdit->setText(attributesText);
}
m_ui->autotypeTree->clear();
AutoTypeAssociations* autotypeAssociations = m_currentEntry->autoTypeAssociations();
QList<QTreeWidgetItem*> items;
for (auto assoc : autotypeAssociations->getAll()) {
QStringList association = QStringList() << assoc.window << assoc.sequence;
if (association.at(1).isEmpty()) {
association.replace(1, m_currentEntry->effectiveAutoTypeSequence());
}
items.append(new QTreeWidgetItem(m_ui->autotypeTree, association));
}
if (items.count() > 0) {
m_ui->autotypeTree->addTopLevelItems(items);
m_ui->tabWidget->setTabEnabled(AutotypeTab, true);
}
if (m_ui->tabWidget->isTabEnabled(m_selectedTabEntry)) {
m_ui->tabWidget->setCurrentIndex(m_selectedTabEntry);
}
}
void DetailsWidget::getSelectedGroup(Group* selectedGroup)
{
if (!selectedGroup) {
hideDetails();
return;
}
m_currentGroup = selectedGroup;
if (!config()->get("GUI/HideDetailsView").toBool()) {
this->show();
}
m_ui->stackedWidget->setCurrentIndex(GroupPreview);
if (m_ui->tabWidget->count() > 2) {
m_autotypeWidget = m_ui->tabWidget->widget(AutotypeTab);
m_attributesWidget = m_ui->tabWidget->widget(AttributesTab);
m_ui->tabWidget->removeTab(AutotypeTab);
m_ui->tabWidget->removeTab(AttributesTab);
}
m_ui->tabWidget->setTabEnabled(GroupNotesTab, false);
m_ui->totpButton->hide();
m_ui->totpWidget->hide();
auto icon = m_currentGroup->iconPixmap();
if (icon.width() > 32 || icon.height() > 32) {
icon = icon.scaled(32, 32);
}
m_ui->entryIcon->setPixmap(icon);
QString title = " / ";
QStringList hierarchy = m_currentGroup->hierarchy();
hierarchy.removeFirst();
title += hierarchy.join(" / ");
if (hierarchy.size() > 0) {
title += " / ";
}
m_ui->titleLabel->setText(title);
QString notes = m_currentGroup->notes();
if (!notes.isEmpty()) {
m_ui->tabWidget->setTabEnabled(GroupNotesTab, true);
m_ui->notesEdit->setText(notes);
}
QString searching = tr("Disabled");
if (m_currentGroup->resolveSearchingEnabled()) {
searching = tr("Enabled");
}
m_ui->searchingLabel->setText(searching);
QString autotype = tr("Disabled");
if (m_currentGroup->resolveAutoTypeEnabled()) {
autotype = tr("Enabled");
}
m_ui->autotypeLabel->setText(autotype);
TimeInfo groupTime = m_currentGroup->timeInfo();
if (groupTime.expires()) {
m_ui->groupExpirationLabel->setText(groupTime.expiryTime().toString(Qt::DefaultLocaleShortDate));
} else {
m_ui->groupExpirationLabel->setText(tr("Never"));
}
if (m_ui->tabWidget->isTabEnabled(m_selectedTabGroup)) {
m_ui->tabWidget->setCurrentIndex(m_selectedTabGroup);
}
}
void DetailsWidget::updateTotp()
{
if (!m_locked) {
QString totpCode = m_currentEntry->totp();
QString firstHalf = totpCode.left(totpCode.size() / 2);
QString secondHalf = totpCode.mid(totpCode.size() / 2);
m_ui->totpLabel->setText(firstHalf + " " + secondHalf);
} else if (nullptr != m_timer) {
m_timer->stop();
}
}
void DetailsWidget::showTotp(bool visible)
{
if (visible) {
m_ui->totpWidget->show();
} else {
m_ui->totpWidget->hide();
}
}
QString DetailsWidget::shortUrl(QString url)
{
QString newurl = "";
if (url.length() > 60) {
newurl.append(url.left(20));
newurl.append("");
newurl.append(url.right(20));
return newurl;
}
return url;
}
QString DetailsWidget::shortPassword(QString password)
{
QString newpassword = "";
if (password.length() > 60) {
newpassword.append(password.left(50));
newpassword.append("");
return newpassword;
}
return password;
}
void DetailsWidget::hideDetails()
{
this->hide();
}
void DetailsWidget::setDatabaseMode(DatabaseWidget::Mode mode)
{
m_locked = false;
if (mode == DatabaseWidget::LockedMode) {
m_locked = true;
return;
}
if (mode == DatabaseWidget::ViewMode) {
if (m_ui->stackedWidget->currentIndex() == GroupPreview) {
getSelectedGroup(m_currentGroup);
} else {
getSelectedEntry(m_currentEntry);
}
}
}
void DetailsWidget::updateTabIndex(int index)
{
if (m_ui->stackedWidget->currentIndex() == GroupPreview) {
m_selectedTabGroup = index;
} else {
m_selectedTabEntry = index;
}
}

75
src/gui/DetailsWidget.h Normal file
View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_DETAILSWIDGET_H
#define KEEPASSX_DETAILSWIDGET_H
#include "gui/DatabaseWidget.h"
#include <QWidget>
namespace Ui {
class DetailsWidget;
}
class DetailsWidget : public QWidget
{
Q_OBJECT
public:
explicit DetailsWidget(QWidget* parent = nullptr);
~DetailsWidget();
enum StackedWidgetIndex
{
EntryPreview = 0,
GroupPreview = 1,
};
enum TabWidgetIndex
{
GeneralTab = 0,
AttributesTab = 1,
GroupNotesTab = 1,
NotesTab = 2,
AutotypeTab = 3,
};
private slots:
void getSelectedEntry(Entry* selectedEntry);
void getSelectedGroup(Group* selectedGroup);
void showTotp(bool visible);
void updateTotp();
void hideDetails();
void setDatabaseMode(DatabaseWidget::Mode mode);
void updateTabIndex(int index);
private:
const QScopedPointer<Ui::DetailsWidget> m_ui;
bool m_locked;
Entry* m_currentEntry;
Group* m_currentGroup;
quint8 m_step;
QTimer* m_timer;
QWidget* m_attributesWidget;
QWidget* m_autotypeWidget;
quint8 m_selectedTabEntry;
quint8 m_selectedTabGroup;
QString shortUrl(QString url);
QString shortPassword(QString password);
};
#endif // KEEPASSX_DETAILSWIDGET_H

539
src/gui/DetailsWidget.ui Normal file
View File

@ -0,0 +1,539 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DetailsWidget</class>
<widget class="QWidget" name="DetailsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>200</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0,0,0">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QLabel" name="entryIcon">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="titleLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
</widget>
</item>
<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="QWidget" name="totpWidget" native="true">
<layout class="QGridLayout" name="gridLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="totpLabel">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QToolButton" name="totpButton">
<property name="toolTip">
<string>Generate TOTP Token</string>
</property>
<property name="text">
<string/>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="closeButton">
<property name="toolTip">
<string>Close</string>
</property>
<property name="text">
<string/>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<property name="documentMode">
<bool>false</bool>
</property>
<property name="tabsClosable">
<bool>false</bool>
</property>
<property name="movable">
<bool>false</bool>
</property>
<widget class="QWidget" name="generalTab">
<attribute name="title">
<string>General</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="entryPage">
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0,1">
<item row="0" column="2">
<widget class="QLabel" name="usernameLabel">
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="passwordLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Password</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="2">
<widget class="QLabel" name="expirationLabel"/>
</item>
<item row="4" column="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<widget class="QLabel" name="urlLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>URL</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="urlLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="expirationLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Expiration</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="passwordLabel"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="usernameLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Username</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="groupPage">
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,1">
<item row="0" column="1">
<widget class="QLabel" name="autotypeLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Autotype</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="groupExpirationLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="3" column="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<widget class="QLabel" name="searchingLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="searchingLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Searching</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="autotypeLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="groupExpirationLabel_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Expiration</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="attributesTab">
<attribute name="title">
<string>Attributes</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextEdit" name="attributesEdit">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="notesTab">
<attribute name="title">
<string>Notes</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTextEdit" name="notesEdit">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="autotypeTab">
<attribute name="title">
<string>Autotype</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QTreeWidget" name="autotypeTree">
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="showDropIndicator" stdset="0">
<bool>true</bool>
</property>
<property name="rootIsDecorated">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="headerCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>250</number>
</attribute>
<attribute name="headerMinimumSectionSize">
<number>50</number>
</attribute>
<attribute name="headerStretchLastSection">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string>Window</string>
</property>
</column>
<column>
<property name="text">
<string>Sequence</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
<action name="searchIcon">
<property name="text">
<string>Search</string>
</property>
</action>
<action name="clearIcon">
<property name="text">
<string>Clear</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -27,7 +27,7 @@ QString FileDialog::getOpenFileName(QWidget* parent, const QString& caption, QSt
{
if (!m_nextFileName.isEmpty()) {
QString result = m_nextFileName;
m_nextFileName = "";
m_nextFileName.clear();
return result;
}
else {
@ -43,11 +43,37 @@ QString FileDialog::getOpenFileName(QWidget* parent, const QString& caption, QSt
parent->activateWindow();
}
if (!result.isEmpty()) {
config()->set("LastDir", QFileInfo(result).absolutePath());
saveLastDir(result);
return result;
}
}
QStringList FileDialog::getOpenFileNames(QWidget *parent, const QString &caption, QString dir,
const QString &filter, QString *selectedFilter,
QFileDialog::Options options)
{
if (!m_nextFileNames.isEmpty()) {
QStringList results = m_nextFileNames;
m_nextFileNames.clear();
return results;
}
else {
if (dir.isEmpty()) {
dir = config()->get("LastDir").toString();
}
return result;
QStringList results = QFileDialog::getOpenFileNames(parent, caption, dir, filter,
selectedFilter, options);
// on Mac OS X the focus is lost after closing the native dialog
if (parent) {
parent->activateWindow();
}
if (!results.isEmpty()) {
saveLastDir(results[0]);
}
return results;
}
}
@ -57,7 +83,7 @@ QString FileDialog::getSaveFileName(QWidget* parent, const QString& caption, QSt
{
if (!m_nextFileName.isEmpty()) {
QString result = m_nextFileName;
m_nextFileName = "";
m_nextFileName.clear();
return result;
}
else {
@ -95,11 +121,33 @@ QString FileDialog::getSaveFileName(QWidget* parent, const QString& caption, QSt
parent->activateWindow();
}
if (!result.isEmpty()) {
config()->set("LastDir", QFileInfo(result).absolutePath());
saveLastDir(result);
return result;
}
}
QString FileDialog::getExistingDirectory(QWidget *parent, const QString &caption, QString dir,
QFileDialog::Options options)
{
if (!m_nextDirName.isEmpty()) {
QString result = m_nextDirName;
m_nextDirName.clear();
return result;
}
else {
if (dir.isEmpty()) {
dir = config()->get("LastDir").toString();
}
return result;
dir = QFileDialog::getExistingDirectory(parent, caption, dir, options);
// on Mac OS X the focus is lost after closing the native dialog
if (parent) {
parent->activateWindow();
}
saveLastDir(dir);
return dir;
}
}
@ -108,10 +156,33 @@ void FileDialog::setNextFileName(const QString& fileName)
m_nextFileName = fileName;
}
void FileDialog::setNextFileNames(const QStringList &fileNames)
{
m_nextFileNames = fileNames;
}
void FileDialog::setNextDirName(const QString &dirName)
{
m_nextDirName = dirName;
}
void FileDialog::setNextForgetDialog()
{
m_forgetLastDir = true;
}
FileDialog::FileDialog()
{
}
void FileDialog::saveLastDir(QString dir) {
if (!dir.isEmpty() && !m_forgetLastDir) {
config()->set("LastDir", QFileInfo(dir).absolutePath());
}
m_forgetLastDir = false;
}
FileDialog* FileDialog::instance()
{
if (!m_instance) {

View File

@ -26,22 +26,35 @@ public:
QString getOpenFileName(QWidget* parent = nullptr, const QString& caption = QString(),
QString dir = QString(), const QString& filter = QString(),
QString* selectedFilter = nullptr, QFileDialog::Options options = 0);
QStringList getOpenFileNames(QWidget* parent = nullptr, const QString& caption = QString(),
QString dir = QString(), const QString& filter = QString(),
QString* selectedFilter = nullptr, QFileDialog::Options options = 0);
QString getSaveFileName(QWidget* parent = nullptr, const QString& caption = QString(),
QString dir = QString(), const QString& filter = QString(),
QString* selectedFilter = nullptr, QFileDialog::Options options = 0,
const QString& defaultExtension = QString());
QString getExistingDirectory(QWidget* parent = nullptr, const QString& caption = QString(),
QString dir = QString(), QFileDialog::Options options = QFileDialog::ShowDirsOnly);
void setNextForgetDialog();
/**
* Sets the result of the next get* method call.
* Use only for testing.
*/
void setNextFileName(const QString& fileName);
void setNextFileNames(const QStringList& fileNames);
void setNextDirName(const QString& dirName);
static FileDialog* instance();
private:
FileDialog();
QString m_nextFileName;
QStringList m_nextFileNames;
QString m_nextDirName;
bool m_forgetLastDir = false;
void saveLastDir(QString);
static FileDialog* m_instance;

View File

@ -49,6 +49,11 @@
#include "http/OptionDialog.h"
#endif
#ifdef WITH_XC_SSHAGENT
#include "sshagent/AgentSettingsPage.h"
#include "sshagent/SSHAgent.h"
#endif
#include "gui/SettingsWidget.h"
#include "gui/PasswordGeneratorWidget.h"
@ -109,6 +114,8 @@ MainWindow::MainWindow()
{
m_ui->setupUi(this);
m_ui->toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
// Setup the search widget in the toolbar
SearchWidget *search = new SearchWidget();
search->connectSignals(m_actionMultiplexer);
@ -121,15 +128,13 @@ MainWindow::MainWindow()
#ifdef WITH_XC_HTTP
m_ui->settingsWidget->addSettingsPage(new HttpPlugin(m_ui->tabWidget));
#endif
#ifdef WITH_XC_SSHAGENT
SSHAgent::init(this);
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget));
#endif
setWindowIcon(filePath()->applicationIcon());
m_ui->globalMessageWidget->setHidden(true);
QAction* toggleViewAction = m_ui->toolBar->toggleViewAction();
toggleViewAction->setText(tr("Show toolbar"));
m_ui->menuView->addAction(toggleViewAction);
bool showToolbar = config()->get("ShowToolbar").toBool();
m_ui->toolBar->setVisible(showToolbar);
connect(m_ui->toolBar, SIGNAL(visibilityChanged(bool)), this, SLOT(saveToolbarState(bool)));
m_clearHistoryAction = new QAction(tr("Clear history"), m_ui->menuFile);
m_lastDatabasesActions = new QActionGroup(m_ui->menuRecentDatabases);
@ -460,7 +465,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionGroupEmptyRecycleBin->setEnabled(recycleBinSelected);
m_ui->actionChangeMasterKey->setEnabled(true);
m_ui->actionChangeDatabaseSettings->setEnabled(true);
m_ui->actionDatabaseSave->setEnabled(true);
m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave());
m_ui->actionDatabaseSaveAs->setEnabled(true);
m_ui->actionExportCsv->setEnabled(true);
m_ui->actionDatabaseMerge->setEnabled(m_ui->tabWidget->currentIndex() != -1);
@ -566,6 +571,7 @@ void MainWindow::updateWindowTitle()
if (m_ui->tabWidget->readOnly(tabWidgetIndex)) {
customWindowTitlePart.append(QString(" [%1]").arg(tr("read-only")));
}
m_ui->actionDatabaseSave->setEnabled(m_ui->tabWidget->canSave(tabWidgetIndex));
} else if (stackedWidgetIndex == 1) {
customWindowTitlePart = tr("Settings");
}
@ -732,7 +738,9 @@ void MainWindow::changeEvent(QEvent* event)
void MainWindow::saveWindowInformation()
{
config()->set("GUI/MainWindowGeometry", saveGeometry());
if (isVisible()) {
config()->set("GUI/MainWindowGeometry", saveGeometry());
}
}
bool MainWindow::saveLastDatabases()
@ -816,11 +824,6 @@ void MainWindow::showGroupContextMenu(const QPoint& globalPos)
m_ui->menuGroups->popup(globalPos);
}
void MainWindow::saveToolbarState(bool value)
{
config()->set("ShowToolbar", value);
}
void MainWindow::setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback)
{
if (!QKeySequence::keyBindings(standard).isEmpty()) {
@ -863,6 +866,7 @@ void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
void MainWindow::hideWindow()
{
saveWindowInformation();
#ifndef Q_OS_MAC
setWindowState(windowState() | Qt::WindowMinimized);
#endif
@ -884,7 +888,7 @@ void MainWindow::toggleWindow()
raise();
activateWindow();
#if defined(Q_OS_LINUX) && ! defined(QT_NO_DBUS)
#if defined(Q_OS_LINUX) && ! defined(QT_NO_DBUS) && (QT_VERSION < QT_VERSION_CHECK(5, 9, 0))
// re-register global D-Bus menu (needed on Ubuntu with Unity)
// see https://github.com/keepassxreboot/keepassxc/issues/271
// and https://bugreports.qt.io/browse/QTBUG-58723
@ -952,16 +956,17 @@ bool MainWindow::isTrayIconEnabled() const
&& QSystemTrayIcon::isSystemTrayAvailable();
}
void MainWindow::displayGlobalMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton)
void MainWindow::displayGlobalMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton,
int autoHideTimeout)
{
m_ui->globalMessageWidget->setCloseButtonVisible(showClosebutton);
m_ui->globalMessageWidget->showMessage(text, type);
m_ui->globalMessageWidget->showMessage(text, type, autoHideTimeout);
}
void MainWindow::displayTabMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton)
void MainWindow::displayTabMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton,
int autoHideTimeout)
{
m_ui->globalMessageWidget->setCloseButtonVisible(showClosebutton);
m_ui->tabWidget->currentDatabaseWidget()->showMessage(text, type);
m_ui->tabWidget->currentDatabaseWidget()->showMessage(text, type, showClosebutton, autoHideTimeout);
}
void MainWindow::hideGlobalMessage()
@ -978,7 +983,8 @@ void MainWindow::hideTabMessage()
void MainWindow::showYubiKeyPopup()
{
displayGlobalMessage(tr("Please touch the button on your YubiKey!"), MessageWidget::Information, false);
displayGlobalMessage(tr("Please touch the button on your YubiKey!"), MessageWidget::Information,
false, MessageWidget::DisableAutoHide);
setEnabled(false);
}

View File

@ -54,8 +54,10 @@ public slots:
void openDatabase(const QString& fileName, const QString& pw = QString(),
const QString& keyFile = QString());
void appExit();
void displayGlobalMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton = true);
void displayTabMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton = true);
void displayGlobalMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton = true,
int autoHideTimeout = MessageWidget::DefaultAutoHideTimeout);
void displayTabMessage(const QString& text, MessageWidget::MessageType type, bool showClosebutton = true,
int autoHideTimeout = MessageWidget::DefaultAutoHideTimeout);
void hideGlobalMessage();
void showYubiKeyPopup();
void hideYubiKeyPopup();
@ -85,7 +87,6 @@ private slots:
void updateCopyAttributesMenu();
void showEntryContextMenu(const QPoint& globalPos);
void showGroupContextMenu(const QPoint& globalPos);
void saveToolbarState(bool value);
void rememberOpenDatabases(const QString& filePath);
void applySettingsChanges();
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);

View File

@ -260,15 +260,9 @@
<addaction name="actionPasswordGenerator"/>
<addaction name="actionSettings"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>&amp;View</string>
</property>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEntries"/>
<addaction name="menuGroups"/>
<addaction name="menuView"/>
<addaction name="menuTools"/>
<addaction name="menuHelp"/>
</widget>
@ -563,7 +557,7 @@
</action>
<action name="actionGroupEmptyRecycleBin">
<property name="text">
<string>Empty recycle bin</string>
<string>E&amp;mpty recycle bin</string>
</property>
<property name="visible">
<bool>false</bool>

View File

@ -18,20 +18,53 @@
#include "MessageWidget.h"
MessageWidget::MessageWidget(QWidget* parent)
:KMessageWidget(parent)
{
#include "QTimer"
const int MessageWidget::DefaultAutoHideTimeout = 6000;
const int MessageWidget::DisableAutoHide = -1;
MessageWidget::MessageWidget(QWidget* parent)
: KMessageWidget(parent)
, m_autoHideTimer(new QTimer(this))
, m_autoHideTimeout(DefaultAutoHideTimeout)
{
m_autoHideTimer->setSingleShot(true);
connect(m_autoHideTimer, SIGNAL(timeout()), this, SLOT(animatedHide()));
connect(this, SIGNAL(hideAnimationFinished()), m_autoHideTimer, SLOT(stop()));
}
int MessageWidget::autoHideTimeout() const
{
return m_autoHideTimeout;
}
void MessageWidget::showMessage(const QString& text, MessageWidget::MessageType type)
{
showMessage(text, type, m_autoHideTimeout);
}
void MessageWidget::showMessage(const QString &text, KMessageWidget::MessageType type, int autoHideTimeout)
{
setMessageType(type);
setText(text);
animatedShow();
if (autoHideTimeout > 0) {
m_autoHideTimer->start(autoHideTimeout);
} else {
m_autoHideTimer->stop();
}
}
void MessageWidget::hideMessage()
{
animatedHide();
m_autoHideTimer->stop();
}
void MessageWidget::setAutoHideTimeout(int autoHideTimeout)
{
m_autoHideTimeout = autoHideTimeout;
if (autoHideTimeout <= 0) {
m_autoHideTimer->stop();
}
}

View File

@ -21,6 +21,8 @@
#include "gui/KMessageWidget.h"
class QTimer;
class MessageWidget : public KMessageWidget
{
Q_OBJECT
@ -28,10 +30,20 @@ class MessageWidget : public KMessageWidget
public:
explicit MessageWidget(QWidget* parent = 0);
int autoHideTimeout() const;
static const int DefaultAutoHideTimeout;
static const int DisableAutoHide;
public slots:
void showMessage(const QString& text, MessageWidget::MessageType type);
void showMessage(const QString& text, MessageWidget::MessageType type, int autoHideTimeout);
void hideMessage();
void setAutoHideTimeout(int autoHideTimeout);
private:
QTimer* m_autoHideTimer;
int m_autoHideTimeout;
};
#endif // MESSAGEWIDGET_H

View File

@ -31,9 +31,18 @@ PasswordEdit::PasswordEdit(QWidget* parent)
{
setEchoMode(QLineEdit::Password);
updateStylesheet();
// set font to system monospace font and increase letter spacing
// use a monospace font for the password field
QFont passwordFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
#ifdef Q_OS_WIN
// try to use Consolas on Windows, because the default Courier New has too many similar characters
QFont consolasFont = QFontDatabase().font("Consolas", passwordFont.styleName(), passwordFont.pointSize());
const QFont defaultFont;
if (passwordFont != defaultFont) {
passwordFont = consolasFont;
}
#endif
passwordFont.setLetterSpacing(QFont::PercentageSpacing, 110);
setFont(passwordFont);
}

Some files were not shown because too many files have changed in this diff Show More