QtPass Developer documentation
QtPass test coverage
Current view: top level - src/configdialog.h (source / functions) Coverage Total Hit
Test: QtPass Lines: 100.0 % 5 5
Test Date: 2026-11-14 13:19:18 Functions: 100.0 % 1 1

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : /**
       4              :  * @file
       5              :  * @brief Settings dialog: every page of the configuration window and the
       6              :  * profile table, with the validation that gates OK.
       7              :  */
       8              : #ifndef SRC_CONFIGDIALOG_H_
       9              : #define SRC_CONFIGDIALOG_H_
      10              : 
      11              : #include "appsettings.h"
      12              : #include "enums.h"
      13              : #include "passwordconfiguration.h"
      14              : #include "profile.h"
      15              : 
      16              : #include <QDialog>
      17              : #include <QHash>
      18              : #include <QProcessEnvironment>
      19              : #include <QSet>
      20              : #include <optional>
      21              : 
      22              : namespace Ui {
      23              : struct UserInfo;
      24              : 
      25              : class ConfigDialog;
      26              : } // namespace Ui
      27              : 
      28              : class QLabel;
      29              : 
      30              : /**
      31              :  * @class ConfigDialog
      32              :  * @ingroup ui
      33              :  * @brief The ConfigDialog handles the configuration interface.
      34              :  *
      35              :  * Presents controls for profiles, generation tools (pwgen/pass),
      36              :  * GPG/git/OTP/qrencode integration, template handling, tray icon and
      37              :  * autoclear options, and exposes methods to read and update those settings.
      38              :  */
      39              : class ConfigDialog : public QDialog {
      40          454 :   Q_OBJECT
      41              : 
      42              : public:
      43              :   /**
      44              :    * @brief Construct a ConfigDialog associated with the given main window.
      45              :    * @param parent Parent widget, usually the main window.
      46              :    */
      47              :   explicit ConfigDialog(QWidget *parent);
      48              :   ~ConfigDialog() override;
      49              : 
      50              :   /**
      51              :    * @brief Whether a signing-key setting holds only full key fingerprints
      52              :    * (40 or 64 hexadecimal characters each, space separated); empty passes.
      53              :    * @param setting The profile's signing key field.
      54              :    * @return true when every token is a fingerprint.
      55              :    */
      56              :   static auto isFingerprintList(const QString &setting) -> bool;
      57              : 
      58              :   /**
      59              :    * @brief Enable or disable clipboard selection mode.
      60              :    * @param useSelection true to enable selection mode.
      61              :    */
      62              :   void useSelection(bool useSelection);
      63              : 
      64              :   /**
      65              :    * @brief Return all configured profiles.
      66              :    * @return Hash of profile name to key-value settings map.
      67              :    */
      68              :   auto getProfiles() -> Profiles;
      69              : 
      70              :   /**
      71              :    * @brief Show or hide the system tray icon.
      72              :    * @param useSystray true to enable the tray icon.
      73              :    */
      74              :   void useTrayIcon(bool useSystray);
      75              : 
      76              :   /**
      77              :    * @brief Enable or disable git integration.
      78              :    * @param useGit true to enable git.
      79              :    */
      80              :   void useGit(bool useGit);
      81              : 
      82              :   /**
      83              :    * @brief Enable or disable OTP support.
      84              :    * @param useOtp true to enable OTP.
      85              :    */
      86              :   void useOtp(bool useOtp);
      87              : 
      88              :   /**
      89              :    * @brief Enable or disable grep content search.
      90              :    * @param useGrepSearch true to enable grep search.
      91              :    */
      92              :   void useGrepSearch(bool useGrepSearch);
      93              : 
      94              :   /**
      95              :    * @brief Enable or disable QR code display.
      96              :    * @param useQrencode true to enable qrencode.
      97              :    */
      98              :   void useQrencode(bool useQrencode);
      99              : 
     100              :   /**
     101              :    * @brief Set the path to the pwgen executable.
     102              :    * @param path Path to pwgen.
     103              :    */
     104              :   void setPwgenPath(const QString &path);
     105              : 
     106              :   /**
     107              :    * @brief Enable or disable pwgen-based password generation.
     108              :    * @param usePwgen true to use pwgen.
     109              :    */
     110              :   void usePwgen(bool usePwgen);
     111              : 
     112              :   /**
     113              :    * @brief Apply a password configuration to the dialog controls.
     114              :    * @param config Configuration to display.
     115              :    */
     116              :   void setPasswordConfiguration(const PasswordConfiguration &config);
     117              : 
     118              :   /**
     119              :    * @brief Read the current password configuration from the dialog controls.
     120              :    * @return Current PasswordConfiguration.
     121              :    */
     122              :   auto getPasswordConfiguration() -> PasswordConfiguration;
     123              : 
     124              : protected:
     125              :   /**
     126              :    * @brief Forget the signing keys gpg did not know while the dialog is in
     127              :    * the background, and check them again once it is back in front.
     128              :    * @param event The change; only activation changes matter here.
     129              :    */
     130              :   void changeEvent(QEvent *event) override;
     131              : 
     132              : private slots:
     133              :   /**
     134              :    * @brief On OK: warn about a dubious SSH_AUTH_SOCK override, save the
     135              :    * settings and the profiles, let the active profile follow a rename, and
     136              :    * create and initialise the stores of profiles added in this session.
     137              :    */
     138              :   void on_accepted();
     139              :   /**
     140              :    * @brief Fill the pass, gpg (gpg2 first), git and pwgen paths from PATH,
     141              :    * keeping a field as it is when nothing is found, and choose the pass
     142              :    * backend when pass was found.
     143              :    */
     144              :   void on_autodetectButton_clicked();
     145              :   /// Native (gpg/git) backend chosen: enable its group box, disable pass's.
     146              :   void on_radioButtonNative_clicked();
     147              :   /// pass backend chosen: enable its group box, disable the native one.
     148              :   void on_radioButtonPass_clicked();
     149              :   /**
     150              :    * @brief Pick the git executable in a file dialog; a cancelled dialog
     151              :    * turns Git off and disables its checkbox, as a missing git does.
     152              :    */
     153              :   void on_toolButtonGit_clicked();
     154              :   /// Pick the gpg executable in a file dialog; cancel keeps the field.
     155              :   void on_toolButtonGpg_clicked();
     156              :   /// Open KeygenDialog for the gpg shown in the dialog.
     157              :   void on_pushButtonGenerateKey_clicked();
     158              :   /**
     159              :    * @brief Pick the pwgen executable; a choice enables "use pwgen", a
     160              :    * cancelled dialog unticks and disables it.
     161              :    */
     162              :   void on_toolButtonPwgen_clicked();
     163              :   /// Pick the pass executable in a file dialog; cancel keeps the field.
     164              :   void on_toolButtonPass_clicked();
     165              :   /// Pick the password store folder for the pass backend's store path.
     166              :   void on_toolButtonStore_clicked();
     167              :   /**
     168              :    * @brief Enable the controls that only mean something with a clipboard
     169              :    * (selection, hiding, autoclear) unless "No clipboard" is chosen.
     170              :    * @param index The combo box row; 0 is "No clipboard".
     171              :    */
     172              :   void on_comboBoxClipboard_activated(int index);
     173              :   /**
     174              :    * @brief Show the chosen character set's characters in the line edit,
     175              :    * editable only for the custom set.
     176              :    * @param index A PasswordConfiguration::CharacterSet value.
     177              :    */
     178              :   void on_passwordCharTemplateSelector_activated(int index);
     179              :   /// Re-apply the clipboard gating for the current clipboard mode.
     180              :   void on_checkBoxSelection_clicked();
     181              :   /**
     182              :    * @brief Append a "New profile" row pointing at the global store path,
     183              :    * select it and put the cursor in the name field.
     184              :    */
     185              :   void on_addButton_clicked();
     186              :   /**
     187              :    * @brief Remove the selected profile row (a message when none is); the
     188              :    * list's currentRowChanged re-points the form at a neighbour.
     189              :    */
     190              :   void on_deleteButton_clicked();
     191              :   /// Pick the selected profile's store folder and store it in its entry.
     192              :   void on_profilePathBrowse_clicked();
     193              :   /// Hide-on-close and start-minimised are only offered with a tray icon.
     194              :   void on_checkBoxUseTrayIcon_clicked();
     195              :   /**
     196              :    * @brief Enable the Git-dependent boxes (add .gpg-id, auto pull/push, and
     197              :    * in native mode the parent-repository switch) with "use Git".
     198              :    */
     199              :   void on_checkBoxUseGit_clicked();
     200              :   /**
     201              :    * @brief Enable pwgen's flags or the character-set controls, whichever
     202              :    * the "use pwgen" box selects; nothing in pass mode, where pass generates.
     203              :    */
     204              :   void on_checkBoxUsePwgen_clicked();
     205              :   /**
     206              :    * @brief The profile list's current row changed: show that profile.
     207              :    * @param row The new row, -1 for none.
     208              :    */
     209              :   void onProfileSelected(int row);
     210              :   /**
     211              :    * @brief The name field was typed in: store it in the entry and the list
     212              :    * item, then validate (duplicates, empty).
     213              :    * @param name The text as typed.
     214              :    */
     215              :   void onProfileNameEdited(const QString &name);
     216              :   /**
     217              :    * @brief The path field was typed in: store it in the entry, validate and
     218              :    * refresh the status line.
     219              :    * @param path The text as typed.
     220              :    */
     221              :   void onProfilePathEdited(const QString &path);
     222              :   /**
     223              :    * @brief The signing-key field was typed in: store it in the entry and
     224              :    * validate (fingerprints only, shared keys).
     225              :    * @param key The text as typed.
     226              :    */
     227              :   void onProfileSigningKeyEdited(const QString &key);
     228              :   /**
     229              :    * @brief One of the profile's Git boxes was clicked: all three flags
     230              :    * become the profile's own (no longer following the globals), and push
     231              :    * and pull are enabled only with Git.
     232              :    */
     233              :   void onProfileGitToggled();
     234              : 
     235              : private:
     236              :   /// The secret-fields and every-field checkboxes only while passwords are
     237              :   /// hidden; the secret words and "Default" only while secret fields are
     238              :   /// hidden and every field is not.
     239              :   void updateSecretFieldsEnabled();
     240              :   /**
     241              :    * @brief One row of the profile list while the dialog is open. A QList
     242              :    * rather than the Profiles map so a profile can pass through an empty or
     243              :    * duplicate name while it is being typed; getProfiles() builds the map.
     244              :    */
     245              :   struct ProfileEntry {
     246              :     QString name;    ///< The name as typed; may be empty or a duplicate.
     247              :     Profile profile; ///< Path, signing key and Git flags as edited.
     248              :     /// The name the profile had when the dialog opened (empty for one added
     249              :     /// here), so a rename of the active profile can follow it.
     250              :     QString originalName;
     251              :   };
     252              :   /// What is wrong with one profile's fields; empty strings where nothing is.
     253              :   struct ProfileProblems {
     254              :     QString name; ///< Empty or duplicate name; empty when fine.
     255              :     QString path; ///< Missing store path; empty when fine.
     256              :     QString key;  ///< Signing key that is no fingerprint list; empty when fine.
     257              :     /// Not a problem, a warning: other profiles sign with the same key.
     258              :     /// It neither blocks OK nor marks the row.
     259              :     QString sharedKey;
     260              :     /**
     261              :      * @brief Whether all three fields are fine.
     262              :      * @return true when name, path and key have no complaint.
     263              :      */
     264              :     [[nodiscard]] auto none() const -> bool {
     265          395 :       return name.isEmpty() && path.isEmpty() && key.isEmpty();
     266              :     }
     267              :     /**
     268              :      * @brief The first complaint, for a tooltip on the profile's row.
     269              :      * @return The name, path or key complaint, in that order; empty if none.
     270              :      */
     271            7 :     [[nodiscard]] auto first() const -> QString {
     272            7 :       return !name.isEmpty() ? name : !path.isEmpty() ? path : key;
     273              :     }
     274              :   };
     275              :   /// Designer tooltip of the profile-name field, put back when validate()
     276              :   /// has no complaint about it.
     277              :   QString m_nameTip;
     278              :   /// Designer tooltip of the store-path field, put back when validate() has
     279              :   /// no complaint about it.
     280              :   QString m_pathTip;
     281              :   /// Designer tooltip of the signing-key field, put back when validate() has
     282              :   /// no complaint about it.
     283              :   QString m_keyTip;
     284              :   /// Below the signing key: the profiles that share it, if any.
     285              :   QLabel *m_sharedKeyWarning = nullptr;
     286              :   /// In the pass box: the stores pass would commit outside of, if any.
     287              :   QLabel *m_passGitWarning = nullptr;
     288              :   /// Primary fingerprint per gpg and signing-key token (empty: gpg does
     289              :   /// not know it), so each key is looked up once, not on every keystroke.
     290              :   mutable QHash<QString, QString> m_primaryOf;
     291              :   /// An unknown key was forgotten in the background: validate() again on
     292              :   /// return.
     293              :   bool m_recheckSigners = false;
     294              :   /// The settings naming the GPG home those lookups run in, read on first
     295              :   /// use.
     296              :   mutable std::optional<AppSettings> m_gpgSettings;
     297              :   /// The profiles as shown; index == row in profileList.
     298              :   QList<ProfileEntry> m_entries;
     299              :   /// Row whose fields the form shows, -1 for none.
     300              :   int m_currentEntry = -1;
     301              :   /// True while loadProfileForm() sets the fields, so their edited-signals
     302              :   /// do not write back.
     303              :   bool m_loadingForm = false;
     304              : 
     305              :   /**
     306              :    * @brief Show row @p row's profile in the form (or clear and disable it
     307              :    * for -1), with m_loadingForm set so the fields' edited-signals do not
     308              :    * write back. The Git boxes show the profile's own flags, else the
     309              :    * global ones.
     310              :    * @param row Row in m_entries, or -1 for none.
     311              :    */
     312              :   void loadProfileForm(int row);
     313              :   /**
     314              :    * @brief The entry the form is editing.
     315              :    * @return A pointer into m_entries, or nullptr when no row is current.
     316              :    */
     317              :   auto currentEntry() -> ProfileEntry *;
     318              :   /**
     319              :    * @brief Refresh the status line under the form: the profile's name and
     320              :    * cleaned path, marked "New" while the folder does not exist, or a
     321              :    * reminder to fill in the required fields.
     322              :    */
     323              :   void updateProfileStatus();
     324              :   /// The widgets from configdialog.ui.
     325              :   QScopedPointer<Ui::ConfigDialog> ui;
     326              : 
     327              :   /**
     328              :    * @brief Show @p path as the git executable; an empty path disables and
     329              :    * turns off "use Git".
     330              :    * @param path The git executable, or empty for none.
     331              :    */
     332              :   void setGitPath(const QString &path);
     333              :   /**
     334              :    * @brief Fill the profile list and m_entries from @p profiles, dropping
     335              :    * the empty-named leftover older versions wrote, select
     336              :    * @p currentProfile and validate.
     337              :    * @param profiles The saved profiles.
     338              :    * @param currentProfile The active profile's name, to select its row.
     339              :    */
     340              :   void setProfiles(Profiles profiles, const QString &currentProfile);
     341              :   /**
     342              :    * @brief Tick the pass or the native radio button and enable the matching
     343              :    * group box.
     344              :    * @param usePass true for the pass backend.
     345              :    */
     346              :   void usePass(bool usePass);
     347              : 
     348              :   /**
     349              :    * @brief Load the dialog's value widgets from an AppSettings struct.
     350              :    *
     351              :    * Mirrors the legacy per-getter constructor loads. Sets values only; the
     352              :    * constructor still handles environment-dependent gating (tray/OTP/qrencode
     353              :    * availability, clipboard combo population, primary-selection support) and
     354              :    * the profile table. Password-generation and pwgen-path widgets are
     355              :    * intentionally not loaded here, matching the previous behaviour.
     356              :    * @param settings Values to display.
     357              :    */
     358              :   void applySettings(const AppSettings &settings);
     359              :   /**
     360              :    * @brief Build an AppSettings struct from the dialog's widgets.
     361              :    *
     362              :    * Starts from the currently persisted settings and overwrites only the
     363              :    * fields this dialog owns, so unrelated keys (window geometry,
     364              :    * profiles, etc.) are preserved on save.
     365              :    * @return Settings reflecting the dialog state.
     366              :    */
     367              :   auto readSettings() -> AppSettings;
     368              : 
     369              :   /**
     370              :    * @brief Enable the group box of the chosen backend and, in pass mode,
     371              :    * disable the password-generation controls pass takes care of; in native
     372              :    * mode hand them back to on_checkBoxUsePwgen_clicked().
     373              :    */
     374              :   void setGroupBoxState();
     375              :   /**
     376              :    * @brief Ask for an existing file in a read-only file dialog.
     377              :    * @return The chosen file, empty when cancelled.
     378              :    */
     379              :   auto selectExecutable() -> QString;
     380              :   /**
     381              :    * @brief Ask for a directory in a directories-only file dialog.
     382              :    * @return The chosen folder, empty when cancelled.
     383              :    */
     384              :   auto selectFolder() -> QString;
     385              :   /**
     386              :    * @brief QMessageBox::critical with a single OK button. Once a workaround
     387              :    * for crashes in Qt 5.4.1 before QApplication::exec(); no caller today.
     388              :    * @param title The box's title.
     389              :    * @param text The message.
     390              :    */
     391              :   void criticalMessage(const QString &title, const QString &text);
     392              : 
     393              :   /**
     394              :    * @brief Whether qrencode can run: @p configuredPath when it is an
     395              :    * executable file, else a `qrencode` on Util's PATH, which is then saved
     396              :    * as the qrencode executable. Always false on Windows.
     397              :    * @param configuredPath The saved qrencode executable, possibly empty.
     398              :    * @return true when QR codes can be shown.
     399              :    */
     400              :   auto isQrencodeAvailable(const QString &configuredPath) -> bool;
     401              :   /**
     402              :    * @brief Check every profile (problemsOf(), sharingTheKey()), mark the
     403              :    * rows and the form, enable OK only when no profile has a problem, and
     404              :    * refresh the pass Git warning.
     405              :    */
     406              :   void validate();
     407              :   /// Shows m_passGitWarning for the stores Util::repositoryPassClimbsTo()
     408              :   /// names, while the pass backend is chosen.
     409              :   void updatePassGitWarning();
     410              :   /**
     411              :    * @brief What is wrong with @p entry's name, store path and signing keys.
     412              :    * @param entry The profile to check.
     413              :    * @param names How many profiles carry each name, for duplicates.
     414              :    * @return The complaints, empty where a field is fine.
     415              :    */
     416              :   auto problemsOf(const ProfileEntry &entry,
     417              :                   const QHash<QString, int> &names) const -> ProfileProblems;
     418              :   /**
     419              :    * @brief Mark row @p row of the profile list, and the form fields when it
     420              :    * is the row being edited, with @p problems.
     421              :    * @param row Row in the profile list.
     422              :    * @param problems What problemsOf() found for it.
     423              :    */
     424              :   void showProblems(int row, const ProfileProblems &problems);
     425              :   /// Per profile row: its store as the file system resolves it, and its
     426              :   /// signing-key fingerprints (upper case).
     427          201 :   struct SigningStores {
     428              :     QStringList stores;        ///< Canonical (or cleaned) store path.
     429              :     QList<QSet<QString>> keys; ///< Fingerprints of the signing key.
     430              :   };
     431              :   /**
     432              :    * @brief Resolve every profile's store and fingerprints once.
     433              :    * @return One entry per row of m_entries.
     434              :    */
     435              :   [[nodiscard]] auto signingStores() const -> SigningStores;
     436              :   /**
     437              :    * @brief One profile's signing keys as signers: each key as written, plus
     438              :    * the primary fingerprint gpg gives for a complete key ID or (sub)key
     439              :    * fingerprint.
     440              :    * @param signingKey The profile's signing-key setting.
     441              :    * @param env The environment gpg looks keys up in (its GPG home).
     442              :    * @return Upper-case fingerprints and key IDs.
     443              :    */
     444              :   [[nodiscard]] auto signerKeys(const QString &signingKey,
     445              :                                 const QProcessEnvironment &env) const
     446              :       -> QSet<QString>;
     447              :   /**
     448              :    * @brief The other profiles, for other stores, whose signing keys share a
     449              :    * fingerprint with row @p row's.
     450              :    * @param row The profile's row.
     451              :    * @param resolved What signingStores() gave for this validation.
     452              :    * @return Their names, sorted; empty when none or no key is set.
     453              :    */
     454              :   [[nodiscard]] auto sharingTheKey(int row, const SigningStores &resolved) const
     455              :       -> QStringList;
     456              : 
     457              :   /**
     458              :    * @brief For every saved profile whose store was not a profile's store
     459              :    * when the dialog opened: offer to create the folder and, when it has no
     460              :    * `.gpg-id`, ask for recipients and initialise it.
     461              :    * @param existingProfiles The profiles as they were when the dialog opened.
     462              :    */
     463              :   void initializeNewProfiles(const Profiles &existingProfiles);
     464              :   /**
     465              :    * @brief Offer to create a new profile's store folder when it is missing.
     466              :    * @param path The folder, cleaned.
     467              :    * @return Whether the folder exists now.
     468              :    */
     469              :   auto ensureProfileFolder(const QString &path) -> bool;
     470              :   /**
     471              :    * @brief Ask for a new profile's recipients and initialise its store with
     472              :    * them, keeping the active backend's settings and queue on the active
     473              :    * store (#1774).
     474              :    * @param name The profile's name, for titles and messages.
     475              :    * @param profile The profile.
     476              :    * @param path Its store folder, cleaned.
     477              :    */
     478              :   void initialiseProfileStore(const QString &name, const Profile &profile,
     479              :                               const QString &path);
     480              :   /// Warn, without refusing, when the SSH_AUTH_SOCK override does not look
     481              :   /// like a socket; the value is saved as entered either way.
     482              :   void warnAboutSshAuthSockOverride();
     483              :   /**
     484              :    * @brief Point the active profile at its new name after a rename (or at
     485              :    * none after a deletion), and let its own Git flags replace the global
     486              :    * ones, as switching to it does.
     487              :    * @param profiles The profiles as saved.
     488              :    */
     489              :   void followActiveProfile(const Profiles &profiles);
     490              : 
     491              :   /// User-defined custom charset, retained while a builtin set is selected so
     492              :   /// it is not lost when the line edit shows the builtin's characters instead.
     493              :   QString m_customPasswordChars;
     494              : };
     495              : 
     496              : #endif // SRC_CONFIGDIALOG_H_
        

Generated by: LCOV version 2.0-1