QtPass Developer documentation
QtPass test coverage
Current view: top level - src/realpass.h Coverage Total Hit
Test: QtPass Lines: 100.0 % 1 1
Test Date: 2026-11-14 13:19:18 Functions: - 0 0

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2016 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : /**
       4              :  * @file
       5              :  * @brief RealPass, the backend that hands every store operation to the `pass`
       6              :  * command-line tool.
       7              :  */
       8              : #ifndef SRC_REALPASS_H_
       9              : #define SRC_REALPASS_H_
      10              : 
      11              : #include "nativegrep.h"
      12              : #include "pass.h"
      13              : 
      14              : class GpgIdSigner;
      15              : #include <QQueue>
      16              : #include <QSet>
      17              : 
      18              : /**
      19              :  * @class RealPass
      20              :  * @ingroup backend
      21              :  * @brief Implementation of Pass that wraps the 'pass' command-line tool.
      22              :  *
      23              :  * RealPass delegates all password store operations to the external 'pass'
      24              :  * utility. It provides a Qt-native interface while handling:
      25              :  * - Git integration (init, pull, push)
      26              :  * - Password CRUD (show, insert, remove, move, copy)
      27              :  * - OTP generation
      28              :  * - Store initialization with GPG keys
      29              :  *
      30              :  * This is the primary implementation when 'pass' is available on the system.
      31              :  */
      32              : class RealPass : public Pass {
      33              :   /// The store's own search: `pass grep` runs `find -L`, which follows a
      34              :   /// link out of the store and decrypts what it finds there.
      35              :   NativeGrep m_grep;
      36              :   /// A linked folder whose removal commit is queued, with the store it
      37              :   /// was queued for: the profile may change before the commit ends.
      38              :   struct LinkCommit {
      39              :     QString rel;            ///< The folder, relative to the store.
      40              :     QString passExecutable; ///< The pass that runs the commit.
      41              :     /// Its environment (PASSWORD_STORE_DIR). Not "environ": a macro on
      42              :     /// Windows (stdlib.h).
      43              :     QProcessEnvironment environment;
      44              :   };
      45              :   /// Queued linked-folder removal commits (as GIT_COMMIT), oldest first;
      46              :   /// finished() judges each.
      47              :   QQueue<LinkCommit> m_linkCommits;
      48              : 
      49              :   /**
      50              :    * @brief Judge a queued linked-folder removal commit by what is left
      51              :    * staged in the store it ran in: `pass git` exits 0 whatever git did.
      52              :    * @param commit The folder and the store it was queued for.
      53              :    * @param out The commit's standard output.
      54              :    * @param err The commit's standard error.
      55              :    */
      56              :   void finishLinkCommit(const LinkCommit &commit, const QString &out,
      57              :                         const QString &err);
      58              : 
      59              :   /**
      60              :    * @brief Execute pass command with arguments.
      61              :    * @param id Process identifier.
      62              :    * @param args Command arguments.
      63              :    * @param input Input to pass to stdin.
      64              :    * @param readStdout Capture stdout.
      65              :    * @param readStderr Capture stderr.
      66              :    */
      67              :   void executePass(PROCESS id, const QStringList &args,
      68              :                    QString input = QString(), bool readStdout = true,
      69              :                    bool readStderr = true);
      70              :   /**
      71              :    * @brief Shared implementation of Move and Copy via `pass mv`/`pass cp`.
      72              :    * @param id Process id (PASS_MOVE or PASS_COPY).
      73              :    * @param subcommand The pass subcommand, "mv" or "cp".
      74              :    * @param src Source path.
      75              :    * @param dest Destination path.
      76              :    * @param force Overwrite an existing destination; without it a
      77              :    *        file-onto-file request is a no-op (pass would otherwise force).
      78              :    */
      79              :   void passMoveOrCopy(PROCESS id, const QString &subcommand, const QString &src,
      80              :                       const QString &dest, bool force);
      81              :   /**
      82              :    * @brief @p path as `pass` names it: relative to the store, without the
      83              :    * `.gpg` pass appends itself.
      84              :    * @param path The file or folder, absolute or relative to the working
      85              :    *        directory.
      86              :    * @param stripGpg Whether to drop a trailing `.gpg`.
      87              :    * @return The store-relative name.
      88              :    */
      89              :   auto passName(const QString &path, bool stripGpg) const -> QString;
      90              : 
      91              :   /**
      92              :    * @brief Whether the failed `pass insert` wrote its entry before its git
      93              :    * step failed. pass does not say, so: the entry's bytes differ from what
      94              :    * they were when that insert started to run (a new entry appeared, or an
      95              :    * existing one was rewritten), and pass did not say that the encryption
      96              :    * broke off (gpg can leave a partial file behind then).
      97              :    * @param err What pass reported on stderr.
      98              :    * @return true when the entry was written.
      99              :    */
     100              :   [[nodiscard]] auto insertWritten(const QString &err) const -> bool override;
     101              : 
     102              :   /**
     103              :    * @brief With signing on, refuse to have pass encrypt @p file when the
     104              :    * signed list it would use is not one QtPass accepts: its folder's own
     105              :    * list was passed over, the signature does not verify, or the list is
     106              :    * older than one this device accepted or signed for another folder.
     107              :    * @param file The entry (or the place a move or copy writes to).
     108              :    * @return Whether it was refused (and the user told).
     109              :    */
     110              :   auto refuseUnverifiedList(const QString &file) -> bool;
     111              : 
     112              :   /**
     113              :    * @brief Run a git command in the store through `pass git`, blocking.
     114              :    * @param args The git arguments (without `git`).
     115              :    * @param out Receives stdout; may be null.
     116              :    * @return true when `pass git` ran and exited 0 (git on).
     117              :    */
     118              :   auto storeGit(const QStringList &args, QString *out) -> bool override;
     119              : 
     120              :   /**
     121              :    * @brief For a folder pass is about to re-encrypt in a move or copy:
     122              :    * refuse when the signed list of one of its entries (every one pass's
     123              :    * walk takes, hidden ones included) is not one QtPass accepts where it
     124              :    * lies now (as refuseUnverifiedList()).
     125              :    * @param dir The folder moved or copied, or the existing folder written
     126              :    *        into. Each of its folders' lists counts, used by an entry yet or
     127              :    *        not: a copy merges into a folder of the same name, so lists on
     128              :    *        either side meet the other side's entries.
     129              :    * @param onlyListsWithin Whether only lists inside @p dir count: for the
     130              :    *        folder that moves, whose entries get the destination's list in
     131              :    *        place of any list above it.
     132              :    * @param except A list about to be written (`pass init`'s own), neither
     133              :    *        verified nor refused for being missing.
     134              :    * @return Whether it was refused (and the user told).
     135              :    */
     136              :   auto refuseUnverifiedTree(const QString &dir, bool onlyListsWithin,
     137              :                             const QString &except = QString()) -> bool;
     138              : 
     139              :   /**
     140              :    * @brief The verifier for signed lists, gpg in the store's environment.
     141              :    * @return A GpgIdSigner with the configured signing keys.
     142              :    */
     143              :   auto listSigner() -> GpgIdSigner;
     144              : 
     145              :   /**
     146              :    * @brief The checks of refuseUnverifiedList() for @p file's nearest list,
     147              :    * once per list.
     148              :    * @param file The entry.
     149              :    * @param signer The verifier.
     150              :    * @param verified The lists already found good; extended.
     151              :    * @param within When set, only a list under this folder (absolute,
     152              :    *        cleaned, with a trailing "/") is verified; the skipped-list check
     153              :    *        applies regardless.
     154              :    * @param except A list about to be written, neither verified nor refused
     155              :    *        for being missing.
     156              :    * @return Whether it was refused (and the user told).
     157              :    */
     158              :   auto refuseList(const QString &file, const GpgIdSigner &signer,
     159              :                   QSet<QString> &verified, const QString &within = QString(),
     160              :                   const QString &except = QString()) -> bool;
     161              : 
     162              :   /**
     163              :    * @brief The executor is about to run (or drop) its next item: for an
     164              :    * insert, take its entry from m_insertQueue and fingerprint it now, not
     165              :    * when it was queued, so an earlier queued save of the same entry counts
     166              :    * as "before". Kept in m_insertPending for that insert's own completion.
     167              :    * @param id The item's process id.
     168              :    * @param completes Whether a completion follows (not for a dropped one).
     169              :    */
     170              :   void onDequeuing(int id, bool completes);
     171              : 
     172              :   /**
     173              :    * @brief A SHA-256 of the regular file @p path (opened without following
     174              :    * a link or blocking on a FIFO).
     175              :    * @param path The entry's file.
     176              :    * @return The hash; empty when @p path is no readable regular file.
     177              :    */
     178              :   static auto entryFingerprint(const QString &path) -> QByteArray;
     179              : 
     180              :   /// An insert's entry and its fingerprint when the insert started to run
     181              :   /// (empty when the entry did not exist).
     182           77 :   struct InsertRecord {
     183              :     QString target;    ///< The entry's file.
     184              :     QByteArray before; ///< Its fingerprint when the insert started.
     185              :   };
     186              :   /// The entries of the queued inserts, oldest first.
     187              :   QQueue<QString> m_insertQueue;
     188              :   /// The inserts that left the queue and have yet to complete, in the
     189              :   /// order their completions come: a failed start's error is posted, and
     190              :   /// the next insert may start before it arrives.
     191              :   QQueue<InsertRecord> m_insertPending;
     192              :   /// The insert whose completion is being handled.
     193              :   InsertRecord m_insertDone;
     194              : 
     195              : public:
     196              :   /**
     197              :    * @brief Construct a RealPass instance.
     198              :    */
     199              :   RealPass();
     200              :   /**
     201              :    * @brief Destructor; lets the search workers wind down.
     202              :    */
     203              :   ~RealPass() override;
     204              : 
     205              :   // Git operations
     206              :   /**
     207              :    * @brief Initialize Git repository in password store.
     208              :    */
     209              :   void GitInit() override;
     210              :   /**
     211              :    * @brief Pull changes from remote.
     212              :    */
     213              :   void GitPull() override;
     214              :   /**
     215              :    * @brief Pull with rebase.
     216              :    */
     217              :   void GitPull_b() override;
     218              :   /**
     219              :    * @brief Push changes to remote.
     220              :    */
     221              :   void GitPush() override;
     222              : 
     223              :   // Password operations
     224              :   /**
     225              :    * @brief Show decrypted password.
     226              :    * @param file Path to password file.
     227              :    */
     228              :   void Show(QString file) override;
     229              :   /**
     230              :    * @brief Insert new password.
     231              :    * @param file Path to password file.
     232              :    * @param newValue Password content.
     233              :    * @param overwrite true to overwrite existing.
     234              :    */
     235              :   void Insert(QString file, QString newValue, bool overwrite) override;
     236              :   /**
     237              :    * @brief Remove an entry or folder with `pass rm`.
     238              :    *
     239              :    * A folder that is a link is unlinked here instead, without deleting its
     240              :    * target; that removal is committed only when Git is enabled and the
     241              :    * tracking lookup and index removal both report success.
     242              :    * @param file Store-relative entry path, without `.gpg` for a password
     243              :    *        file.
     244              :    * @param isDir true to remove a directory; false to remove a password
     245              :    *        file.
     246              :    */
     247              :   void Remove(QString file, bool isDir) override;
     248              :   /**
     249              :    * @brief Initialize password store.
     250              :    * @param path Store root path.
     251              :    * @param users GPG recipients.
     252              :    */
     253              :   void Init(QString path, const QList<UserInfo> &users) override;
     254              : 
     255              : protected:
     256              :   /**
     257              :    * @brief Finish a process; a linked-folder removal commit (GIT_COMMIT,
     258              :    * which RealPass uses for nothing else) is judged by finishLinkCommit().
     259              :    * @param id Process identifier.
     260              :    * @param exitCode Exit code.
     261              :    * @param out Standard output.
     262              :    * @param err Standard error.
     263              :    */
     264              :   void finished(int id, int exitCode, const QString &out,
     265              :                 const QString &err) override;
     266              : 
     267              :   // Pass interface
     268              : public:
     269              :   /**
     270              :    * @brief Move password file or directory.
     271              :    * @param src Source path.
     272              :    * @param dest Destination path.
     273              :    * @param force Overwrite existing.
     274              :    */
     275              :   void Move(const QString src, const QString dest, const bool force) override;
     276              :   /**
     277              :    * @brief Copy password file or directory.
     278              :    * @param src Source path.
     279              :    * @param dest Destination path.
     280              :    * @param force Overwrite existing.
     281              :    */
     282              :   void Copy(const QString src, const QString dest, const bool force) override;
     283              :   /**
     284              :    * @brief Search all password content by GPG-decrypting each real `.gpg`
     285              :    * file, as ImitatePass does.
     286              :    *
     287              :    * Not `pass grep`: that enumerates with `find -L`, which follows a link
     288              :    * out of the store and decrypts what it finds there. Same pattern
     289              :    * semantics as ImitatePass::Grep (QRegularExpression).
     290              :    * @param pattern Search pattern (QRegularExpression).
     291              :    * @param caseInsensitive true for case-insensitive search.
     292              :    */
     293              :   void Grep(QString pattern, bool caseInsensitive) override;
     294              : };
     295              : 
     296              : #endif // SRC_REALPASS_H_
        

Generated by: LCOV version 2.0-1