mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-12-04 15:39:34 +01:00
Compare commits
1 Commits
copilot/fi
...
fix/cli-sh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87ccc85247 |
5
.github/CONTRIBUTING.md
vendored
5
.github/CONTRIBUTING.md
vendored
@@ -15,7 +15,6 @@ These are just guidelines, not rules. Use your best judgment, and feel free to p
|
||||
* [Bug reports](#bug-reports)
|
||||
* [Discuss with the team](#discuss-with-the-team)
|
||||
* [Your first code contribution](#your-first-code-contribution)
|
||||
* [Using AI](#using-ai)
|
||||
* [Pull requests](#pull-requests)
|
||||
* [Translations](#translations)
|
||||
|
||||
@@ -75,10 +74,6 @@ Unsure where to begin contributing to KeePassXC? You can start by looking throug
|
||||
|
||||
Both issue lists are sorted by total number of comments. While not perfect, looking at the number of comments on an issue can give a general idea of how much an impact a given change will have.
|
||||
|
||||
### Using AI
|
||||
|
||||
Generative AI is fast becoming a first-party feature in most development environments, including GitHub itself. If you use Generative AI to write the vast majority of your submission (e.g., agent-based or vibe coding) then you **must document your use of AI** in your pull request. Please include the service you used and/or model that generated the code. All code submissions go through a rigourous review process regardless of the development workflow used.
|
||||
|
||||
### Pull requests
|
||||
|
||||
Along with our desire to hear your feedback and suggestions, we're also interested in accepting direct assistance in the form of code.
|
||||
|
||||
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,13 +1,12 @@
|
||||
[NOTE]: # ( Describe your changes in detail. Explain large or complex code modifications. )
|
||||
[NOTE]: # ( Describe your changes in detail, why is this change required? )
|
||||
[NOTE]: # ( Explain large or complex code modifications. )
|
||||
[NOTE]: # ( If it fixes an open issue, please add "Fixes #XXX". )
|
||||
[NOTE]: # ( If you used Generative AI to write the majority of your code, you must state this. )
|
||||
|
||||
|
||||
## Screenshots
|
||||
[NOTE]: # ( Do not include screenshots of your actual database! )
|
||||
[TIP]: # ( Use View -> Allow Screen Capture )
|
||||
|
||||
|
||||
## Testing strategy
|
||||
[NOTE]: # ( Please describe in detail how you tested your changes. )
|
||||
[TIP]: # ( We expect new code to be covered by unit tests and include helpful comments. )
|
||||
|
||||
38
.github/copilot-instructions.md
vendored
38
.github/copilot-instructions.md
vendored
@@ -1,38 +0,0 @@
|
||||
This is a C++ based repository that uses Qt5 as a primary support and GUI library. This repository is for a password manager application that stores passwords
|
||||
and other highly sensitive information. The data format that passwords are stored is called KDBX which is a mixed binary and XML format that is fully encrypted
|
||||
at rest. This format is unpacked into a series of data structures: Database, Groups, and Entries. Please follow these guidelines when contributing:
|
||||
|
||||
## Code Standards
|
||||
|
||||
### Required Before Each Commit
|
||||
- Run `cmake --build . --target format` before committing any changes to ensure proper code formatting
|
||||
- This will run clang-format to ensure all code conforms to the style guide
|
||||
- From the checkout directory, also run `./release-tool i18n lupdate` to update translation files
|
||||
|
||||
### Development Flow
|
||||
- Setup Build Folder: `mkdir build; cd build`
|
||||
- Configure: `cmake -G Ninja -DWITH_XC_ALL=ON -DWITH_GUI_TESTS=ON ..`
|
||||
- Build: `cmake --build . -- -j $(nproc)`
|
||||
- Test: `ctest`
|
||||
|
||||
## Repository Structure
|
||||
- `docs/topics`: Documentation written in asciidoctor syntax
|
||||
- `src/`: Main source code files are under this subdirectory
|
||||
- `src/autotype`: Code that emulates a virtual keyboard to type into interfaces
|
||||
- `src/browser`: Interface with the KeePassXC Browser Extension using a JSON-based protocol
|
||||
- `src/cli`: Command Line Interface code
|
||||
- `src/core`: Contains files that define the data model and other shared code structures
|
||||
- `src/format`: Code for import/export and reading/writing of KDBX databases
|
||||
- `src/fdosecrets`: freedesktop.org Secret Service interface code
|
||||
- `src/quickunlock`: Quick unlock interfaces for various platforms
|
||||
- `src/sshagent`: SSH Agent interface code to load private keys from the database into ssh-agent
|
||||
- `tests/`: Test source code files
|
||||
- `tests/gui`: GUI test source code files
|
||||
|
||||
## Key Guidelines
|
||||
1. Follow C++20 and Qt5 best practices and idiomatic patterns
|
||||
2. Maintain existing code structure and organization
|
||||
3. Prefer not to edit cryptographic handling code or other sensitive parts of the code base
|
||||
4. Write unit tests for new functionality using QTest scaffolding
|
||||
5. Suggest changes to the `docs/topics` folder when appropriate
|
||||
6. Unless the change is simple, don't actually make edits to .ui files, just suggest the changes needed
|
||||
29
.github/workflows/copilot-setup-steps.yml
vendored
29
.github/workflows/copilot-setup-steps.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: "Copilot Setup Steps"
|
||||
|
||||
# Setup the environment for Copilot agents to run in
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
|
||||
jobs:
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Needed to clone the repository
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Install dependencies
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install --no-install-recommends build-essential cmake g++ ninja-build qtbase5-dev qtbase5-private-dev qttools5-dev qttools5-dev-tools libqt5svg5-dev libargon2-dev libkeyutils-dev libminizip-dev libbotan-2-dev libqrencode-dev zlib1g-dev asciidoctor libreadline-dev libpcsclite-dev libusb-1.0-0-dev libxi-dev libxtst-dev libqt5x11extras5-dev
|
||||
@@ -56,10 +56,6 @@ You may directly contribute your own code by submitting a pull request. Please r
|
||||
|
||||
Contributors are required to adhere to the project's [Code of Conduct](CODE-OF-CONDUCT.md).
|
||||
|
||||
## Generative AI
|
||||
|
||||
Generative AI is fast becoming a first-party feature in most development environments, including GitHub itself. If the majority of a code submission is made using Generative AI (e.g., agent-based or vibe coding) then **we will document that in the pull request.** All code submissions go through a rigourous review process regardless of the development workflow or submitter.
|
||||
|
||||
## License
|
||||
|
||||
KeePassXC code is licensed under GPL-2 or GPL-3. Additional licensing for third-party files is detailed in [COPYING](./COPYING).
|
||||
|
||||
@@ -7,7 +7,6 @@ KeePassXC Team <team@keepassxc.org>
|
||||
:imagesdir: images
|
||||
:stylesheet: styles/dark.css
|
||||
:toc: left
|
||||
:experimental:
|
||||
ifdef::backend-pdf[]
|
||||
:title-page:
|
||||
:title-logo-image: {imagesdir}/kpxc_logo.png
|
||||
|
||||
@@ -7,7 +7,6 @@ KeePassXC Team <team@keepassxc.org>
|
||||
:stylesheet: styles/dark.css
|
||||
:toc: left
|
||||
:sectanchors:
|
||||
:experimental:
|
||||
ifdef::backend-pdf[]
|
||||
:title-page:
|
||||
:title-logo-image: {imagesdir}/kpxc_logo.png
|
||||
|
||||
@@ -52,11 +52,12 @@ It provides the ability to query and modify the entries of a KeePass database, d
|
||||
Removes the named attachment from an entry.
|
||||
|
||||
*clip* [_options_] <__database__> <__entry__> [_timeout_]::
|
||||
Copies an attribute or the current TOTP (if the *-t* option is specified) of a database entry to the clipboard.
|
||||
Copies an attribute, current TOTP value, UUID, or tags list of a database entry to the clipboard.
|
||||
If no attribute name is specified using the *-a* option, the password is copied.
|
||||
If multiple entries with the same name exist in different groups, only the attribute for the first one is copied.
|
||||
For copying the attribute of an entry in a specific group, the group path to the entry should be specified as well, instead of just the name.
|
||||
Optionally, a timeout in seconds can be specified to automatically clear the clipboard, the default timeout is 10 seconds, set to 0 to disable.
|
||||
Note: an error will be thrown if you specify multiple options at once (eg, *--uuid* and *-a*).
|
||||
|
||||
*close*::
|
||||
In interactive mode, closes the currently opened database (see *open*).
|
||||
@@ -143,8 +144,8 @@ It provides the ability to query and modify the entries of a KeePass database, d
|
||||
Searches all entries that match a specific search term in a database.
|
||||
|
||||
*show* [_options_] <__database__> <__entry__>::
|
||||
Shows the title, username, password, URL and notes of a database entry.
|
||||
Can also show the current TOTP.
|
||||
Shows the title, username, password, URL and notes of a database entry by default.
|
||||
Can also show the current TOTP, entry UUID, and tags list.
|
||||
Regarding the occurrence of multiple entries with the same name in different groups, everything stated in the *clip* command section also applies here.
|
||||
|
||||
== OPTIONS
|
||||
@@ -235,6 +236,12 @@ The same password generation options as documented for the generate command can
|
||||
Copies the current TOTP instead of the specified attribute to the clipboard.
|
||||
Will report an error if no TOTP is configured for the entry.
|
||||
|
||||
*--uuid*::
|
||||
Copies the UUID of the entry to the clipboard.
|
||||
|
||||
*--tags*::
|
||||
Copies the tags of the entry to the clipboard.
|
||||
|
||||
*-b*, *--best*::
|
||||
Try to find and copy to clipboard a unique entry matching the input
|
||||
If a unique matching entry is found it will be copied to the clipboard.
|
||||
@@ -262,7 +269,6 @@ The same password generation options as documented for the generate command can
|
||||
*-a*, *--attributes* <__attribute__>...::
|
||||
Shows the named attributes.
|
||||
This option can be specified more than once, with each attribute shown one-per-line in the given order.
|
||||
If no attributes are specified and *-t* is not specified, a summary of the default attributes is given.
|
||||
Protected attributes will be displayed in clear text if specified explicitly by this option.
|
||||
|
||||
*--all*::
|
||||
@@ -275,7 +281,13 @@ The same password generation options as documented for the generate command can
|
||||
Shows the attachment names along with the size of the attachments.
|
||||
|
||||
*-t*, *--totp*::
|
||||
Also shows the current TOTP, reporting an error if no TOTP is configured for the entry.
|
||||
Shows the current TOTP and then exits. An error is thrown if no TOTP is configured for the entry.
|
||||
|
||||
*--uuid*::
|
||||
Shows the UUID of the entry.
|
||||
|
||||
*--tags*::
|
||||
Shows the tag list of the entry.
|
||||
|
||||
=== Diceware options
|
||||
*-W*, *--words* <__count__>::
|
||||
|
||||
@@ -4,4 +4,3 @@ KeePassXC Team <team@keepassxc.org>
|
||||
:stylesheet: ../styles/dark.css
|
||||
:icons: font
|
||||
:toc: left
|
||||
:experimental:
|
||||
|
||||
@@ -24,13 +24,13 @@ You can also set the time to remember the last used entry between presses of the
|
||||
=== Configure Auto-Type Sequences
|
||||
Each entry in your database can have multiple Auto-Type sequences associated with various window titles. Simulated key presses can be sent to any other currently open window of your choice (web browser windows, login dialogs boxes, and so on). When the Global Auto-Type hotkey is pressed, KeePassXC will search your database for entries matching the current selected window title.
|
||||
|
||||
NOTE: The default Auto-Type sequence is `{USERNAME}{TAB}{PASSWORD}{ENTER}`. This means that it first types the username of the selected entry, then presses the kbd:[Tab] key, then types the password of the entry and finally presses the kbd:[Enter] key.
|
||||
NOTE: The default Auto-Type sequence is `{USERNAME}{TAB}{PASSWORD}{ENTER}`. This means that it first types the username of the selected entry, then presses the `Tab` key, then types the password of the entry and finally presses the `Enter` key.
|
||||
|
||||
TIP: To change the default Auto-Type sequence for all entries of your database, edit the root (top-most) group of your database and set a specific sequence. Child groups and entries will inherit this sequence by default.
|
||||
|
||||
To configure Auto-Type sequences for your entries, perform the following steps:
|
||||
|
||||
1. Navigate to the entries list and open the desired entry for editing. Click the _Auto-Type_ item from the left-hand menu bar *(1)*. Press the kbd:[+] button *(2)* to add a new sequence entry. Select the desired window using the drop-down menu, or simply type a window title in the box *(3)*.
|
||||
1. Navigate to the entries list and open the desired entry for editing. Click the _Auto-Type_ item from the left-hand menu bar *(1)*. Press the `+` button *(2)* to add a new sequence entry. Select the desired window using the drop-down menu, or simply type a window title in the box *(3)*.
|
||||
+
|
||||
TIP: You can use an asterisk (`\*`) to match any value (e.g., when a window title contains a dynamic filename or website name). Set the window title to `*` to match all windows. Leave the window title blank to offer additional default Auto-Type sequences, such as custom attributes.
|
||||
+
|
||||
@@ -60,7 +60,7 @@ image::autotype_entry_sequences.png[]
|
||||
|Press the corresponding keyboard key
|
||||
|
||||
|{UP}, {DOWN}, {LEFT}, {RIGHT} |Press the corresponding arrow key
|
||||
|{LEFTBRACE}, {RIGHTBRACE} |Press kbd:[{] or kbd:[}], respectively
|
||||
|{LEFTBRACE}, {RIGHTBRACE} |Press `{` or `}`, respectively
|
||||
|{<KEY> X} |Repeat <KEY> X times (e.g., {SPACE 5} inserts five spaces)
|
||||
|{DELAY=X} |Set delay between key presses to X milliseconds
|
||||
|{DELAY X} |Pause typing for X milliseconds
|
||||
@@ -89,7 +89,7 @@ When you press the global Auto-Type hotkey, KeePassXC searches all unlocked data
|
||||
.Auto-Type sequence selection
|
||||
image::autotype_selection_dialog.png[,70%]
|
||||
|
||||
Perform the selected Auto-Type sequence by double clicking the desired row or pressing kbd:[Enter]. Press the up and down arrows to navigate the list. Sequences can be filtered through the text edit field.
|
||||
Perform the selected Auto-Type sequence by double clicking the desired row or pressing _Enter_. Press the up and down arrows to navigate the list. Sequences can be filtered through the text edit field.
|
||||
|
||||
.Auto-Type search database
|
||||
image::autotype_selection_dialog_search.png[,70%]
|
||||
@@ -104,7 +104,7 @@ The option to type just the username, password, or current TOTP value is availab
|
||||
TIP: On Windows, you will see an option to use a virtual keyboard in this sub-menu. This is an experimental feature that allows you to type into virtual machines by simulating actual keyboard presses. Some international keyboards may be unsupported due to limitations in the Windows API.
|
||||
|
||||
=== Performing Entry-Level Auto-Type
|
||||
You can quickly activate the default Auto-Type sequence for a particular entry using Entry-Level Auto-Type. For this operation, the KeePassXC window will be minimized and the Auto-Type sequence occurs in the previously selected window. You can perform Entry-Level Auto-Type from the toolbar icon *(A)*, entry context menu *(B)*, or by pressing kbd:[Ctrl+Shift+V].
|
||||
You can quickly activate the default Auto-Type sequence for a particular entry using Entry-Level Auto-Type. For this operation, the KeePassXC window will be minimized and the Auto-Type sequence occurs in the previously selected window. You can perform Entry-Level Auto-Type from the toolbar icon *(A)*, entry context menu *(B)*, or by pressing `Ctrl+Shift+V`.
|
||||
|
||||
WARNING: Be careful when using Entry-Level Auto-Type as you can inadvertently type into the wrong window. For example, a chat window or email.
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ NOTE: On Windows, you will be prompted to authenticate to Windows Hello after un
|
||||
.Windows Hello example
|
||||
image::quick_unlock_windows_hello.png[]
|
||||
|
||||
When your database is locked, you will see the following unlock dialog. Simply press kbd:[Enter] or click on _Unlock Database_ to initiate the biometric authentication process. If you are using a hardware key (e.g. Yubikey), it must be connected to your computer to complete the unlock.
|
||||
When your database is locked, you will see the following unlock dialog. Simply press _Enter_ or click on _Unlock Database_ to initiate the biometric authentication process. If you are using a hardware key (e.g. Yubikey), it must be connected to your computer to complete the unlock.
|
||||
|
||||
.Quick Unlock
|
||||
image::quick_unlock.png[]
|
||||
@@ -92,7 +92,7 @@ All the details such as usernames, passwords, URLs, attachments, notes, and so o
|
||||
|
||||
To add an entry, perform the following step:
|
||||
|
||||
1. Navigate to Entries > New Entry (or press kbd:[Ctrl+N]). The following screen appears:
|
||||
1. Navigate to Entries > New Entry (Or, press Ctrl+N). The following screen appears:
|
||||
+
|
||||
.Adding a new entry
|
||||
image::edit_entry.png[]
|
||||
@@ -115,7 +115,7 @@ To edit the details in an entry, perform the following steps:
|
||||
|
||||
1. Select the entry you want to edit.
|
||||
|
||||
2. Press kbd:[Enter], click the edit toolbar icon, or right-click and select Edit Entry from the menu.
|
||||
2. Press `Enter`, click the edit toolbar icon, or right-click and select Edit Entry from the menu.
|
||||
|
||||
3. Make the desired changes.
|
||||
|
||||
@@ -156,13 +156,13 @@ TIP: Each KeePass application has different default icons. If you use a mobile a
|
||||
==== Deleting an Entry
|
||||
To delete an entry, perform the following steps:
|
||||
|
||||
1. Select the entry you want to delete and press the kbd:[Del] button on your keyboard.
|
||||
1. Select the entry you want to delete and press the `Delete` button on your keyboard.
|
||||
|
||||
2. You will be prompted to move the entry to the Recycle Bin (if enabled).
|
||||
+
|
||||
NOTE: You can disable the recycle bin within the Database Settings. If the recycle bin is disabled then deleted entries will be permanently removed from the database.
|
||||
|
||||
3. To permanently delete the entry, navigate to the Recycle Bin, select the entry you want to delete and press the kbd:[Del] button on your keyboard.
|
||||
3. To permanently delete the entry, navigate to the Recycle Bin, select the entry you want to delete and press the `Delete` button on your keyboard.
|
||||
|
||||
// tag::advanced[]
|
||||
==== Clone an Entry
|
||||
@@ -170,7 +170,7 @@ Creating a clone of an entry provides you a ready-to-use template for creating n
|
||||
|
||||
To create a clone of an existing entry, perform the following steps:
|
||||
|
||||
1. Right-click on the entry for which you want to create a clone and select _Clone Entry_. Alternatively, select the desired entry and press kbd:[Ctrl+K].
|
||||
1. Right-click on the entry for which you want to create a clone and select _Clone Entry_. Alternatively, select the desired entry and press `Ctrl+K`.
|
||||
+
|
||||
.Clone entry from context menu
|
||||
image::clone_entry.png[]
|
||||
|
||||
@@ -3,62 +3,52 @@ include::.sharedheader[]
|
||||
:imagesdir: ../images
|
||||
|
||||
// tag::content[]
|
||||
NOTE: On macOS please substitute kbd:[Ctrl] with kbd:[Cmd] (AKA kbd:[⌘]).
|
||||
NOTE: On macOS please substitute `Ctrl` with `Cmd` (aka `⌘`).
|
||||
|
||||
[grid=rows, frame=none, width=75%]
|
||||
|===
|
||||
|Action | Keyboard Shortcut
|
||||
|Action | Keyboard Shortcut
|
||||
|
||||
|Settings | kbd:[Ctrl + ,]
|
||||
|Open Database | kbd:[Ctrl + O]
|
||||
|Save Database | kbd:[Ctrl + S]
|
||||
|Save Database As | kbd:[Ctrl + Shift + S]
|
||||
|New Database | kbd:[Ctrl + Shift + N]
|
||||
|Close Database | kbd:[Ctrl + W] +
|
||||
_or_ +
|
||||
kbd:[Ctrl + F4]
|
||||
|Lock Current Database | kbd:[Ctrl + L]
|
||||
|Lock All Databases | kbd:[Ctrl + Shift + L]
|
||||
|Database Settings | kbd:[Ctrl + Shift + ,]
|
||||
|Database Reports | kbd:[Ctrl + Shift + R]
|
||||
|Quit | kbd:[Ctrl + Q]
|
||||
|New Entry | kbd:[Ctrl + N]
|
||||
|Edit Entry | kbd:[Enter] +
|
||||
_or_ +
|
||||
kbd:[Ctrl + E]
|
||||
|Delete Entry | kbd:[Del]
|
||||
|Clone Entry | kbd:[Ctrl + D]
|
||||
|Copy Username | kbd:[Ctrl + B]
|
||||
|Copy Password | kbd:[Ctrl + C]
|
||||
|Copy URL | kbd:[Ctrl + U]
|
||||
|Open URL | kbd:[Ctrl + Shift + U]
|
||||
|Copy TOTP | kbd:[Ctrl + T]
|
||||
|Copy Password and TOTP | kbd:[Ctrl + Y]
|
||||
|Show TOTP | kbd:[Ctrl + Shift + T]
|
||||
|Trigger AutoType | kbd:[Ctrl + Shift + V]
|
||||
|Add key to SSH Agent | kbd:[Ctrl + H]
|
||||
|Remove key from SSH Agent | kbd:[Ctrl + Shift + H]
|
||||
|Move entry up (if unsorted) | kbd:[Ctrl + Alt + Up]
|
||||
|Move entry down (if unsorted) | kbd:[Ctrl + Alt + Down]
|
||||
|Sort Groups A-Z | kbd:[Ctrl + Down]
|
||||
|Sort Groups Z-A | kbd:[Ctrl + Up]
|
||||
|Minimize Window | kbd:[Ctrl + M]
|
||||
|Hide Window | kbd:[Ctrl + Shift + M]
|
||||
|Select Next Database Tab | kbd:[Ctrl + Tab] +
|
||||
_or_ +
|
||||
kbd:[Ctrl + PgDn]
|
||||
|Select Previous Database Tab | kbd:[Ctrl + Shift + Tab] +
|
||||
_or_ +
|
||||
kbd:[Ctrl + PgUp]
|
||||
|Select the nth database | kbd:[Ctrl + <n>], where kbd:[<n>] is the number of the database tab
|
||||
|Toggle Passwords Hidden | kbd:[Ctrl + Shift + C]
|
||||
|Toggle Usernames Hidden | kbd:[Ctrl + Shift + B]
|
||||
|Focus Groups (edit if focused) | kbd:[F1]
|
||||
|Focus Entries (edit if focused) | kbd:[F2]
|
||||
|Focus Search | kbd:[F3] +
|
||||
_or_ +
|
||||
kbd:[Ctrl + F]
|
||||
|Clear Search | kbd:[Esc]
|
||||
|Show Keyboard Shortcuts | kbd:[Ctrl + /]
|
||||
|Settings | Ctrl + ,
|
||||
|Open Database | Ctrl + O
|
||||
|Save Database | Ctrl + S
|
||||
|Save Database As | Ctrl + Shift + S
|
||||
|New Database | Ctrl + Shift + N
|
||||
|Close Database | Ctrl + W ; Ctrl + F4
|
||||
|Lock Current Database | Ctrl + L
|
||||
|Lock All Databases | Ctrl + Shift + L
|
||||
|Database Settings | Ctrl + Shift + ,
|
||||
|Database Reports | Ctrl + Shift + R
|
||||
|Quit | Ctrl + Q
|
||||
|New Entry | Ctrl + N
|
||||
|Edit Entry | Enter ; Ctrl + E
|
||||
|Delete Entry | Delete
|
||||
|Clone Entry | Ctrl + D
|
||||
|Copy Username | Ctrl + B
|
||||
|Copy Password | Ctrl + C
|
||||
|Copy URL | Ctrl + U
|
||||
|Open URL | Ctrl + Shift + U
|
||||
|Copy TOTP | Ctrl + T
|
||||
|Copy Password and TOTP | Ctrl + Y
|
||||
|Show TOTP | Ctrl + Shift + T
|
||||
|Trigger AutoType | Ctrl + Shift + V
|
||||
|Add key to SSH Agent | Ctrl + H
|
||||
|Remove key from SSH Agent | Ctrl + Shift + H
|
||||
|Move entry up (if unsorted) | Ctrl + Alt + Up
|
||||
|Move entry down (if unsorted) | Ctrl + Alt + Down
|
||||
|Sort Groups A-Z | Ctrl + Down
|
||||
|Sort Groups Z-A | Ctrl + Up
|
||||
|Minimize Window | Ctrl + M
|
||||
|Hide Window | Ctrl + Shift + M
|
||||
|Select Next Database Tab | Ctrl + Tab ; Ctrl + PageDn
|
||||
|Select Previous Database Tab | Ctrl + Shift + Tab ; Ctrl + PageUp
|
||||
|Select the nth database | Ctrl + n, where n is the number of the database tab
|
||||
|Toggle Passwords Hidden | Ctrl + Shift + C
|
||||
|Toggle Usernames Hidden | Ctrl + Shift + B
|
||||
|Focus Groups (edit if focused) | F1
|
||||
|Focus Entries (edit if focused) | F2
|
||||
|Focus Search | F3 ; Ctrl + F
|
||||
|Clear Search | Escape
|
||||
|Show Keyboard Shortcuts | Ctrl + /
|
||||
|===
|
||||
// end::content[]
|
||||
|
||||
@@ -19,8 +19,8 @@ image::password_generator.png[]
|
||||
|
||||
3. Select the length of the desired password by dragging the Length slider.
|
||||
4. Select the character-sets that you want to include in your password.
|
||||
5. Use the regenerate button (kbd:[Ctrl + R]) to make a new password using the chosen options.
|
||||
6. Use the clipboard button (kbd:[Ctrl + C]) to copy the generated password to the clipboard.
|
||||
5. Use the regenerate button (Ctrl + R) to make a new password using the chosen options.
|
||||
6. Use the clipboard button (Ctrl + C) to copy the generated password to the clipboard.
|
||||
7. Click the Advanced button to specify additional conditions for your desired password.
|
||||
+
|
||||
.Advanced Password Generator Options
|
||||
@@ -39,6 +39,6 @@ Word Count slider.
|
||||
3. In the Word Separator field, enter a character, word, number, or space that you want to use as a separator between the words in your passphrase.
|
||||
4. _(Optional)_ You can choose a word case between lower, upper, and title case options.
|
||||
5. _(Optional)_ You can also load your own custom word lists. Click the plus sign button to the right of the wordlist selection dialog to choose a custom word list. You can download alternative lists from the https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases[EFF's Website] or from https://github.com/redacted/XKCD-password-generator#additional-languages[GitHub].
|
||||
6. Click the Regenerate button (kbd:[Ctrl + R]) to generate a new random passphrase.
|
||||
7. Click the Clipboard button (kbd:[Ctrl + C]) to copy the passphrase to the clipboard.
|
||||
6. Click the Regenerate button (Ctrl + R) to generate a new random passphrase.
|
||||
7. Click the Clipboard button (Ctrl + C) to copy the passphrase to the clipboard.
|
||||
// end::content[]
|
||||
|
||||
@@ -77,8 +77,8 @@ Examples: +
|
||||
|Press the corresponding keyboard key
|
||||
|
||||
|{UP}, {DOWN}, {LEFT}, {RIGHT} |Press the corresponding arrow key
|
||||
|{F1}, {F2}, ..., {F16} |Press kbd:[F1], kbd:[F2], etc.
|
||||
|{LEFTBRACE}, {RIGHTBRACE} |Press kbd:[{] or kbd:[}], respectively
|
||||
|{F1}, {F2}, ..., {F16} |Press F1, F2, etc.
|
||||
|{LEFTBRACE}, {RIGHTBRACE} |Press `{` or `}`, respectively
|
||||
|{<KEY> X} |Repeat <KEY> X times (e.g., {SPACE 5} inserts five spaces)
|
||||
|{DELAY=X} |Set delay between key presses to X milliseconds
|
||||
|{DELAY X} |Pause typing for X milliseconds
|
||||
@@ -90,10 +90,10 @@ Examples: +
|
||||
|===
|
||||
|Modifier |Description
|
||||
|
||||
|+ |kbd:[Shift]
|
||||
|^ |kbd:[Ctrl]
|
||||
|% |kbd:[Alt]
|
||||
|# |kbd:[Win]/kbd:[Cmd]
|
||||
|+ |SHIFT
|
||||
|^ |CTRL
|
||||
|% |ALT
|
||||
|# |WIN/CMD
|
||||
|===
|
||||
*Text Conversions:*
|
||||
|
||||
|
||||
@@ -7635,10 +7635,6 @@ Do you want to overwrite it?</source>
|
||||
<source>Entry %1 not found.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ERROR: Please specify one of --attribute or --totp, not both.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Entry with path %1 has no TOTP set up.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -8352,18 +8348,10 @@ Available commands:
|
||||
<source>Search term.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show the entry's current TOTP.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show the protected attributes in clear text.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show all the attributes of the entry.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show the attachments of the entry.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -9248,6 +9236,34 @@ This option is deprecated, use --set-key-file instead.</source>
|
||||
<source>Tags</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy the entry's UUID to the clipboard.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy the entry's tag list to the clipboard.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ERROR: Cannot specify multiple options at once (--attribute, --totp, --uuid, --tags).</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Only show the entry's current TOTP.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show the entry's UUID.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show the entry's tags.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show all the attributes of the entry, including UUID and Tags.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QtIOCompressor</name>
|
||||
@@ -10158,10 +10174,6 @@ This option is deprecated, use --set-key-file instead.</source>
|
||||
<source>Weak Passwords</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TOTP Entries</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TagView</name>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -71,7 +71,7 @@ PublicKeyCredential BrowserPasskeys::buildRegisterPublicKeyCredential(const QJso
|
||||
}
|
||||
|
||||
const auto authenticatorAttachment = credentialCreationOptions["authenticatorAttachment"];
|
||||
const auto clientDataJson = credentialCreationOptions["clientDataJSON"].toString();
|
||||
const auto clientDataJson = credentialCreationOptions["clientDataJSON"].toObject();
|
||||
const auto extensions = credentialCreationOptions["extensions"].toString();
|
||||
const auto credentialId = testingVariables.credentialId.isEmpty()
|
||||
? browserMessageBuilder()->getRandomBytesAsBase64(ID_BYTES)
|
||||
@@ -98,7 +98,7 @@ PublicKeyCredential BrowserPasskeys::buildRegisterPublicKeyCredential(const QJso
|
||||
// Response
|
||||
QJsonObject responseObject;
|
||||
responseObject["attestationObject"] = browserMessageBuilder()->getBase64FromArray(attestationObject);
|
||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromArray(clientDataJson.toUtf8());
|
||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromJson(clientDataJson);
|
||||
responseObject["clientExtensionResults"] = credentialCreationOptions["clientExtensionResults"];
|
||||
|
||||
// Additions for extension side functions
|
||||
@@ -130,8 +130,8 @@ QJsonObject BrowserPasskeys::buildGetPublicKeyCredential(const QJsonObject& asse
|
||||
|
||||
const auto authenticatorData =
|
||||
buildAuthenticatorData(assertionOptions["rpId"].toString(), assertionOptions["extensions"].toString());
|
||||
const auto clientDataJson = assertionOptions["clientDataJson"].toString();
|
||||
const auto clientDataArray = clientDataJson.toUtf8();
|
||||
const auto clientDataJson = assertionOptions["clientDataJson"].toObject();
|
||||
const auto clientDataArray = QJsonDocument(clientDataJson).toJson(QJsonDocument::Compact);
|
||||
|
||||
const auto signature = buildSignature(authenticatorData, clientDataArray, privateKeyPem);
|
||||
if (signature.isEmpty()) {
|
||||
@@ -140,7 +140,7 @@ QJsonObject BrowserPasskeys::buildGetPublicKeyCredential(const QJsonObject& asse
|
||||
|
||||
QJsonObject responseObject;
|
||||
responseObject["authenticatorData"] = browserMessageBuilder()->getBase64FromArray(authenticatorData);
|
||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromArray(clientDataArray);
|
||||
responseObject["clientDataJSON"] = browserMessageBuilder()->getBase64FromJson(clientDataJson);
|
||||
responseObject["clientExtensionResults"] = assertionOptions["clientExtensionResults"];
|
||||
responseObject["signature"] = browserMessageBuilder()->getBase64FromArray(signature);
|
||||
responseObject["userHandle"] = userHandle;
|
||||
|
||||
@@ -330,7 +330,6 @@ QJsonObject BrowserService::createNewGroup(const QString& groupName, bool isPass
|
||||
}
|
||||
#endif
|
||||
name = newGroup->name();
|
||||
newGroup->setCustomDataTriState(BrowserService::OPTION_HIDE_ENTRY, Group::Disable);
|
||||
uuid = Tools::uuidToHex(newGroup->uuid());
|
||||
previousGroup = newGroup;
|
||||
continue;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -53,8 +53,8 @@ bool PasskeyUtils::checkCredentialCreationOptions(const QJsonObject& credentialC
|
||||
{
|
||||
if (!credentialCreationOptions["attestation"].isString()
|
||||
|| credentialCreationOptions["attestation"].toString().isEmpty()
|
||||
|| !credentialCreationOptions["clientDataJSON"].isString()
|
||||
|| credentialCreationOptions["clientDataJSON"].toString().isEmpty()
|
||||
|| !credentialCreationOptions["clientDataJSON"].isObject()
|
||||
|| credentialCreationOptions["clientDataJSON"].toObject().isEmpty()
|
||||
|| !credentialCreationOptions["rp"].isObject() || credentialCreationOptions["rp"].toObject().isEmpty()
|
||||
|| !credentialCreationOptions["user"].isObject() || credentialCreationOptions["user"].toObject().isEmpty()
|
||||
|| !credentialCreationOptions["residentKey"].isBool() || credentialCreationOptions["residentKey"].isUndefined()
|
||||
@@ -75,7 +75,7 @@ bool PasskeyUtils::checkCredentialCreationOptions(const QJsonObject& credentialC
|
||||
// Basic check for the object that it contains necessary variables in a correct form
|
||||
bool PasskeyUtils::checkCredentialAssertionOptions(const QJsonObject& assertionOptions) const
|
||||
{
|
||||
if (!assertionOptions["clientDataJson"].isString() || assertionOptions["clientDataJson"].toString().isEmpty()
|
||||
if (!assertionOptions["clientDataJson"].isObject() || assertionOptions["clientDataJson"].toObject().isEmpty()
|
||||
|| !assertionOptions["rpId"].isString() || assertionOptions["rpId"].toString().isEmpty()
|
||||
|| !assertionOptions["userPresence"].isBool() || assertionOptions["userPresence"].isUndefined()
|
||||
|| !assertionOptions["userVerification"].isBool() || assertionOptions["userVerification"].isUndefined()) {
|
||||
@@ -352,11 +352,15 @@ ExtensionResult PasskeyUtils::buildExtensionData(QJsonObject& extensionObject) c
|
||||
return {};
|
||||
}
|
||||
|
||||
// Serialization order: https://w3c.github.io/webauthn/#clientdatajson-serialization
|
||||
QString PasskeyUtils::buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const
|
||||
QJsonObject PasskeyUtils::buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const
|
||||
{
|
||||
return QString("{\"type\":\"%1\",\"challenge\":\"%2\",\"origin\":\"%3\",\"crossOrigin\":false}")
|
||||
.arg((get ? QString("webauthn.get") : QString("webauthn.create")), publicKey["challenge"].toString(), origin);
|
||||
QJsonObject clientData;
|
||||
clientData["challenge"] = publicKey["challenge"];
|
||||
clientData["crossOrigin"] = false;
|
||||
clientData["origin"] = origin;
|
||||
clientData["type"] = get ? QString("webauthn.get") : QString("webauthn.create");
|
||||
|
||||
return clientData;
|
||||
}
|
||||
|
||||
QStringList PasskeyUtils::getAllowedCredentialsFromAssertionOptions(const QJsonObject& assertionOptions) const
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
bool isUserVerificationRequired(const QJsonObject& authenticatorSelection) const;
|
||||
bool isOriginAllowedWithLocalhost(bool allowLocalhostWithPasskeys, const QString& origin) const;
|
||||
ExtensionResult buildExtensionData(QJsonObject& extensionObject) const;
|
||||
QString buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const;
|
||||
QJsonObject buildClientDataJson(const QJsonObject& publicKey, const QString& origin, bool get) const;
|
||||
QStringList getAllowedCredentialsFromAssertionOptions(const QJsonObject& assertionOptions) const;
|
||||
QString getCredentialIdFromEntry(const Entry* entry) const;
|
||||
QString getUsernameFromEntry(const Entry* entry) const;
|
||||
|
||||
@@ -37,6 +37,12 @@ const QCommandLineOption Clip::TotpOption =
|
||||
QCommandLineOption(QStringList() << "t" << "totp",
|
||||
QObject::tr("Copy the current TOTP to the clipboard (equivalent to \"-a totp\")."));
|
||||
|
||||
const QCommandLineOption Clip::UuidOption =
|
||||
QCommandLineOption(QStringList() << "uuid", QObject::tr("Copy the entry's UUID to the clipboard."));
|
||||
|
||||
const QCommandLineOption Clip::TagsOption =
|
||||
QCommandLineOption(QStringList() << "tags", QObject::tr("Copy the entry's tag list to the clipboard."));
|
||||
|
||||
const QCommandLineOption Clip::BestMatchOption =
|
||||
QCommandLineOption(QStringList() << "b" << "best-match",
|
||||
QObject::tr("Must match only one entry, otherwise a list of possible matches is shown."));
|
||||
@@ -47,6 +53,8 @@ Clip::Clip()
|
||||
description = QObject::tr("Copy an entry's attribute to the clipboard.");
|
||||
options.append(Clip::AttributeOption);
|
||||
options.append(Clip::TotpOption);
|
||||
options.append(Clip::UuidOption);
|
||||
options.append(Clip::TagsOption);
|
||||
options.append(Clip::BestMatchOption);
|
||||
positionalArguments.append(
|
||||
{QString("entry"), QObject::tr("Path of the entry to clip.", "clip = copy to clipboard"), QString("")});
|
||||
@@ -99,8 +107,13 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (parser->isSet(AttributeOption) && parser->isSet(TotpOption)) {
|
||||
err << QObject::tr("ERROR: Please specify one of --attribute or --totp, not both.") << Qt::endl;
|
||||
auto optionCount = parser->isSet(AttributeOption) ? 1 : 0;
|
||||
optionCount += parser->isSet(TotpOption) ? 1 : 0;
|
||||
optionCount += parser->isSet(UuidOption) ? 1 : 0;
|
||||
optionCount += parser->isSet(TagsOption) ? 1 : 0;
|
||||
if (optionCount > 1) {
|
||||
err << QObject::tr("ERROR: Cannot specify multiple options at once (--attribute, --totp, --uuid, --tags).")
|
||||
<< Qt::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
@@ -113,11 +126,16 @@ int Clip::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
selectedAttribute = "totp";
|
||||
found = true;
|
||||
value = entry->totp();
|
||||
} else if (Utils::EntryFieldNames.contains(selectedAttribute)) {
|
||||
value = Utils::getTopLevelField(entry, selectedAttribute);
|
||||
selectedAttribute = "TOTP";
|
||||
found = true;
|
||||
} else if (parser->isSet(UuidOption)) {
|
||||
value = entry->uuid().toString();
|
||||
selectedAttribute = "UUID";
|
||||
found = true;
|
||||
} else if (parser->isSet(TagsOption)) {
|
||||
value = entry->tags();
|
||||
selectedAttribute = "Tags";
|
||||
found = true;
|
||||
} else {
|
||||
QStringList attrs = Utils::findAttributes(*entry->attributes(), selectedAttribute);
|
||||
|
||||
@@ -29,6 +29,8 @@ public:
|
||||
|
||||
static const QCommandLineOption AttributeOption;
|
||||
static const QCommandLineOption TotpOption;
|
||||
static const QCommandLineOption UuidOption;
|
||||
static const QCommandLineOption TagsOption;
|
||||
static const QCommandLineOption BestMatchOption;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,14 +24,21 @@
|
||||
#include <QCommandLineParser>
|
||||
|
||||
const QCommandLineOption Show::TotpOption =
|
||||
QCommandLineOption(QStringList() << "t" << "totp", QObject::tr("Show the entry's current TOTP."));
|
||||
QCommandLineOption(QStringList() << "t" << "totp", QObject::tr("Only show the entry's current TOTP."));
|
||||
|
||||
const QCommandLineOption Show::UuidOption =
|
||||
QCommandLineOption(QStringList() << "uuid", QObject::tr("Show the entry's UUID."));
|
||||
|
||||
const QCommandLineOption Show::TagsOption =
|
||||
QCommandLineOption(QStringList() << "tags", QObject::tr("Show the entry's tags."));
|
||||
|
||||
const QCommandLineOption Show::ProtectedAttributesOption =
|
||||
QCommandLineOption(QStringList() << "s" << "show-protected",
|
||||
QObject::tr("Show the protected attributes in clear text."));
|
||||
|
||||
const QCommandLineOption Show::AllAttributesOption =
|
||||
QCommandLineOption(QStringList() << "all", QObject::tr("Show all the attributes of the entry."));
|
||||
QCommandLineOption(QStringList() << "all",
|
||||
QObject::tr("Show all the attributes of the entry, including UUID and Tags."));
|
||||
|
||||
const QCommandLineOption Show::AttachmentsOption =
|
||||
QCommandLineOption(QStringList() << "show-attachments", QObject::tr("Show the attachments of the entry."));
|
||||
@@ -49,6 +56,8 @@ Show::Show()
|
||||
name = QString("show");
|
||||
description = QObject::tr("Show an entry's information.");
|
||||
options.append(Show::TotpOption);
|
||||
options.append(Show::UuidOption);
|
||||
options.append(Show::TagsOption);
|
||||
options.append(Show::AttributesOption);
|
||||
options.append(Show::ProtectedAttributesOption);
|
||||
options.append(Show::AllAttributesOption);
|
||||
@@ -63,9 +72,10 @@ int Show::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
|
||||
|
||||
const QStringList args = parser->positionalArguments();
|
||||
const QString& entryPath = args.at(1);
|
||||
bool showTotp = parser->isSet(Show::TotpOption);
|
||||
bool showProtectedAttributes = parser->isSet(Show::ProtectedAttributesOption);
|
||||
bool showAllAttributes = parser->isSet(Show::AllAttributesOption);
|
||||
bool showUuid = parser->isSet(Show::UuidOption);
|
||||
bool showTags = parser->isSet(Show::TagsOption);
|
||||
QStringList attributes = parser->values(Show::AttributesOption);
|
||||
|
||||
Entry* entry = database->rootGroup()->findEntryByPath(entryPath);
|
||||
@@ -74,18 +84,23 @@ int Show::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (showTotp && !entry->hasTotp()) {
|
||||
err << QObject::tr("Entry with path %1 has no TOTP set up.").arg(entryPath) << Qt::endl;
|
||||
return EXIT_FAILURE;
|
||||
// Early exit if the user only wants to show the TOTP
|
||||
if (parser->isSet(Show::TotpOption)) {
|
||||
if (!entry->hasTotp()) {
|
||||
err << QObject::tr("Entry with path %1 has no TOTP set up.").arg(entryPath) << Qt::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
out << entry->totp() << Qt::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
bool attributesWereSpecified = true;
|
||||
bool attributesWereSpecified = !showUuid && !showTags;
|
||||
if (showAllAttributes) {
|
||||
attributesWereSpecified = false;
|
||||
showUuid = true;
|
||||
showTags = true;
|
||||
attributes = EntryAttributes::DefaultAttributes;
|
||||
for (QString fieldName : Utils::EntryFieldNames) {
|
||||
attributes.append(fieldName);
|
||||
}
|
||||
// Adding the custom attributes after the default attributes so that
|
||||
// the default attributes are always shown first.
|
||||
for (QString attributeName : entry->attributes()->keys()) {
|
||||
@@ -94,26 +109,16 @@ int Show::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
|
||||
}
|
||||
attributes.append(attributeName);
|
||||
}
|
||||
} else if (attributes.isEmpty() && !showTotp) {
|
||||
} else if (attributes.isEmpty() && !showUuid && !showTags) {
|
||||
// If no attributes are specified, output the default attribute set.
|
||||
attributesWereSpecified = false;
|
||||
attributes = EntryAttributes::DefaultAttributes;
|
||||
for (QString fieldName : Utils::EntryFieldNames) {
|
||||
attributes.append(fieldName);
|
||||
}
|
||||
showTags = true;
|
||||
}
|
||||
|
||||
// Iterate over the attributes and output them line-by-line.
|
||||
bool encounteredError = false;
|
||||
for (const QString& attributeName : asConst(attributes)) {
|
||||
if (Utils::EntryFieldNames.contains(attributeName)) {
|
||||
if (!attributesWereSpecified) {
|
||||
out << attributeName << ": ";
|
||||
}
|
||||
out << Utils::getTopLevelField(entry, attributeName) << Qt::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList attrs = Utils::findAttributes(*entry->attributes(), attributeName);
|
||||
if (attrs.isEmpty()) {
|
||||
encounteredError = true;
|
||||
@@ -137,6 +142,14 @@ int Show::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
|
||||
}
|
||||
}
|
||||
|
||||
// Output UUID and Tags if a certain field wasn't specified
|
||||
if (showTags) {
|
||||
out << "Tags: " << entry->tags() << Qt::endl;
|
||||
}
|
||||
if (showUuid) {
|
||||
out << "UUID: " << entry->uuid().toString() << Qt::endl;
|
||||
}
|
||||
|
||||
if (parser->isSet(Show::AttachmentsOption)) {
|
||||
// Separate attachment output from attributes output via a newline.
|
||||
out << Qt::endl;
|
||||
@@ -156,9 +169,5 @@ int Show::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
|
||||
}
|
||||
}
|
||||
|
||||
if (showTotp) {
|
||||
out << entry->totp() << Qt::endl;
|
||||
}
|
||||
|
||||
return encounteredError ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ public:
|
||||
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser) override;
|
||||
|
||||
static const QCommandLineOption TotpOption;
|
||||
static const QCommandLineOption UuidOption;
|
||||
static const QCommandLineOption TagsOption;
|
||||
static const QCommandLineOption AllAttributesOption;
|
||||
static const QCommandLineOption AttributesOption;
|
||||
static const QCommandLineOption ProtectedAttributesOption;
|
||||
|
||||
@@ -395,17 +395,6 @@ namespace Utils
|
||||
return result;
|
||||
}
|
||||
|
||||
QString getTopLevelField(const Entry* entry, const QString& fieldName)
|
||||
{
|
||||
if (fieldName == UuidFieldName) {
|
||||
return entry->uuid().toString();
|
||||
}
|
||||
if (fieldName == TagsFieldName) {
|
||||
return entry->tags();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
QStringList findAttributes(const EntryAttributes& attributes, const QString& name)
|
||||
{
|
||||
QStringList result;
|
||||
|
||||
@@ -34,10 +34,6 @@ namespace Utils
|
||||
extern QTextStream STDIN;
|
||||
extern QTextStream DEVNULL;
|
||||
|
||||
static const QString UuidFieldName = "Uuid";
|
||||
static const QString TagsFieldName = "Tags";
|
||||
static const QStringList EntryFieldNames(QStringList() << UuidFieldName << TagsFieldName);
|
||||
|
||||
void setDefaultTextStreams();
|
||||
void resetTextStreams();
|
||||
|
||||
@@ -61,10 +57,6 @@ namespace Utils
|
||||
* (case-insensitive).
|
||||
*/
|
||||
QStringList findAttributes(const EntryAttributes& attributes, const QString& name);
|
||||
/**
|
||||
* Get the value of a top-level Entry field using its name.
|
||||
*/
|
||||
QString getTopLevelField(const Entry* entry, const QString& fieldName);
|
||||
}; // namespace Utils
|
||||
|
||||
#endif // KEEPASSXC_UTILS_H
|
||||
|
||||
@@ -334,7 +334,6 @@ QList<QString> Entry::autoTypeSequences(const QString& windowTitle) const
|
||||
};
|
||||
|
||||
QList<QString> sequenceList;
|
||||
QList<QString> emptyWindowSequences; // Store sequences with empty window titles as fallback
|
||||
|
||||
// Add window association matches
|
||||
const auto assocList = autoTypeAssociations()->getAll();
|
||||
@@ -346,13 +345,6 @@ QList<QString> Entry::autoTypeSequences(const QString& windowTitle) const
|
||||
} else {
|
||||
sequenceList << effectiveAutoTypeSequence();
|
||||
}
|
||||
} else if (assoc.window.isEmpty()) {
|
||||
// Store empty window title associations as fallback
|
||||
if (!assoc.sequence.isEmpty()) {
|
||||
emptyWindowSequences << assoc.sequence;
|
||||
} else {
|
||||
emptyWindowSequences << effectiveAutoTypeSequence();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,12 +358,6 @@ QList<QString> Entry::autoTypeSequences(const QString& windowTitle) const
|
||||
sequenceList << effectiveAutoTypeSequence();
|
||||
}
|
||||
|
||||
// If no associations, title, or URL matched, use empty window title associations as fallback
|
||||
// Only use fallback when title matching is enabled to avoid interfering with existing behavior
|
||||
if (sequenceList.isEmpty() && config()->get(Config::AutoTypeEntryTitleMatch).toBool()) {
|
||||
sequenceList << emptyWindowSequences;
|
||||
}
|
||||
|
||||
return sequenceList;
|
||||
}
|
||||
|
||||
|
||||
@@ -221,13 +221,6 @@ bool EntrySearcher::searchEntryImpl(const Entry* entry)
|
||||
}
|
||||
found = false;
|
||||
break;
|
||||
case Field::Has:
|
||||
if (term.word.compare("totp", Qt::CaseInsensitive) == 0) {
|
||||
found = entry->hasTotp();
|
||||
break;
|
||||
}
|
||||
found = false;
|
||||
break;
|
||||
case Field::Uuid:
|
||||
found = term.regex.match(entry->uuidToHex()).hasMatch();
|
||||
break;
|
||||
@@ -267,7 +260,6 @@ void EntrySearcher::parseSearchTerms(const QString& searchString)
|
||||
{QStringLiteral("group"), Field::Group},
|
||||
{QStringLiteral("tag"), Field::Tag},
|
||||
{QStringLiteral("is"), Field::Is},
|
||||
{QStringLiteral("has"), Field::Has},
|
||||
{QStringLiteral("uuid"), Field::Uuid}};
|
||||
|
||||
// Group 1 = modifiers, Group 2 = field, Group 3 = quoted string, Group 4 = unquoted string
|
||||
|
||||
@@ -41,7 +41,6 @@ public:
|
||||
Group,
|
||||
Tag,
|
||||
Is,
|
||||
Has,
|
||||
Uuid
|
||||
};
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ bool PasswordWidget::eventFilter(QObject* watched, QEvent* event)
|
||||
if (isVisible() && (type == QEvent::KeyPress || type == QEvent::KeyRelease || type == QEvent::FocusIn)) {
|
||||
checkCapslockState();
|
||||
}
|
||||
if (type == QEvent::FocusIn || type == QEvent::FocusOut || type == QEvent::Hide) {
|
||||
if (type == QEvent::FocusIn || type == QEvent::FocusOut) {
|
||||
osUtils->setUserInputProtection(type == QEvent::FocusIn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,8 +171,8 @@ bool UrlTools::isUrlValid(const QString& urlField, bool looseComparison) const
|
||||
url.remove(0, 1);
|
||||
url.remove(url.length() - 1, 1);
|
||||
} else {
|
||||
// Do not allow URL with just wildcards, or double wildcards
|
||||
if (url.length() == url.count("*") || url.contains("**") || url.contains("*.*")) {
|
||||
// Do not allow URL with just wildcards, or double wildcards, or no separator (.)
|
||||
if (url.length() == url.count("*") || url.contains("**") || url.contains("*.*") || !url.contains(".")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -152,22 +152,11 @@ bool MacUtils::isCapslockEnabled()
|
||||
|
||||
void MacUtils::setUserInputProtection(bool enable)
|
||||
{
|
||||
static bool secureInputEnabled = false;
|
||||
if (enable) {
|
||||
/*
|
||||
* MacOS keeps a single counter over all apps that needs to be zero to disable secure input. By never going
|
||||
* higher than 1 internally this makes sure secure input doesn't stay active after calling this function
|
||||
* multiple times.
|
||||
*/
|
||||
if (secureInputEnabled) {
|
||||
DisableSecureEventInput();
|
||||
}
|
||||
EnableSecureEventInput();
|
||||
} else {
|
||||
DisableSecureEventInput();
|
||||
}
|
||||
// Store our last known state
|
||||
secureInputEnabled = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,8 +30,7 @@ TagModel::TagModel(QObject* parent)
|
||||
{
|
||||
m_defaultSearches << qMakePair(tr("Clear Search"), QString("")) << qMakePair(tr("All Entries"), QString("*"))
|
||||
<< qMakePair(tr("Expired"), QString("is:expired"))
|
||||
<< qMakePair(tr("Weak Passwords"), QString("is:weak"))
|
||||
<< qMakePair(tr("TOTP Entries"), QString("has:totp"));
|
||||
<< qMakePair(tr("Weak Passwords"), QString("is:weak"));
|
||||
}
|
||||
|
||||
TagModel::~TagModel() = default;
|
||||
|
||||
@@ -125,15 +125,6 @@ void TestAutoType::init()
|
||||
m_entry5->setPassword("example5");
|
||||
m_entry5->setTitle("some title");
|
||||
m_entry5->setUrl("http://example.org");
|
||||
|
||||
// Entry with empty window title (should act as fallback)
|
||||
m_entry6 = new Entry();
|
||||
m_entry6->setGroup(m_group);
|
||||
m_entry6->setPassword("empty_window_test");
|
||||
m_entry6->setTitle("Entry for Empty Window Test");
|
||||
association.window = ""; // Empty window title
|
||||
association.sequence = "empty_window_sequence";
|
||||
m_entry6->autoTypeAssociations()->add(association);
|
||||
}
|
||||
|
||||
void TestAutoType::cleanup()
|
||||
@@ -289,31 +280,6 @@ void TestAutoType::testGlobalAutoTypeRegExp()
|
||||
m_test->clearActions();
|
||||
}
|
||||
|
||||
void TestAutoType::testGlobalAutoTypeEmptyWindow()
|
||||
{
|
||||
// Enable title matching for this test since our fallback logic requires it
|
||||
config()->set(Config::AutoTypeEntryTitleMatch, true);
|
||||
|
||||
// Test that empty window title associations work as fallback when no other associations match
|
||||
// This should use the empty window association from m_entry6 when no specific window matches
|
||||
m_test->setActiveWindowTitle("no_matching_window_title");
|
||||
emit osUtils->globalShortcutTriggered("autotype");
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
QCOMPARE(m_test->actionChars(), QString("empty_window_sequence"));
|
||||
m_test->clearActions();
|
||||
|
||||
// Test that empty window title associations do NOT match when other associations exist and match
|
||||
// This entry has window associations that should take precedence over empty window title
|
||||
m_test->setActiveWindowTitle("custom window"); // This should match m_entry1 association
|
||||
emit osUtils->globalShortcutTriggered("autotype");
|
||||
m_autoType->performGlobalAutoType(m_dbList);
|
||||
QCOMPARE(m_test->actionChars(), QString("myuserassociationmypass")); // Should be from m_entry1, not empty window
|
||||
m_test->clearActions();
|
||||
|
||||
// Reset title matching to default state
|
||||
config()->set(Config::AutoTypeEntryTitleMatch, false);
|
||||
}
|
||||
|
||||
void TestAutoType::testAutoTypeResults()
|
||||
{
|
||||
QScopedPointer<Entry> entry(new Entry());
|
||||
|
||||
@@ -47,7 +47,6 @@ private slots:
|
||||
void testGlobalAutoTypeUrlSubdomainMatch();
|
||||
void testGlobalAutoTypeTitleMatchDisabled();
|
||||
void testGlobalAutoTypeRegExp();
|
||||
void testGlobalAutoTypeEmptyWindow();
|
||||
void testAutoTypeResults();
|
||||
void testAutoTypeResults_data();
|
||||
void testAutoTypeSyntaxChecks();
|
||||
@@ -65,7 +64,6 @@ private:
|
||||
Entry* m_entry3;
|
||||
Entry* m_entry4;
|
||||
Entry* m_entry5;
|
||||
Entry* m_entry6;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTAUTOTYPE_H
|
||||
|
||||
@@ -673,14 +673,14 @@ void TestCli::testClip()
|
||||
|
||||
// Uuid (top-level field)
|
||||
setInput("a");
|
||||
execCmd(clipCmd, {"clip", m_dbFile->fileName(), "/Sample Entry", "0", "-a", "Uuid"});
|
||||
execCmd(clipCmd, {"clip", m_dbFile->fileName(), "/Sample Entry", "0", "--uuid"});
|
||||
QTRY_COMPARE(clipboard->text(), QString("{9f4544c2-ab00-c74a-8a1a-6eaf26cf57e9}"));
|
||||
|
||||
// TOTP
|
||||
setInput("a");
|
||||
execCmd(clipCmd, {"clip", m_dbFile->fileName(), "/Sample Entry", "0", "--totp"});
|
||||
QTRY_VERIFY(isTotp(clipboard->text()));
|
||||
QCOMPARE(m_stdout->readLine(), QByteArray("Entry's \"totp\" attribute copied to the clipboard!\n"));
|
||||
QCOMPARE(m_stdout->readLine(), QByteArray("Entry's \"TOTP\" attribute copied to the clipboard!\n"));
|
||||
|
||||
// Test Unicode
|
||||
setInput("a");
|
||||
@@ -725,7 +725,7 @@ void TestCli::testClip()
|
||||
|
||||
setInput("a");
|
||||
execCmd(clipCmd, {"clip", m_dbFile2->fileName(), "--attribute", "Username", "--totp", "/Sample Entry", "0"});
|
||||
QVERIFY(m_stderr->readAll().contains("ERROR: Please specify one of --attribute or --totp, not both.\n"));
|
||||
QVERIFY(m_stderr->readAll().contains("ERROR: Cannot specify multiple options at once"));
|
||||
|
||||
// Best option
|
||||
setInput("a");
|
||||
@@ -2077,72 +2077,55 @@ void TestCli::testShow()
|
||||
QVERIFY(!showCmd.name.isEmpty());
|
||||
QVERIFY(showCmd.getDescriptionLine().contains(showCmd.name));
|
||||
|
||||
const QByteArray expectTitle("Title: Sample Entry");
|
||||
const QByteArray expectUserName("UserName: User Name");
|
||||
const QByteArray expectUrl("URL: http://www.somesite.com/");
|
||||
const QByteArray expectUuid("UUID: {9f4544c2-ab00-c74a-8a1a-6eaf26cf57e9}");
|
||||
const QByteArray expectNotes("Notes: Notes");
|
||||
const QByteArray expectTags("Tags: ");
|
||||
|
||||
setInput("a");
|
||||
execCmd(showCmd, {"show", m_dbFile->fileName(), "/Sample Entry"});
|
||||
m_stderr->readLine(); // Skip password prompt
|
||||
QCOMPARE(m_stderr->readAll(), QByteArray());
|
||||
QCOMPARE(m_stdout->readAll(),
|
||||
QByteArray("Title: Sample Entry\n"
|
||||
"UserName: User Name\n"
|
||||
"Password: PROTECTED\n"
|
||||
"URL: http://www.somesite.com/\n"
|
||||
"Notes: Notes\n"
|
||||
"Uuid: {9f4544c2-ab00-c74a-8a1a-6eaf26cf57e9}\n"
|
||||
"Tags: \n"));
|
||||
auto out = m_stdout->readAll();
|
||||
QVERIFY(out.contains(expectTitle));
|
||||
QVERIFY(out.contains(expectUserName));
|
||||
QVERIFY(out.contains(expectUrl));
|
||||
QVERIFY(out.contains(expectNotes));
|
||||
QVERIFY(out.contains(expectTags));
|
||||
QVERIFY(!out.contains(expectUuid));
|
||||
QVERIFY(out.contains("Password: PROTECTED"));
|
||||
|
||||
setInput("a");
|
||||
execCmd(showCmd, {"show", "-s", m_dbFile->fileName(), "/Sample Entry"});
|
||||
QCOMPARE(m_stdout->readAll(),
|
||||
QByteArray("Title: Sample Entry\n"
|
||||
"UserName: User Name\n"
|
||||
"Password: Password\n"
|
||||
"URL: http://www.somesite.com/\n"
|
||||
"Notes: Notes\n"
|
||||
"Uuid: {9f4544c2-ab00-c74a-8a1a-6eaf26cf57e9}\n"
|
||||
"Tags: \n"));
|
||||
out = m_stdout->readAll();
|
||||
QVERIFY(out.contains("Password: Password"));
|
||||
|
||||
setInput("a");
|
||||
execCmd(showCmd, {"show", m_dbFile->fileName(), "-q", "/Sample Entry"});
|
||||
QCOMPARE(m_stderr->readAll(), QByteArray());
|
||||
QCOMPARE(m_stdout->readAll(),
|
||||
QByteArray("Title: Sample Entry\n"
|
||||
"UserName: User Name\n"
|
||||
"Password: PROTECTED\n"
|
||||
"URL: http://www.somesite.com/\n"
|
||||
"Notes: Notes\n"
|
||||
"Uuid: {9f4544c2-ab00-c74a-8a1a-6eaf26cf57e9}\n"
|
||||
"Tags: \n"));
|
||||
out = m_stdout->readAll();
|
||||
QVERIFY(out.contains(expectTitle));
|
||||
QVERIFY(out.contains(expectUserName));
|
||||
QVERIFY(out.contains(expectUrl));
|
||||
QVERIFY(out.contains(expectNotes));
|
||||
QVERIFY(out.contains(expectTags));
|
||||
QVERIFY(!out.contains(expectUuid));
|
||||
|
||||
setInput("a");
|
||||
execCmd(showCmd, {"show", m_dbFile->fileName(), "--show-attachments", "/Sample Entry"});
|
||||
m_stderr->readLine(); // Skip password prompt
|
||||
QCOMPARE(m_stderr->readAll(), QByteArray());
|
||||
QCOMPARE(m_stdout->readAll(),
|
||||
QByteArray("Title: Sample Entry\n"
|
||||
"UserName: User Name\n"
|
||||
"Password: PROTECTED\n"
|
||||
"URL: http://www.somesite.com/\n"
|
||||
"Notes: Notes\n"
|
||||
"Uuid: {9f4544c2-ab00-c74a-8a1a-6eaf26cf57e9}\n"
|
||||
"Tags: \n"
|
||||
"\n"
|
||||
"Attachments:\n"
|
||||
" Sample attachment.txt (15 B)\n"));
|
||||
out = m_stdout->readAll();
|
||||
QVERIFY(out.contains("Attachments:\n Sample attachment.txt (15 B)"));
|
||||
|
||||
setInput("a");
|
||||
execCmd(showCmd, {"show", m_dbFile->fileName(), "--show-attachments", "/Homebanking/Subgroup/Subgroup Entry"});
|
||||
m_stderr->readLine(); // Skip password prompt
|
||||
QCOMPARE(m_stderr->readAll(), QByteArray());
|
||||
QCOMPARE(m_stdout->readAll(),
|
||||
QByteArray("Title: Subgroup Entry\n"
|
||||
"UserName: Bank User Name\n"
|
||||
"Password: PROTECTED\n"
|
||||
"URL: https://www.bank.com\n"
|
||||
"Notes: Important note\n"
|
||||
"Uuid: {20b183fd-6878-4506-a50b-06d30792aa10}\n"
|
||||
"Tags: \n"
|
||||
"\n"
|
||||
"No attachments present.\n"));
|
||||
out = m_stdout->readAll();
|
||||
QVERIFY(out.contains("No attachments present."));
|
||||
|
||||
setInput("a");
|
||||
execCmd(showCmd, {"show", "-a", "Title", m_dbFile->fileName(), "/Sample Entry"});
|
||||
@@ -2153,8 +2136,8 @@ void TestCli::testShow()
|
||||
QCOMPARE(m_stdout->readAll(), QByteArray("Password\n"));
|
||||
|
||||
setInput("a");
|
||||
execCmd(showCmd, {"show", "-a", "Uuid", m_dbFile->fileName(), "/Sample Entry"});
|
||||
QCOMPARE(m_stdout->readAll(), QByteArray("{9f4544c2-ab00-c74a-8a1a-6eaf26cf57e9}\n"));
|
||||
execCmd(showCmd, {"show", "--uuid", m_dbFile->fileName(), "/Sample Entry"});
|
||||
QVERIFY(m_stdout->readAll().contains(expectUuid));
|
||||
|
||||
setInput("a");
|
||||
execCmd(showCmd, {"show", "-a", "Title", "-a", "URL", m_dbFile->fileName(), "/Sample Entry"});
|
||||
@@ -2178,9 +2161,9 @@ void TestCli::testShow()
|
||||
execCmd(showCmd, {"show", "-t", m_dbFile->fileName(), "/Sample Entry"});
|
||||
QVERIFY(isTotp(m_stdout->readAll()));
|
||||
|
||||
// TOTP paramter short circuits any other parameter
|
||||
setInput("a");
|
||||
execCmd(showCmd, {"show", "-a", "Title", m_dbFile->fileName(), "--totp", "/Sample Entry"});
|
||||
QCOMPARE(m_stdout->readLine(), QByteArray("Sample Entry\n"));
|
||||
QVERIFY(isTotp(m_stdout->readAll()));
|
||||
|
||||
setInput("a");
|
||||
@@ -2196,18 +2179,15 @@ void TestCli::testShow()
|
||||
|
||||
setInput("a");
|
||||
execCmd(showCmd, {"show", "--all", m_dbFile->fileName(), "/Sample Entry"});
|
||||
QCOMPARE(m_stdout->readAll(),
|
||||
QByteArray("Title: Sample Entry\n"
|
||||
"UserName: User Name\n"
|
||||
"Password: PROTECTED\n"
|
||||
"URL: http://www.somesite.com/\n"
|
||||
"Notes: Notes\n"
|
||||
"Uuid: {9f4544c2-ab00-c74a-8a1a-6eaf26cf57e9}\n"
|
||||
"Tags: \n"
|
||||
"TOTP Seed: PROTECTED\n"
|
||||
"TOTP Settings: 30;6\n"
|
||||
"TestAttribute1: b\n"
|
||||
"testattribute1: a\n"));
|
||||
out = m_stdout->readAll();
|
||||
QVERIFY(out.contains(expectTitle));
|
||||
QVERIFY(out.contains(expectUserName));
|
||||
QVERIFY(out.contains(expectUuid));
|
||||
QVERIFY(out.contains(expectTags));
|
||||
QVERIFY(out.contains("TOTP Seed: PROTECTED"));
|
||||
QVERIFY(out.contains("TOTP Settings: 30;6"));
|
||||
QVERIFY(out.contains("TestAttribute1: b"));
|
||||
QVERIFY(out.contains("testattribute1: a"));
|
||||
}
|
||||
|
||||
void TestCli::testInvalidDbFiles()
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "TestEntrySearcher.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Tools.h"
|
||||
#include "core/Totp.h"
|
||||
|
||||
#include <QTest>
|
||||
|
||||
@@ -395,42 +394,3 @@ void TestEntrySearcher::testUUIDSearch()
|
||||
m_searchResult = m_entrySearcher.search("uuid:" + Tools::uuidToHex(uuid1), m_rootGroup);
|
||||
QCOMPARE(m_searchResult.count(), 1);
|
||||
}
|
||||
|
||||
void TestEntrySearcher::testTotpSearch()
|
||||
{
|
||||
auto entry1 = new Entry();
|
||||
entry1->setGroup(m_rootGroup);
|
||||
entry1->setTitle("Regular Entry");
|
||||
|
||||
auto entry2 = new Entry();
|
||||
entry2->setGroup(m_rootGroup);
|
||||
entry2->setTitle("TOTP Entry");
|
||||
// Set up TOTP on entry2
|
||||
auto totpSettings = Totp::createSettings("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ", 6, 30);
|
||||
entry2->setTotp(totpSettings);
|
||||
|
||||
auto entry3 = new Entry();
|
||||
entry3->setGroup(m_rootGroup);
|
||||
entry3->setTitle("Another TOTP Entry");
|
||||
// Set up TOTP on entry3
|
||||
auto totpSettings2 = Totp::createSettings("MFRGG43UEBUXGIDBKRWXAZLSMUQGG6LQ", 6, 30);
|
||||
entry3->setTotp(totpSettings2);
|
||||
|
||||
// Test searching for TOTP entries
|
||||
m_searchResult = m_entrySearcher.search("has:totp", m_rootGroup);
|
||||
QCOMPARE(m_searchResult.count(), 2);
|
||||
QVERIFY(m_searchResult.contains(entry2));
|
||||
QVERIFY(m_searchResult.contains(entry3));
|
||||
QVERIFY(!m_searchResult.contains(entry1));
|
||||
|
||||
// Test case insensitive search
|
||||
m_searchResult = m_entrySearcher.search("has:TOTP", m_rootGroup);
|
||||
QCOMPARE(m_searchResult.count(), 2);
|
||||
|
||||
// Test excluding TOTP entries
|
||||
m_searchResult = m_entrySearcher.search("!has:totp", m_rootGroup);
|
||||
QCOMPARE(m_searchResult.count(), 1);
|
||||
QVERIFY(m_searchResult.contains(entry1));
|
||||
QVERIFY(!m_searchResult.contains(entry2));
|
||||
QVERIFY(!m_searchResult.contains(entry3));
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ private slots:
|
||||
void testGroup();
|
||||
void testSkipProtected();
|
||||
void testUUIDSearch();
|
||||
void testTotpSearch();
|
||||
|
||||
private:
|
||||
Group* m_rootGroup;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2025 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -440,12 +440,12 @@ void TestPasskeys::testGet()
|
||||
auto response = publicKeyCredential["response"].toObject();
|
||||
QCOMPARE(response["authenticatorData"].toString(), QString("dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvAFAAAAAA"));
|
||||
QCOMPARE(response["clientDataJSON"].toString(),
|
||||
QString("eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiOXozNnZUZlFUTDk1TGY3V25aZ3l0ZTdvaEdlRi1YUmlMeGtML"
|
||||
"Ux1R1Uxem9wUm1NSVVBMUxWd3pHcHlJbTFmT0JuMVFuUmEwUUgyN0FEQWFKR0h5c1EiLCJvcmlnaW4iOiJodHRwczovL3dlYm"
|
||||
"F1dGhuLmlvIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ"));
|
||||
QString("eyJjaGFsbGVuZ2UiOiI5ejM2dlRmUVRMOTVMZjdXblpneXRlN29oR2VGLVhSaUx4a0wtTHVHVTF6b3BSbU1JVUExTFZ3ekdwe"
|
||||
"UltMWZPQm4xUW5SYTBRSDI3QURBYUpHSHlzUSIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdX"
|
||||
"Robi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5nZXQifQ"));
|
||||
QCOMPARE(
|
||||
response["signature"].toString(),
|
||||
QString("MEYCIQCpbDaYJ4b2ofqWBxfRNbH3XCpsyao7Iui5lVuJRU9HIQIhAPl5moNZgJu5zmurkKK_P900Ct6wd3ahVIqCEqTeeRdE"));
|
||||
QString("MEUCIHFv0lOOGGloi_XoH5s3QDSs__8yAp9ZTMEjNiacMpOxAiEA04LAfO6TE7j12XNxd3zHQpn4kZN82jQFPntPiPBSD5c"));
|
||||
|
||||
auto clientDataJson = response["clientDataJSON"].toString();
|
||||
auto clientDataByteArray = browserMessageBuilder()->getArrayFromBase64(clientDataJson);
|
||||
|
||||
@@ -160,7 +160,6 @@ void TestUrlTools::testIsUrlValidWithLooseComparison()
|
||||
urls["https://example.*"] = false;
|
||||
urls["https://*.example.*"] = false;
|
||||
urls["https://example.c*"] = false;
|
||||
urls["https://myowndomain:8000"] = true;
|
||||
|
||||
QHashIterator<QString, bool> i(urls);
|
||||
while (i.hasNext()) {
|
||||
|
||||
@@ -9,14 +9,14 @@ KEEPASSXC=$(which -a keepassxc | sed -e "\\,$0,d" -e 'q')
|
||||
|
||||
daemon_main() {
|
||||
# open kdewallet
|
||||
handle=$(qdbus6 org.kde.kwalletd6 /modules/kwalletd6 open kdewallet 0 "$PROG")
|
||||
while [[ true != $(qdbus6 org.kde.kwalletd6 /modules/kwalletd6 isOpen kdewallet) ]]; do
|
||||
handle=$(qdbus org.kde.kwalletd5 /modules/kwalletd5 org.kde.KWallet.open kdewallet 0 "$PROG")
|
||||
while [[ true != $(qdbus org.kde.kwalletd5 /modules/kwalletd5 org.kde.KWallet.isOpen kdewallet) ]]; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
declare -A DBs
|
||||
for DBPATH in $(ls -r $KDBX_SEARCH); do
|
||||
DBs[$(realpath $DBPATH)]=$(qdbus6 org.kde.kwalletd6 /modules/kwalletd6 readPassword "$handle" "Passwords" "${DBPATH##*/}" "$PROG")
|
||||
DBs[$(realpath $DBPATH)]=$(qdbus org.kde.kwalletd5 /modules/kwalletd5 org.kde.KWallet.readPassword "$handle" "Passwords" "${DBPATH##*/}" "$PROG")
|
||||
done
|
||||
|
||||
# launch real keepassxc
|
||||
@@ -24,7 +24,7 @@ daemon_main() {
|
||||
"$KEEPASSXC" --pw-stdin "${!DBs[@]}" <<<"${DBs[*]}" &
|
||||
|
||||
# done with kdewallet
|
||||
qdbus6 org.kde.kwalletd6 /modules/kwalletd6 close "$handle" "false" "$PROG"
|
||||
qdbus org.kde.kwalletd5 /modules/kwalletd5 org.kde.KWallet.close "$handle" "false" "$PROG"
|
||||
}
|
||||
|
||||
if [[ $1 == '-d' ]]; then
|
||||
|
||||
Reference in New Issue
Block a user