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 <utility>
5 
6 #ifdef QT_DEBUG
7 #include "debughelper.h"
8 #endif
9 
10 using namespace Enums;
11 
16 ImitatePass::ImitatePass() = default;
17 
18 static QString pgit(const QString &path) {
19  if (!QtPassSettings::getGitExecutable().startsWith("wsl "))
20  return path;
21  QString res = "$(wslpath " + path + ")";
22  return res.replace('\\', '/');
23 }
24 
25 static QString pgpg(const QString &path) {
26  if (!QtPassSettings::getGpgExecutable().startsWith("wsl "))
27  return path;
28  QString res = "$(wslpath " + path + ")";
29  return res.replace('\\', '/');
30 }
31 
36  executeGit(GIT_INIT, {"init", pgit(QtPassSettings::getPassStore())});
37 }
38 
42 void ImitatePass::GitPull() { executeGit(GIT_PULL, {"pull"}); }
43 
48  exec.executeBlocking(QtPassSettings::getGitExecutable(), {"pull"});
49 }
50 
56  executeGit(GIT_PUSH, {"push"});
57  }
58 }
59 
63 void ImitatePass::Show(QString file) {
64  file = QtPassSettings::getPassStore() + file + ".gpg";
65  QStringList args = {"-d", "--quiet", "--yes", "--no-encrypt-to",
66  "--batch", "--use-agent", pgpg(file)};
67  executeGpg(PASS_SHOW, args);
68 }
69 
73 void ImitatePass::OtpGenerate(QString file) {
74 #ifdef QT_DEBUG
75  dbg() << "No OTP generation code for fake pass yet, attempting for file: " +
76  file;
77 #else
78  Q_UNUSED(file)
79 #endif
80 }
81 
89 void ImitatePass::Insert(QString file, QString newValue, bool overwrite) {
90  file = file + ".gpg";
91  transactionHelper trans(this, PASS_INSERT);
92  QStringList recipients = Pass::getRecipientList(file);
93  if (recipients.isEmpty()) {
94  // TODO(bezet): probably throw here
95  emit critical(tr("Can not edit"),
96  tr("Could not read encryption key to use, .gpg-id "
97  "file missing or invalid."));
98  return;
99  }
100  QStringList args = {"--batch", "-eq", "--output", pgpg(file)};
101  for (auto &r : recipients) {
102  args.append("-r");
103  args.append(r);
104  };
105  if (overwrite)
106  args.append("--yes");
107  args.append("-");
108  executeGpg(PASS_INSERT, args, newValue);
110  // TODO(bezet) why not?
111  if (!overwrite)
112  executeGit(GIT_ADD, {"add", pgit(file)});
113  QString path = QDir(QtPassSettings::getPassStore()).relativeFilePath(file);
114  path.replace(QRegExp("\\.gpg$"), "");
115  QString msg =
116  QString(overwrite ? "Edit" : "Add") + " for " + path + " using QtPass.";
117  GitCommit(file, msg);
118  }
119 }
120 
127 void ImitatePass::GitCommit(const QString &file, const QString &msg) {
128  executeGit(GIT_COMMIT, {"commit", "-m", msg, "--", pgit(file)});
129 }
130 
134 void ImitatePass::Remove(QString file, bool isDir) {
135  file = QtPassSettings::getPassStore() + file;
136  transactionHelper trans(this, PASS_REMOVE);
137  if (!isDir)
138  file += ".gpg";
139  if (QtPassSettings::isUseGit()) {
140  executeGit(GIT_RM, {"rm", (isDir ? "-rf" : "-f"), pgit(file)});
141  // TODO(bezet): commit message used to have pass-like file name inside(ie.
142  // getFile(file, true)
143  GitCommit(file, "Remove for " + file + " using QtPass.");
144  } else {
145  if (isDir) {
146 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
147  QDir dir(file);
148  dir.removeRecursively();
149 #else
150  removeDir(QtPassSettings::getPassStore() + file);
151 #endif
152  } else
153  QFile(file).remove();
154  }
155 }
156 
164 void ImitatePass::Init(QString path, const QList<UserInfo> &users) {
165  QString gpgIdFile = path + ".gpg-id";
166  QFile gpgId(gpgIdFile);
167  bool addFile = false;
168  transactionHelper trans(this, PASS_INIT);
169  if (QtPassSettings::isAddGPGId(true)) {
170  QFileInfo checkFile(gpgIdFile);
171  if (!checkFile.exists() || !checkFile.isFile())
172  addFile = true;
173  }
174  if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
175  emit critical(tr("Cannot update"),
176  tr("Failed to open .gpg-id for writing."));
177  return;
178  }
179  bool secret_selected = false;
180  foreach (const UserInfo &user, users) {
181  if (user.enabled) {
182  gpgId.write((user.key_id + "\n").toUtf8());
183  secret_selected |= user.have_secret;
184  }
185  }
186  gpgId.close();
187  if (!secret_selected) {
188  emit critical(
189  tr("Check selected users!"),
190  tr("None of the selected keys have a secret key available.\n"
191  "You will not be able to decrypt any newly added passwords!"));
192  return;
193  }
194 
196  !QtPassSettings::getGitExecutable().isEmpty()) {
197  if (addFile)
198  executeGit(GIT_ADD, {"add", pgit(gpgIdFile)});
199  QString commitPath = gpgIdFile;
200  commitPath.replace(QRegExp("\\.gpg$"), "");
201  GitCommit(gpgIdFile, "Added " + commitPath + " using QtPass.");
202  }
203  reencryptPath(path);
204 }
205 
211 bool ImitatePass::removeDir(const QString &dirName) {
212  bool result = true;
213  QDir dir(dirName);
214 
215  if (dir.exists(dirName)) {
216  Q_FOREACH (QFileInfo info,
217  dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System |
218  QDir::Hidden | QDir::AllDirs | QDir::Files,
219  QDir::DirsFirst)) {
220  if (info.isDir())
221  result = removeDir(info.absoluteFilePath());
222  else
223  result = QFile::remove(info.absoluteFilePath());
224 
225  if (!result)
226  return result;
227  }
228  result = dir.rmdir(dirName);
229  }
230  return result;
231 }
232 
240 void ImitatePass::reencryptPath(const QString &dir) {
241  emit statusMsg(tr("Re-encrypting from folder %1").arg(dir), 3000);
242  emit startReencryptPath();
244  // TODO(bezet): move statuses inside actions?
245  emit statusMsg(tr("Updating password-store"), 2000);
246  GitPull_b();
247  }
248  QDir currentDir;
249  QDirIterator gpgFiles(dir, QStringList() << "*.gpg", QDir::Files,
250  QDirIterator::Subdirectories);
251  QStringList gpgId;
252  while (gpgFiles.hasNext()) {
253  QString fileName = gpgFiles.next();
254  if (gpgFiles.fileInfo().path() != currentDir.path()) {
255  gpgId = getRecipientList(fileName);
256  gpgId.sort();
257  }
258  // TODO(bezet): enable --with-colons for better future-proofness?
259  QStringList args = {
260  "-v", "--no-secmem-warning", "--no-permission-warning",
261  "--list-only", "--keyid-format=long", pgpg(fileName)};
262  QString keys, err;
263  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, &keys, &err);
264  QStringList actualKeys;
265  keys += err;
266  QStringList key = keys.split(QRegExp("[\r\n]"), QString::SkipEmptyParts);
267  QListIterator<QString> itr(key);
268  while (itr.hasNext()) {
269  QString current = itr.next();
270  QStringList cur = current.split(" ");
271  if (cur.length() > 4) {
272  QString actualKey = cur.takeAt(4);
273  if (actualKey.length() == 16) {
274  actualKeys << actualKey;
275  }
276  }
277  }
278  actualKeys.sort();
279  if (actualKeys != gpgId) {
280  // dbg()<< actualKeys << gpgId << getRecipientList(fileName);
281 #ifdef QT_DEBUG
282  dbg() << "reencrypt " << fileName << " for " << gpgId;
283 #endif
284  QString local_lastDecrypt = "Could not decrypt";
285  args = QStringList{
286  "-d", "--quiet", "--yes", "--no-encrypt-to",
287  "--batch", "--use-agent", pgpg(fileName)};
288  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
289  &local_lastDecrypt);
290 
291  if (!local_lastDecrypt.isEmpty() &&
292  local_lastDecrypt != "Could not decrypt") {
293  if (local_lastDecrypt.right(1) != "\n")
294  local_lastDecrypt += "\n";
295 
296  QStringList recipients = Pass::getRecipientList(fileName);
297  if (recipients.isEmpty()) {
298  emit critical(tr("Can not edit"),
299  tr("Could not read encryption key to use, .gpg-id "
300  "file missing or invalid."));
301  return;
302  }
303  args =
304  QStringList{"--yes", "--batch", "-eq", "--output", pgpg(fileName)};
305  for (auto &i : recipients) {
306  args.append("-r");
307  args.append(i);
308  }
309  args.append("-");
310  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
311  local_lastDecrypt);
312 
314  exec.executeBlocking(QtPassSettings::getGitExecutable(),
315  {"add", pgit(fileName)});
316  QString path =
317  QDir(QtPassSettings::getPassStore()).relativeFilePath(fileName);
318  path.replace(QRegExp("\\.gpg$"), "");
319  exec.executeBlocking(QtPassSettings::getGitExecutable(),
320  {"commit", pgit(fileName), "-m",
321  "Edit for " + path + " using QtPass."});
322  }
323 
324  } else {
325 #ifdef QT_DEBUG
326  dbg() << "Decrypt error on re-encrypt";
327 #endif
328  }
329  }
330  }
332  emit statusMsg(tr("Updating password-store"), 2000);
333  // TODO(bezet): this is non-blocking and shall be done outside
334  GitPush();
335  }
336  emit endReencryptPath();
337 }
338 
339 void ImitatePass::Move(const QString src, const QString dest,
340  const bool force) {
341  QFileInfo destFileInfo(dest);
342  transactionHelper trans(this, PASS_MOVE);
343  if (QtPassSettings::isUseGit()) {
344  QStringList args;
345  args << "mv";
346  if (force) {
347  args << "-f";
348  }
349  args << pgit(src);
350  args << pgit(dest);
351  executeGit(GIT_MOVE, args);
352 
353  QString message = QString("moved from %1 to %2 using QTPass.");
354  message = message.arg(src).arg(dest);
355  GitCommit("", message);
356  } else {
357  QDir qDir;
358  QFileInfo srcFileInfo(src);
359  QString destCopy = dest;
360  if (srcFileInfo.isFile() && destFileInfo.isDir()) {
361  destCopy = destFileInfo.absoluteFilePath() + QDir::separator() +
362  srcFileInfo.fileName();
363  }
364  if (force) {
365  qDir.remove(destCopy);
366  }
367  qDir.rename(src, destCopy);
368  }
369  // reecrypt all files under the new folder
370  if (destFileInfo.isDir()) {
371  reencryptPath(destFileInfo.absoluteFilePath());
372  } else if (destFileInfo.isFile()) {
373  reencryptPath(destFileInfo.dir().path());
374  }
375 }
376 
377 void ImitatePass::Copy(const QString src, const QString dest,
378  const bool force) {
379  QFileInfo destFileInfo(dest);
380  transactionHelper trans(this, PASS_COPY);
381  if (QtPassSettings::isUseGit()) {
382  QStringList args;
383  args << "cp";
384  if (force) {
385  args << "-f";
386  }
387  args << pgit(src);
388  args << pgit(dest);
389  executeGit(GIT_COPY, args);
390 
391  QString message = QString("copied from %1 to %2 using QTPass.");
392  message = message.arg(src).arg(dest);
393  GitCommit("", message);
394  } else {
395  QDir qDir;
396  if (force) {
397  qDir.remove(dest);
398  }
399  QFile::copy(src, dest);
400  }
401  // reecrypt all files under the new folder
402  if (destFileInfo.isDir()) {
403  reencryptPath(destFileInfo.absoluteFilePath());
404  } else if (destFileInfo.isFile()) {
405  reencryptPath(destFileInfo.dir().path());
406  }
407 }
408 
413 void ImitatePass::executeGpg(PROCESS id, const QStringList &args, QString input,
414  bool readStdout, bool readStderr) {
415  executeWrapper(id, QtPassSettings::getGpgExecutable(), args, std::move(input),
416  readStdout, readStderr);
417 }
422 void ImitatePass::executeGit(PROCESS id, const QStringList &args, QString input,
423  bool readStdout, bool readStderr) {
424  executeWrapper(id, QtPassSettings::getGitExecutable(), args, std::move(input),
425  readStdout, readStderr);
426 }
427 
438 void ImitatePass::finished(int id, int exitCode, const QString &out,
439  const QString &err) {
440 #ifdef QT_DEBUG
441  dbg() << "Imitate Pass";
442 #endif
443  static QString transactionOutput;
444  PROCESS pid = transactionIsOver(static_cast<PROCESS>(id));
445  transactionOutput.append(out);
446 
447  if (exitCode == 0) {
448  if (pid == INVALID)
449  return;
450  } else {
451  while (pid == INVALID) {
452  id = exec.cancelNext();
453  if (id == -1) {
454  // this is probably irrecoverable and shall not happen
455 #ifdef QT_DEBUG
456  dbg() << "No such transaction!";
457 #endif
458  return;
459  }
460  pid = transactionIsOver(static_cast<PROCESS>(id));
461  }
462  }
463  Pass::finished(pid, exitCode, transactionOutput, err);
464  transactionOutput.clear();
465 }
466 
476 void ImitatePass::executeWrapper(PROCESS id, const QString &app,
477  const QStringList &args, QString input,
478  bool readStdout, bool readStderr) {
479  transactionAdd(id);
480  Pass::executeWrapper(id, app, args, input, readStdout, readStderr);
481 }
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:54
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:63
debughelper.h
ImitatePass::Copy
void Copy(const QString src, const QString dest, const bool force=false) Q_DECL_OVERRIDE
Definition: imitatepass.cpp:377
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:89
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:188
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:134
ImitatePass::Move
void Move(const QString src, const QString dest, const bool force=false) Q_DECL_OVERRIDE
Definition: imitatepass.cpp:339
Pass::getRecipientList
static QStringList getRecipientList(QString for_file)
Pass::getRecipientList return list of gpg-id's to encrypt for.
Definition: pass.cpp:259
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:164
ImitatePass::GitPull_b
virtual void GitPull_b() Q_DECL_OVERRIDE
ImitatePass::GitPull_b git pull wrapper.
Definition: imitatepass.cpp:47
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:35
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:476
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:240
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:42
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:73
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:438
qtpasssettings.h
Enums::PASS_REMOVE
@ PASS_REMOVE
Definition: enums.h:25