16#include <QCoreApplication>
21#include <QRegularExpression>
23#include <QStandardPaths>
24#include <QTemporaryDir>
41static QString findGpg() {
42 for (
const auto &c : {
"/usr/bin/gpg2",
"/usr/bin/gpg",
"/usr/local/bin/gpg2",
43 "/usr/local/bin/gpg"}) {
47 return QStandardPaths::findExecutable(
"gpg");
50static QString findGpgconf() {
51 for (
const auto &c : {
"/usr/bin/gpgconf",
"/usr/local/bin/gpgconf"}) {
55 return QStandardPaths::findExecutable(
"gpgconf");
58static QString findPass() {
return QStandardPaths::findExecutable(
"pass"); }
61static int runGpg(
const QString &gnupgHome,
const QStringList &args,
62 const QString &input = QString(), QString *out =
nullptr,
63 QString *err =
nullptr) {
65 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
66 env.insert(
"GNUPGHOME", gnupgHome);
67 p.setProcessEnvironment(env);
68 p.start(findGpg(), args);
69 if (!input.isEmpty()) {
70 p.write(input.toUtf8());
71 p.closeWriteChannel();
73 p.waitForFinished(30000);
75 *out = QString::fromUtf8(p.readAllStandardOutput());
77 *err = QString::fromUtf8(p.readAllStandardError());
82static QString generateTestKey(
const QString &gnupgHome) {
83 const QString batch = QStringLiteral(
"%no-protection\n"
87 "Subkey-Length: 2048\n"
88 "Name-Real: QtPass Integration Test\n"
89 "Name-Email: qtpass-test@localhost\n"
93 int rc = runGpg(gnupgHome, {
"--batch",
"--gen-key"}, batch);
98 runGpg(gnupgHome, {
"--with-colons",
"--fingerprint",
"qtpass-test@localhost"},
102 for (
const auto &line : out.split(
'\n')) {
103 if (line.startsWith(
"fpr:")) {
104 const auto parts = line.split(
':');
105 if (parts.size() >= 10) {
106 fingerprint = parts[9].trimmed();
111 if (fingerprint.isEmpty())
116 if (runGpg(gnupgHome, {
"--batch",
"--import-ownertrust"},
117 fingerprint +
":6:\n") != 0) {
118 qWarning() <<
"Failed to import ownertrust for key:" << fingerprint;
133 QTemporaryDir m_gnupgHome;
134 QString m_keyFingerprint;
135 QString m_originalPassSigningKey;
138 static bool waitForSignal(QSignalSpy &spy,
int timeoutMs = 15000) {
141 return spy.wait(timeoutMs);
145 static void setupPass(
Pass &pass) {
150 static auto gpgInsertErrorMsg(
const QSignalSpy &errorSpy) -> QByteArray {
151 if (errorSpy.count() > 0)
152 return QString(
"GPG Insert error (rc=%1): %2")
153 .arg(errorSpy[0][0].toInt())
154 .arg(errorSpy[0][1].toString())
156 return "finishedInsert not emitted (GPG may have hung or failed to start)";
160 static auto runGitConfig(QProcess &proc,
const QString &gitExe,
161 const QStringList &args) ->
bool {
162 proc.start(gitExe, args);
163 if (!proc.waitForStarted()) {
166 if (!proc.waitForFinished()) {
169 if (proc.exitStatus() != QProcess::NormalExit || proc.exitCode() != 0) {
177 void cleanupTestCase();
180 void imitatePass_insertAndShow();
181 void imitatePass_insertAndGrep();
182 void imitatePass_insertMoveAndShow();
183 void imitatePass_insertCopyAndShow();
184 void imitatePass_insertAndRemove();
185 void imitatePass_nestedDirectoryInsertAndShow();
186 void imitatePass_editExistingEntry();
187 void imitatePass_gitInitAndCommit();
190 void realPass_insertAndShow();
191 void realPass_insertAndGrep();
194 void imitatePass_otpGenerate();
199void tst_integration::initTestCase() {
200 m_gpgExe = findGpg();
201 if (m_gpgExe.isEmpty())
202 QSKIP(
"gpg not found – skipping integration tests");
204 QVERIFY2(m_gnupgHome.isValid(),
"Failed to create temp GNUPGHOME");
206 QFile::setPermissions(m_gnupgHome.path(),
207 QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
212 QByteArray agentPayload =
213 "allow-loopback-pinentry\ndefault-cache-ttl 300\n";
215 agentPayload +=
"pinentry-program /usr/bin/pinentry-tty\n";
217 QFile agentConf(QDir::cleanPath(m_gnupgHome.path() +
"/gpg-agent.conf"));
219 agentConf.open(QIODevice::WriteOnly | QIODevice::Text),
220 qPrintable(
"Cannot open gpg-agent.conf: " + agentConf.errorString()));
222 agentConf.write(agentPayload) == agentPayload.size(),
223 qPrintable(
"Cannot write gpg-agent.conf: " + agentConf.errorString()));
224 const QByteArray gpgPayload =
"pinentry-mode loopback\nbatch\nno-tty\n";
225 QFile gpgConf(QDir::cleanPath(m_gnupgHome.path() +
"/gpg.conf"));
226 QVERIFY2(gpgConf.open(QIODevice::WriteOnly | QIODevice::Text),
227 qPrintable(
"Cannot open gpg.conf: " + gpgConf.errorString()));
228 QVERIFY2(gpgConf.write(gpgPayload) == gpgPayload.size(),
229 qPrintable(
"Cannot write gpg.conf: " + gpgConf.errorString()));
234 QString gpgconfExe = findGpgconf();
235 if (gpgconfExe.isEmpty()) {
236 QSKIP(
"gpgconf not found - skipping GPG integration tests");
238 QProcess agentLaunch;
239 QProcessEnvironment agentEnv = QProcessEnvironment::systemEnvironment();
240 agentEnv.insert(
"GNUPGHOME", m_gnupgHome.path());
241 agentLaunch.setProcessEnvironment(agentEnv);
243 gpgconfExe, {
"--homedir", m_gnupgHome.path(),
"--launch",
"gpg-agent"});
244 QVERIFY2(agentLaunch.waitForStarted(5000),
"gpgconf failed to start");
245 QVERIFY2(agentLaunch.waitForFinished(10000),
246 "gpgconf --launch gpg-agent timed out");
247 QVERIFY2(agentLaunch.exitStatus() == QProcess::NormalExit &&
248 agentLaunch.exitCode() == 0,
249 qPrintable(
"gpgconf --launch gpg-agent failed (rc=" +
250 QString::number(agentLaunch.exitCode()) +
251 "): " + agentLaunch.readAllStandardError()));
254 m_keyFingerprint = generateTestKey(m_gnupgHome.path());
255 QVERIFY2(!m_keyFingerprint.isEmpty(),
256 "Failed to generate GPG key for integration tests");
261 qputenv(
"GNUPGHOME", m_gnupgHome.path().toLocal8Bit());
267 qRegisterMetaType<GrepResults>(
"GrepResults");
268 qRegisterMetaType<GrepResults>(
269 "QList<QPair<QString,QStringList>>");
272void tst_integration::cleanupTestCase() {
278 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
279 env.insert(
"GNUPGHOME", m_gnupgHome.path());
280 killer.setProcessEnvironment(env);
281 killer.start(
"gpgconf", {
"--kill",
"gpg-agent"});
282 killer.waitForFinished(5000);
289void tst_integration::imitatePass_insertAndShow() {
290 QTemporaryDir storeDir;
291 QVERIFY(storeDir.isValid());
296 QFile gpgId(storeDir.path() +
"/.gpg-id");
297 QVERIFY(gpgId.open(QIODevice::WriteOnly | QIODevice::Text));
298 gpgId.write((m_keyFingerprint +
"\n").toUtf8());
304 const QString entryName = QStringLiteral(
"test/password");
305 const QString entryContent = QStringLiteral(
"hunter2\nuser: testuser\n");
308 QVERIFY(QDir(storeDir.path()).mkpath(
"test"));
312 pass.
Insert(entryName, entryContent,
false);
313 QVERIFY2(waitForSignal(insertSpy), gpgInsertErrorMsg(insertErrorSpy));
316 const QString expectedFile = storeDir.path() +
"/" + entryName +
".gpg";
317 QVERIFY2(QFile::exists(expectedFile),
"encrypted file should exist");
321 pass.
Show(entryName);
322 QVERIFY2(waitForSignal(showSpy),
"finishedShow not emitted");
324 const QString decrypted = showSpy[0][0].toString();
325 QVERIFY2(decrypted.contains(
"hunter2"),
326 "decrypted output should contain password");
327 QVERIFY2(decrypted.contains(
"testuser"),
328 "decrypted output should contain user");
331void tst_integration::imitatePass_insertAndGrep() {
332 QTemporaryDir storeDir;
333 QVERIFY(storeDir.isValid());
338 QFile gpgId(storeDir.path() +
"/.gpg-id");
339 QVERIFY(gpgId.open(QIODevice::WriteOnly | QIODevice::Text));
340 gpgId.write((m_keyFingerprint +
"\n").toUtf8());
346 QVERIFY(QDir(storeDir.path()).mkpath(
"work"));
350 pass.
Insert(QStringLiteral(
"work/github"),
351 QStringLiteral(
"s3cr3t\ntoken: abc123\n"),
false);
352 QVERIFY2(waitForSignal(insertSpy), gpgInsertErrorMsg(insertErrorSpy));
355 insertErrorSpy.clear();
356 pass.
Insert(QStringLiteral(
"work/gitlab"),
357 QStringLiteral(
"another\ntoken: xyz789\n"),
false);
358 QVERIFY2(waitForSignal(insertSpy), gpgInsertErrorMsg(insertErrorSpy));
361 pass.
Grep(QStringLiteral(
"token"));
362 QVERIFY2(waitForSignal(grepSpy, 20000),
"finishedGrep not emitted");
364 const auto results = grepSpy[0][0].value<
GrepResults>();
365 QVERIFY2(results.size() == 2,
"grep should find both entries");
368void tst_integration::imitatePass_insertMoveAndShow() {
369 QTemporaryDir storeDir;
370 QVERIFY(storeDir.isValid());
375 QFile gpgId(storeDir.path() +
"/.gpg-id");
376 QVERIFY(gpgId.open(QIODevice::WriteOnly | QIODevice::Text));
377 gpgId.write((m_keyFingerprint +
"\n").toUtf8());
385 pass.
Insert(QStringLiteral(
"original"), QStringLiteral(
"moveme\n"),
false);
386 QVERIFY2(waitForSignal(insertSpy), gpgInsertErrorMsg(insertErrorSpy));
388 const QString src = storeDir.path() +
"/original.gpg";
389 const QString dst = storeDir.path() +
"/moved.gpg";
390 QVERIFY2(QFile::exists(src),
"source .gpg must exist before move");
394 QVERIFY2(!QFile::exists(src),
"source should be gone after move");
395 QVERIFY2(QFile::exists(dst),
"destination should exist after move");
398 pass.
Show(QStringLiteral(
"moved"));
399 QVERIFY2(waitForSignal(showSpy),
"finishedShow not emitted after move");
400 QVERIFY2(showSpy[0][0].toString().contains(
"moveme"),
401 "decrypted moved entry should contain original content");
404void tst_integration::imitatePass_insertCopyAndShow() {
405 QTemporaryDir storeDir;
406 QVERIFY(storeDir.isValid());
411 QFile gpgId(storeDir.path() +
"/.gpg-id");
412 QVERIFY(gpgId.open(QIODevice::WriteOnly | QIODevice::Text));
413 gpgId.write((m_keyFingerprint +
"\n").toUtf8());
421 pass.
Insert(QStringLiteral(
"original"), QStringLiteral(
"copyme\n"),
false);
422 QVERIFY2(waitForSignal(insertSpy), gpgInsertErrorMsg(insertErrorSpy));
424 const QString src = storeDir.path() +
"/original.gpg";
425 const QString dst = storeDir.path() +
"/copy.gpg";
429 QVERIFY2(QFile::exists(src),
"source should still exist after copy");
430 QVERIFY2(QFile::exists(dst),
"destination should exist after copy");
433 pass.
Show(QStringLiteral(
"copy"));
434 QVERIFY2(waitForSignal(showSpy),
"finishedShow not emitted after copy");
435 QVERIFY2(showSpy[0][0].toString().contains(
"copyme"),
436 "decrypted copy should contain original content");
439void tst_integration::imitatePass_insertAndRemove() {
440 QTemporaryDir storeDir;
441 QVERIFY(storeDir.isValid());
446 QFile gpgId(storeDir.path() +
"/.gpg-id");
447 QVERIFY(gpgId.open(QIODevice::WriteOnly | QIODevice::Text));
448 gpgId.write((m_keyFingerprint +
"\n").toUtf8());
456 pass.
Insert(QStringLiteral(
"deleteme"), QStringLiteral(
"gone\n"),
false);
457 QVERIFY2(waitForSignal(insertSpy), gpgInsertErrorMsg(insertErrorSpy));
459 const QString gpgFile = storeDir.path() +
"/deleteme.gpg";
460 QVERIFY2(QFile::exists(gpgFile),
"file must exist before remove");
463 pass.
Remove(QStringLiteral(
"deleteme"),
false);
464 QVERIFY2(!QFile::exists(gpgFile),
"file should be gone after remove");
467void tst_integration::imitatePass_nestedDirectoryInsertAndShow() {
468 QTemporaryDir storeDir;
469 QVERIFY(storeDir.isValid());
474 QFile gpgId(storeDir.path() +
"/.gpg-id");
475 QVERIFY(gpgId.open(QIODevice::WriteOnly | QIODevice::Text));
476 gpgId.write((m_keyFingerprint +
"\n").toUtf8());
482 QVERIFY(QDir(storeDir.path()).mkpath(
"level1/level2/level3"));
486 pass.
Insert(QStringLiteral(
"level1/level2/level3/deep"),
487 QStringLiteral(
"deepvalue\n"),
false);
488 QVERIFY2(waitForSignal(insertSpy), gpgInsertErrorMsg(insertErrorSpy));
490 const QString gpgFile = storeDir.path() +
"/level1/level2/level3/deep.gpg";
491 QVERIFY2(QFile::exists(gpgFile),
"nested .gpg file should be created");
494 pass.
Show(QStringLiteral(
"level1/level2/level3/deep"));
495 QVERIFY2(waitForSignal(showSpy),
"finishedShow not emitted for nested entry");
496 QVERIFY2(showSpy[0][0].toString().contains(
"deepvalue"),
497 "decrypted nested entry should contain the content");
502struct RestoreUseGit {
504 RestoreUseGit() : orig(QtPassSettings::isUseGit()) {}
509void tst_integration::imitatePass_editExistingEntry() {
510 RestoreUseGit restoreUseGit;
513 QTemporaryDir storeDir;
514 QVERIFY(storeDir.isValid());
519 QFile gpgId(QDir::cleanPath(storeDir.path() +
"/.gpg-id"));
520 QVERIFY(gpgId.open(QIODevice::WriteOnly | QIODevice::Text));
521 gpgId.write((m_keyFingerprint +
"\n").toUtf8());
528 const QString entryName = QStringLiteral(
"editme");
529 const QString originalContent = QStringLiteral(
"original\n");
532 pass.
Insert(entryName, originalContent,
false);
533 QVERIFY2(waitForSignal(insertSpy), gpgInsertErrorMsg(insertErrorSpy));
536 const QString gpgFile =
537 QDir::cleanPath(storeDir.path() +
"/" + entryName +
".gpg");
538 QVERIFY2(QFile::exists(gpgFile),
"encrypted file should exist");
541 const QString newContent = QStringLiteral(
"updated\npassword: newpass\n");
543 pass.
Insert(entryName, newContent,
true);
544 QVERIFY2(waitForSignal(editSpy), gpgInsertErrorMsg(insertErrorSpy));
548 pass.
Show(entryName);
549 QVERIFY2(waitForSignal(showSpy),
"finishedShow not emitted");
551 const QString decrypted = showSpy[0][0].toString();
552 QVERIFY2(decrypted.contains(
"updated"),
553 "decrypted should contain new content");
554 QVERIFY2(decrypted.contains(
"newpass"),
555 "decrypted should contain new password");
556 QVERIFY2(!decrypted.contains(
"original"),
557 "decrypted should NOT contain original content");
560void tst_integration::imitatePass_gitInitAndCommit() {
561 const QString gitExe = QStandardPaths::findExecutable(
"git");
562 if (gitExe.isEmpty())
563 QSKIP(
"git not installed – skipping Git integration test");
565 RestoreUseGit restoreUseGit;
567 QTemporaryDir storeDir;
568 QVERIFY(storeDir.isValid());
575 QFile gpgId(QDir::cleanPath(storeDir.path() +
"/.gpg-id"));
576 QVERIFY(gpgId.open(QIODevice::WriteOnly | QIODevice::Text));
577 gpgId.write((m_keyFingerprint +
"\n").toUtf8());
581 gitInit.setWorkingDirectory(storeDir.path());
582 gitInit.start(gitExe, {
"init"});
583 QVERIFY2(gitInit.waitForFinished(),
"git init should complete");
584 QVERIFY2(gitInit.exitCode() == 0,
"git init should succeed");
588 gitConfig.setWorkingDirectory(storeDir.path());
590 runGitConfig(gitConfig, gitExe, {
"config",
"user.name",
"Test User"}),
592 QString(
"git config user.name failed: %1")
593 .arg(QString::fromUtf8(gitConfig.readAllStandardError()))));
595 QProcess gitConfigEmail;
596 gitConfigEmail.setWorkingDirectory(storeDir.path());
597 QVERIFY2(runGitConfig(gitConfigEmail, gitExe,
598 {
"config",
"user.email",
"test@example.com"}),
599 qPrintable(QString(
"git config user.email failed: %1")
600 .arg(QString::fromUtf8(
601 gitConfigEmail.readAllStandardError()))));
603 QProcess gitConfigSign;
604 gitConfigSign.setWorkingDirectory(storeDir.path());
605 QVERIFY2(runGitConfig(gitConfigSign, gitExe,
606 {
"config",
"commit.gpgsign",
"false"}),
607 qPrintable(QString(
"git config commit.gpgsign failed: %1")
608 .arg(QString::fromUtf8(
609 gitConfigSign.readAllStandardError()))));
614 const QString entryName = QStringLiteral(
"gitentry");
615 const QString entryContent = QStringLiteral(
"secret\nurl: example.com\n");
619 pass.Insert(entryName, entryContent,
false);
620 QVERIFY2(waitForSignal(insertSpy), gpgInsertErrorMsg(insertErrorSpy));
623 gitLog.setWorkingDirectory(storeDir.path());
624 gitLog.start(gitExe, {
"log",
"--format=%s",
"-1"});
625 QVERIFY2(gitLog.waitForFinished(),
"git log should complete");
626 QVERIFY2(gitLog.exitCode() == 0,
"git log should succeed");
628 const QString commitMsg = QString::fromUtf8(gitLog.readAll()).trimmed();
629 QVERIFY2(commitMsg.contains(
"gitentry"),
630 qPrintable(QString(
"commit message should mention gitentry: %1")
638void tst_integration::realPass_insertAndShow() {
639 const QString passExe = findPass();
640 if (passExe.isEmpty())
641 QSKIP(
"pass not installed – skipping RealPass integration test");
643 QTemporaryDir storeDir;
644 QVERIFY(storeDir.isValid());
653 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
654 env.insert(
"GNUPGHOME", m_gnupgHome.path());
655 env.insert(
"PASSWORD_STORE_DIR", storeDir.path());
656 initProc.setProcessEnvironment(env);
657 initProc.start(passExe, {
"init", m_keyFingerprint});
658 QVERIFY2(initProc.waitForFinished(15000),
"pass init timed out");
659 QVERIFY2(initProc.exitCode() == 0,
"pass init should succeed");
666 pass.
Insert(QStringLiteral(
"realtest"),
667 QStringLiteral(
"realpassword\nurl: example.com\n"),
false);
668 QVERIFY2(waitForSignal(insertSpy, 20000), gpgInsertErrorMsg(insertErrorSpy));
671 pass.
Show(QStringLiteral(
"realtest"));
672 QVERIFY2(waitForSignal(showSpy, 20000),
"RealPass finishedShow not emitted");
673 QVERIFY2(showSpy[0][0].toString().contains(
"realpassword"),
674 "RealPass show should return inserted content");
679void tst_integration::realPass_insertAndGrep() {
680 const QString passExe = findPass();
681 if (passExe.isEmpty())
682 QSKIP(
"pass not installed – skipping RealPass grep integration test");
684 QTemporaryDir storeDir;
685 QVERIFY(storeDir.isValid());
693 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
694 env.insert(
"GNUPGHOME", m_gnupgHome.path());
695 env.insert(
"PASSWORD_STORE_DIR", storeDir.path());
696 initProc.setProcessEnvironment(env);
697 initProc.start(passExe, {
"init", m_keyFingerprint});
698 QVERIFY2(initProc.waitForFinished(15000),
"pass init timed out");
699 QVERIFY2(initProc.exitCode() == 0,
"pass init should succeed");
706 pass.
Insert(QStringLiteral(
"email/gmail"),
707 QStringLiteral(
"gmailpass\nurl: mail.google.com\n"),
false);
708 QVERIFY2(waitForSignal(insertSpy, 20000), gpgInsertErrorMsg(insertErrorSpy));
711 insertErrorSpy.clear();
712 pass.
Insert(QStringLiteral(
"email/outlook"),
713 QStringLiteral(
"outlookpass\nurl: outlook.com\n"),
false);
714 QVERIFY2(waitForSignal(insertSpy, 20000), gpgInsertErrorMsg(insertErrorSpy));
717 pass.
Grep(QStringLiteral(
"url"));
718 QVERIFY2(waitForSignal(grepSpy, 30000),
"RealPass finishedGrep not emitted");
720 const auto results = grepSpy[0][0].value<
GrepResults>();
721 QVERIFY2(results.size() >= 2,
"grep should find both entries with 'url'");
730void tst_integration::imitatePass_otpGenerate() {
731 const QString passExe = findPass();
732 if (passExe.isEmpty())
733 QSKIP(
"pass not installed – skipping OTP integration test");
736 QFile::exists(
"/usr/lib/password-store/extensions/otp.bash");
738 QSKIP(
"pass-otp extension not found – skipping OTP integration test");
740 QTemporaryDir storeDir;
741 QVERIFY(storeDir.isValid());
748 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
749 env.insert(
"GNUPGHOME", m_gnupgHome.path());
750 env.insert(
"PASSWORD_STORE_DIR", storeDir.path());
753 initProc.setProcessEnvironment(env);
754 initProc.start(passExe, {
"init", m_keyFingerprint});
755 QVERIFY2(initProc.waitForFinished(15000),
"pass init timed out");
756 QVERIFY2(initProc.exitCode() == 0,
"pass init should succeed");
759 const QString totpUri = QStringLiteral(
760 "otpauth://totp/test@example.com?secret=JBSWY3DPEHPK3PXP"
761 "&issuer=IntegrationTest&algorithm=SHA1&digits=6&period=30");
766 otpInsert.setProcessEnvironment(env);
767 otpInsert.start(passExe, {
"insert",
"--force",
"otp/testaccount"});
768 QVERIFY2(otpInsert.waitForStarted(10000),
"pass insert failed to start");
769 otpInsert.write((totpUri +
"\n" + totpUri +
"\n").toUtf8());
770 otpInsert.closeWriteChannel();
771 QVERIFY2(otpInsert.waitForFinished(20000),
"pass insert timed out");
772 if (otpInsert.exitCode() != 0)
773 QSKIP(
"pass insert for OTP failed – skipping OTP generation test");
780 pass.
OtpGenerate(QStringLiteral(
"otp/testaccount"));
781 QVERIFY2(waitForSignal(otpSpy, 20000),
"finishedOtpGenerate not emitted");
783 const QString token = otpSpy[0][0].toString().trimmed();
785 QVERIFY2(QRegularExpression(
"^\\d{6}$").match(token).hasMatch(),
786 qPrintable(
"OTP token should be 6 digits, got: " + token));
792#include "tst_integration.moc"
void Grep(QString pattern, bool caseInsensitive=false) override
Search all password content by GPG-decrypting each .gpg file.
void Insert(QString file, QString newValue, bool overwrite=false) override
Insert new password.
void Move(const QString src, const QString dest, const bool force=false) override
Move password file.
void Copy(const QString src, const QString dest, const bool force=false) override
Copy password file.
void Remove(QString file, bool isDir=false) override
Remove password.
void Show(QString file) override
Show decrypted password.
Abstract base class for password store operations.
void init()
Initialize the Pass instance.
void finishedShow(const QString &)
Emitted when show finishes.
void finishedInsert(const QString &, const QString &)
Emitted when insert finishes.
void finishedGrep(const QList< QPair< QString, QStringList > > &results)
Emitted when grep finishes with matching results.
void finishedOtpGenerate(const QString &)
Emitted when OTP generation finishes.
void processErrorExit(int exitCode, const QString &err)
Emitted on process error exit.
void updateEnv()
Update environment for subprocesses.
static void setPassExecutable(const QString &passExecutable)
Save pass executable path.
static auto getInstance() -> QtPassSettings *
Get the singleton instance.
static void setPassStore(const QString &passStore)
Save password store path.
static void setGitExecutable(const QString &gitExecutable)
Save git executable path.
static void setPassSigningKey(const QString &passSigningKey)
Save GPG signing key.
static auto getPassSigningKey(const QString &defaultValue=QVariant().toString()) -> QString
Get GPG signing key for pass.
static void setGpgExecutable(const QString &gpgExecutable)
Save GPG executable path.
static void setUsePass(const bool &usePass)
Save use pass setting.
static void setUseGit(const bool &useGit)
Save Git integration flag.
void OtpGenerate(QString file) override
Generate OTP code.
void Insert(QString file, QString newValue, bool overwrite=false) override
Insert new password.
void Show(QString file) override
Show decrypted password.
void Grep(QString pattern, bool caseInsensitive=false) override
Search password content via 'pass grep'.
static const QString gpgHome
QList< QPair< QString, QStringList > > GrepResults
Integration tests for ImitatePass and RealPass backends.