QtPass  1.3.3
Multi-platform GUI for pass, the standard unix password manager.
imitatepass.cpp
Go to the documentation of this file.
1 #include "imitatepass.h"
2 #include "qtpasssettings.h"
3 #include <QDirIterator>
4 #include <QRegularExpression>
5 #include <utility>
6 
7 #ifdef QT_DEBUG
8 #include "debughelper.h"
9 #endif
10 
11 using namespace Enums;
12 
17 ImitatePass::ImitatePass() = default;
18 
19 static QString pgit(const QString &path) {
20  if (!QtPassSettings::getGitExecutable().startsWith("wsl "))
21  return path;
22  QString res = "$(wslpath " + path + ")";
23  return res.replace('\\', '/');
24 }
25 
26 static QString pgpg(const QString &path) {
27  if (!QtPassSettings::getGpgExecutable().startsWith("wsl "))
28  return path;
29  QString res = "$(wslpath " + path + ")";
30  return res.replace('\\', '/');
31 }
32 
37  executeGit(GIT_INIT, {"init", pgit(QtPassSettings::getPassStore())});
38 }
39 
43 void ImitatePass::GitPull() { executeGit(GIT_PULL, {"pull"}); }
44 
49  exec.executeBlocking(QtPassSettings::getGitExecutable(), {"pull"});
50 }
51 
57  executeGit(GIT_PUSH, {"push"});
58  }
59 }
60 
64 void ImitatePass::Show(QString file) {
65  file = QtPassSettings::getPassStore() + file + ".gpg";
66  QStringList args = {"-d", "--quiet", "--yes", "--no-encrypt-to",
67  "--batch", "--use-agent", pgpg(file)};
68  executeGpg(PASS_SHOW, args);
69 }
70 
74 void ImitatePass::OtpGenerate(QString file) {
75 #ifdef QT_DEBUG
76  dbg() << "No OTP generation code for fake pass yet, attempting for file: " +
77  file;
78 #else
79  Q_UNUSED(file)
80 #endif
81 }
82 
90 void ImitatePass::Insert(QString file, QString newValue, bool overwrite) {
91  file = file + ".gpg";
92  transactionHelper trans(this, PASS_INSERT);
93  QStringList recipients = Pass::getRecipientList(file);
94  if (recipients.isEmpty()) {
95  // TODO(bezet): probably throw here
96  emit critical(tr("Can not edit"),
97  tr("Could not read encryption key to use, .gpg-id "
98  "file missing or invalid."));
99  return;
100  }
101  QStringList args = {"--batch", "-eq", "--output", pgpg(file)};
102  for (auto &r : recipients) {
103  args.append("-r");
104  args.append(r);
105  };
106  if (overwrite)
107  args.append("--yes");
108  args.append("-");
109  executeGpg(PASS_INSERT, args, newValue);
111  // TODO(bezet) why not?
112  if (!overwrite)
113  executeGit(GIT_ADD, {"add", pgit(file)});
114  QString path = QDir(QtPassSettings::getPassStore()).relativeFilePath(file);
115  path.replace(QRegularExpression("\\.gpg$"), "");
116  QString msg =
117  QString(overwrite ? "Edit" : "Add") + " for " + path + " using QtPass.";
118  GitCommit(file, msg);
119  }
120 }
121 
128 void ImitatePass::GitCommit(const QString &file, const QString &msg) {
129  if (file.isEmpty())
130  executeGit(GIT_COMMIT, {"commit", "-m", msg});
131  else
132  executeGit(GIT_COMMIT, {"commit", "-m", msg, "--", pgit(file)});
133 }
134 
138 void ImitatePass::Remove(QString file, bool isDir) {
139  file = QtPassSettings::getPassStore() + file;
140  transactionHelper trans(this, PASS_REMOVE);
141  if (!isDir)
142  file += ".gpg";
143  if (QtPassSettings::isUseGit()) {
144  executeGit(GIT_RM, {"rm", (isDir ? "-rf" : "-f"), pgit(file)});
145  // TODO(bezet): commit message used to have pass-like file name inside(ie.
146  // getFile(file, true)
147  GitCommit(file, "Remove for " + file + " using QtPass.");
148  } else {
149  if (isDir) {
150 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
151  QDir dir(file);
152  dir.removeRecursively();
153 #else
154  removeDir(QtPassSettings::getPassStore() + file);
155 #endif
156  } else
157  QFile(file).remove();
158  }
159 }
160 
168 void ImitatePass::Init(QString path, const QList<UserInfo> &users) {
169  QString gpgIdFile = path + ".gpg-id";
170  QFile gpgId(gpgIdFile);
171  bool addFile = false;
172  transactionHelper trans(this, PASS_INIT);
173  if (QtPassSettings::isAddGPGId(true)) {
174  QFileInfo checkFile(gpgIdFile);
175  if (!checkFile.exists() || !checkFile.isFile())
176  addFile = true;
177  }
178  if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
179  emit critical(tr("Cannot update"),
180  tr("Failed to open .gpg-id for writing."));
181  return;
182  }
183  bool secret_selected = false;
184  foreach (const UserInfo &user, users) {
185  if (user.enabled) {
186  gpgId.write((user.key_id + "\n").toUtf8());
187  secret_selected |= user.have_secret;
188  }
189  }
190  gpgId.close();
191  if (!secret_selected) {
192  emit critical(
193  tr("Check selected users!"),
194  tr("None of the selected keys have a secret key available.\n"
195  "You will not be able to decrypt any newly added passwords!"));
196  return;
197  }
198 
200  !QtPassSettings::getGitExecutable().isEmpty()) {
201  if (addFile)
202  executeGit(GIT_ADD, {"add", pgit(gpgIdFile)});
203  QString commitPath = gpgIdFile;
204  commitPath.replace(QRegularExpression("\\.gpg$"), "");
205  GitCommit(gpgIdFile, "Added " + commitPath + " using QtPass.");
206  }
207  reencryptPath(path);
208 }
209 
215 bool ImitatePass::removeDir(const QString &dirName) {
216  bool result = true;
217  QDir dir(dirName);
218 
219  if (dir.exists(dirName)) {
220  Q_FOREACH (QFileInfo info,
221  dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System |
222  QDir::Hidden | QDir::AllDirs | QDir::Files,
223  QDir::DirsFirst)) {
224  if (info.isDir())
225  result = removeDir(info.absoluteFilePath());
226  else
227  result = QFile::remove(info.absoluteFilePath());
228 
229  if (!result)
230  return result;
231  }
232  result = dir.rmdir(dirName);
233  }
234  return result;
235 }
236 
244 void ImitatePass::reencryptPath(const QString &dir) {
245  emit statusMsg(tr("Re-encrypting from folder %1").arg(dir), 3000);
246  emit startReencryptPath();
248  // TODO(bezet): move statuses inside actions?
249  emit statusMsg(tr("Updating password-store"), 2000);
250  GitPull_b();
251  }
252  QDir currentDir;
253  QDirIterator gpgFiles(dir, QStringList() << "*.gpg", QDir::Files,
254  QDirIterator::Subdirectories);
255  QStringList gpgId;
256  while (gpgFiles.hasNext()) {
257  QString fileName = gpgFiles.next();
258  if (gpgFiles.fileInfo().path() != currentDir.path()) {
259  gpgId = getRecipientList(fileName);
260  gpgId.sort();
261  }
262  // TODO(bezet): enable --with-colons for better future-proofness?
263  QStringList args = {
264  "-v", "--no-secmem-warning", "--no-permission-warning",
265  "--list-only", "--keyid-format=long", pgpg(fileName)};
266  QString keys, err;
267  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, &keys, &err);
268  QStringList actualKeys;
269  keys += err;
270 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
271  QStringList key = keys.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts);
272 #else
273  QStringList key = keys.split(QRegularExpression("[\r\n]"), QString::SkipEmptyParts);
274 #endif
275  QListIterator<QString> itr(key);
276  while (itr.hasNext()) {
277  QString current = itr.next();
278  QStringList cur = current.split(" ");
279  if (cur.length() > 4) {
280  QString actualKey = cur.takeAt(4);
281  if (actualKey.length() == 16) {
282  actualKeys << actualKey;
283  }
284  }
285  }
286  actualKeys.sort();
287  if (actualKeys != gpgId) {
288  // dbg()<< actualKeys << gpgId << getRecipientList(fileName);
289 #ifdef QT_DEBUG
290  dbg() << "reencrypt " << fileName << " for " << gpgId;
291 #endif
292  QString local_lastDecrypt = "Could not decrypt";
293  args = QStringList{
294  "-d", "--quiet", "--yes", "--no-encrypt-to",
295  "--batch", "--use-agent", pgpg(fileName)};
296  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
297  &local_lastDecrypt);
298 
299  if (!local_lastDecrypt.isEmpty() &&
300  local_lastDecrypt != "Could not decrypt") {
301  if (local_lastDecrypt.right(1) != "\n")
302  local_lastDecrypt += "\n";
303 
304  QStringList recipients = Pass::getRecipientList(fileName);
305  if (recipients.isEmpty()) {
306  emit critical(tr("Can not edit"),
307  tr("Could not read encryption key to use, .gpg-id "
308  "file missing or invalid."));
309  return;
310  }
311  args =
312  QStringList{"--yes", "--batch", "-eq", "--output", pgpg(fileName)};
313  for (auto &i : recipients) {
314  args.append("-r");
315  args.append(i);
316  }
317  args.append("-");
318  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
319  local_lastDecrypt);
320 
322  exec.executeBlocking(QtPassSettings::getGitExecutable(),
323  {"add", pgit(fileName)});
324  QString path =
325  QDir(QtPassSettings::getPassStore()).relativeFilePath(fileName);
326  path.replace(QRegularExpression("\\.gpg$"), "");
327  exec.executeBlocking(QtPassSettings::getGitExecutable(),
328  {"commit", pgit(fileName), "-m",
329  "Edit for " + path + " using QtPass."});
330  }
331 
332  } else {
333 #ifdef QT_DEBUG
334  dbg() << "Decrypt error on re-encrypt";
335 #endif
336  }
337  }
338  }
340  emit statusMsg(tr("Updating password-store"), 2000);
341  // TODO(bezet): this is non-blocking and shall be done outside
342  GitPush();
343  }
344  emit endReencryptPath();
345 }
346 
347 void ImitatePass::Move(const QString src, const QString dest,
348  const bool force) {
349  transactionHelper trans(this, PASS_MOVE);
350  QFileInfo srcFileInfo(src);
351  QFileInfo destFileInfo(dest);
352  QString destFile;
353 
354  if (srcFileInfo.isFile()) {
355  if (destFileInfo.isFile()) {
356  if (!force) {
357 #ifdef QT_DEBUG
358  dbg() << "Destination file already exists";
359 #endif
360  return;
361  }
362  } else if (destFileInfo.isDir()) {
363  destFile = QDir(dest).filePath(srcFileInfo.baseName());
364  } else {
365  destFile = dest;
366  }
367 
368  if (!destFile.endsWith(".gpg"))
369  destFile.append(".gpg");
370 
371  } else if (srcFileInfo.isDir()) {
372  if (destFileInfo.isDir()) {
373  destFile = QDir(dest).filePath(srcFileInfo.baseName());
374  } else if (destFileInfo.isFile()) {
375 #ifdef QT_DEBUG
376  dbg() << "Destination is a file";
377 #endif
378  return;
379  } else {
380  destFile = dest;
381  }
382 
383  } else {
384 #ifdef QT_DEBUG
385  dbg() << "Source file does not exist";
386 #endif
387  return;
388  }
389 
390 #ifdef QT_DEBUG
391  dbg() << "Move Source: " << src;
392  dbg() << "Move Destination: " << destFile;
393 #endif
394 
395  if (QtPassSettings::isUseGit()) {
396  QStringList args;
397  args << "mv";
398  if (force) {
399  args << "-f";
400  }
401  args << pgit(src);
402  args << pgit(destFile);
403  executeGit(GIT_MOVE, args);
404 
405  QString relSrc = QDir(QtPassSettings::getPassStore()).relativeFilePath(src);
406  relSrc.replace(QRegularExpression("\\.gpg$"), "");
407  QString relDest = QDir(QtPassSettings::getPassStore()).relativeFilePath(destFile);
408  relDest.replace(QRegularExpression("\\.gpg$"), "");
409  QString message = QString("Moved for %1 to %2 using QtPass.");
410  message = message.arg(relSrc).arg(relDest);
411  GitCommit("", message);
412  } else {
413  QDir qDir;
414  if (force) {
415  qDir.remove(destFile);
416  }
417  qDir.rename(src, destFile);
418  }
419 }
420 
421 void ImitatePass::Copy(const QString src, const QString dest,
422  const bool force) {
423  QFileInfo destFileInfo(dest);
424  transactionHelper trans(this, PASS_COPY);
425  if (QtPassSettings::isUseGit()) {
426  QStringList args;
427  args << "cp";
428  if (force) {
429  args << "-f";
430  }
431  args << pgit(src);
432  args << pgit(dest);
433  executeGit(GIT_COPY, args);
434 
435  QString message = QString("copied from %1 to %2 using QTPass.");
436  message = message.arg(src).arg(dest);
437  GitCommit("", message);
438  } else {
439  QDir qDir;
440  if (force) {
441  qDir.remove(dest);
442  }
443  QFile::copy(src, dest);
444  }
445  // reecrypt all files under the new folder
446  if (destFileInfo.isDir()) {
447  reencryptPath(destFileInfo.absoluteFilePath());
448  } else if (destFileInfo.isFile()) {
449  reencryptPath(destFileInfo.dir().path());
450  }
451 }
452 
457 void ImitatePass::executeGpg(PROCESS id, const QStringList &args, QString input,
458  bool readStdout, bool readStderr) {
459  executeWrapper(id, QtPassSettings::getGpgExecutable(), args, std::move(input),
460  readStdout, readStderr);
461 }
466 void ImitatePass::executeGit(PROCESS id, const QStringList &args, QString input,
467  bool readStdout, bool readStderr) {
468  executeWrapper(id, QtPassSettings::getGitExecutable(), args, std::move(input),
469  readStdout, readStderr);
470 }
471 
482 void ImitatePass::finished(int id, int exitCode, const QString &out,
483  const QString &err) {
484 #ifdef QT_DEBUG
485  dbg() << "Imitate Pass";
486 #endif
487  static QString transactionOutput;
488  PROCESS pid = transactionIsOver(static_cast<PROCESS>(id));
489  transactionOutput.append(out);
490 
491  if (exitCode == 0) {
492  if (pid == INVALID)
493  return;
494  } else {
495  while (pid == INVALID) {
496  id = exec.cancelNext();
497  if (id == -1) {
498  // this is probably irrecoverable and shall not happen
499 #ifdef QT_DEBUG
500  dbg() << "No such transaction!";
501 #endif
502  return;
503  }
504  pid = transactionIsOver(static_cast<PROCESS>(id));
505  }
506  }
507  Pass::finished(pid, exitCode, transactionOutput, err);
508  transactionOutput.clear();
509 }
510 
520 void ImitatePass::executeWrapper(PROCESS id, const QString &app,
521  const QStringList &args, QString input,
522  bool readStdout, bool readStderr) {
523  transactionAdd(id);
524  Pass::executeWrapper(id, app, args, input, readStdout, readStderr);
525 }
QtPassSettings::getGitExecutable
static QString getGitExecutable(const QString &defaultValue=QVariant().toString())
Definition: qtpasssettings.cpp:305
Enums::GIT_PULL
@ GIT_PULL
Definition: enums.h:21
Enums::PASS_COPY
@ PASS_COPY
Definition: enums.h:29
ImitatePass::GitPush
virtual void GitPush() Q_DECL_OVERRIDE
ImitatePass::GitPush git init wrapper.
Definition: imitatepass.cpp:55
UserInfo::have_secret
bool have_secret
UserInfo::have_secret secret key is available (can decrypt with this key)
Definition: userinfo.h:46
UserInfo::key_id
QString key_id
UserInfo::key_id hexadecimal representation.
Definition: userinfo.h:36
QtPassSettings::getGpgExecutable
static QString getGpgExecutable(const QString &defaultValue=QVariant().toString())
Definition: qtpasssettings.cpp:314
UserInfo::enabled
bool enabled
UserInfo::enabled.
Definition: userinfo.h:50
Enums::GIT_COMMIT
@ GIT_COMMIT
Definition: enums.h:19
ImitatePass::Show
virtual void Show(QString file) Q_DECL_OVERRIDE
ImitatePass::Show shows content of file.
Definition: imitatepass.cpp:64
debughelper.h
ImitatePass::Copy
void Copy(const QString src, const QString dest, const bool force=false) Q_DECL_OVERRIDE
Definition: imitatepass.cpp:421
imitatepass.h
QtPassSettings::isAddGPGId
static bool isAddGPGId(const bool &defaultValue=QVariant().toBool())
Definition: qtpasssettings.cpp:245
Enums::PASS_INIT
@ PASS_INIT
Definition: enums.h:26
Enums::PASS_SHOW
@ PASS_SHOW
Definition: enums.h:23
QtPassSettings::isAutoPush
static bool isAutoPush(const bool &defaultValue=QVariant().toBool())
Definition: qtpasssettings.cpp:520
ImitatePass::Insert
virtual void Insert(QString file, QString newValue, bool overwrite=false) Q_DECL_OVERRIDE
ImitatePass::Insert create new file with encrypted content.
Definition: imitatepass.cpp:90
Pass::finished
virtual void finished(int id, int exitCode, const QString &out, const QString &err)
Pass::processFinished reemits specific signal based on what process has finished.
Definition: pass.cpp:192
Enums::GIT_ADD
@ GIT_ADD
Definition: enums.h:18
Enums::GIT_MOVE
@ GIT_MOVE
Definition: enums.h:30
ImitatePass::ImitatePass
ImitatePass()
ImitatePass::ImitatePass for situaions when pass is not available we imitate the behavior of pass htt...
ImitatePass::Remove
virtual void Remove(QString file, bool isDir=false) Q_DECL_OVERRIDE
ImitatePass::Remove custom implementation of "pass remove".
Definition: imitatepass.cpp:138
ImitatePass::Move
void Move(const QString src, const QString dest, const bool force=false) Q_DECL_OVERRIDE
Definition: imitatepass.cpp:347
Pass::getRecipientList
static QStringList getRecipientList(QString for_file)
Pass::getRecipientList return list of gpg-id's to encrypt for.
Definition: pass.cpp:263
Enums::PROCESS
PROCESS
Definition: enums.h:16
ImitatePass::Init
virtual void Init(QString path, const QList< UserInfo > &users) Q_DECL_OVERRIDE
ImitatePass::Init initialize pass repository.
Definition: imitatepass.cpp:168
ImitatePass::GitPull_b
virtual void GitPull_b() Q_DECL_OVERRIDE
ImitatePass::GitPull_b git pull wrapper.
Definition: imitatepass.cpp:48
QtPassSettings::isUseGit
static bool isUseGit(const bool &defaultValue=QVariant().toBool())
Definition: qtpasssettings.cpp:383
Enums::INVALID
@ INVALID
Definition: enums.h:33
QtPassSettings::isAutoPull
static bool isAutoPull(const bool &defaultValue=QVariant().toBool())
Definition: qtpasssettings.cpp:511
Enums
Enumerators for configuration and runtime items.
ImitatePass::GitInit
virtual void GitInit() Q_DECL_OVERRIDE
ImitatePass::GitInit git init wrapper.
Definition: imitatepass.cpp:36
ImitatePass::executeWrapper
virtual void executeWrapper(PROCESS id, const QString &app, const QStringList &args, QString input, bool readStdout=true, bool readStderr=true) Q_DECL_OVERRIDE
executeWrapper overrided so that every execution is a transaction
Definition: imitatepass.cpp:520
Enums::PASS_MOVE
@ PASS_MOVE
Definition: enums.h:28
ImitatePass::reencryptPath
void reencryptPath(const QString &dir)
ImitatePass::reencryptPath reencrypt all files under the chosen directory.
Definition: imitatepass.cpp:244
Enums::GIT_INIT
@ GIT_INIT
Definition: enums.h:17
Enums::GIT_COPY
@ GIT_COPY
Definition: enums.h:31
Pass::executeWrapper
void executeWrapper(PROCESS id, const QString &app, const QStringList &args, bool readStdout=true, bool readStderr=true)
Definition: pass.cpp:29
Enums::GIT_RM
@ GIT_RM
Definition: enums.h:20
Enums::GIT_PUSH
@ GIT_PUSH
Definition: enums.h:22
dbg
#define dbg()
Definition: debughelper.h:7
UserInfo
Stores key info lines including validity, creation date and more.
Definition: userinfo.h:11
Enums::PASS_INSERT
@ PASS_INSERT
Definition: enums.h:24
ImitatePass::GitPull
virtual void GitPull() Q_DECL_OVERRIDE
ImitatePass::GitPull git init wrapper.
Definition: imitatepass.cpp:43
QtPassSettings::getPassStore
static QString getPassStore(const QString &defaultValue=QVariant().toString())
Definition: qtpasssettings.cpp:254
ImitatePass::OtpGenerate
virtual void OtpGenerate(QString file) Q_DECL_OVERRIDE
ImitatePass::OtpGenerate generates an otp code.
Definition: imitatepass.cpp:74
QtPassSettings::isUseWebDav
static bool isUseWebDav(const bool &defaultValue=QVariant().toBool())
Definition: qtpasssettings.cpp:338
ImitatePass::finished
virtual void finished(int id, int exitCode, const QString &out, const QString &err) Q_DECL_OVERRIDE
ImitatePass::finished this function is overloaded to ensure identical behaviour to RealPass ie....
Definition: imitatepass.cpp:482
qtpasssettings.h
Enums::PASS_REMOVE
@ PASS_REMOVE
Definition: enums.h:25