QtPass  1.2.0-pre
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 "debughelper.h"
3 #include "qtpasssettings.h"
4 #include <QDirIterator>
5 
6 using namespace Enums;
7 
13 
18  executeGit(GIT_INIT, {"init", QtPassSettings::getPassStore()});
19 }
20 
24 void ImitatePass::GitPull() { executeGit(GIT_PULL, {"pull"}); }
25 
30  exec.executeBlocking(QtPassSettings::getGitExecutable(), {"pull"});
31 }
32 
38  executeGit(GIT_PUSH, {"push"});
39  }
40 }
41 
46 void ImitatePass::Show(QString file) {
47  file = QtPassSettings::getPassStore() + file + ".gpg";
48  QStringList args = {"-d", "--quiet", "--yes", "--no-encrypt-to",
49  "--batch", "--use-agent", file};
50  executeGpg(PASS_SHOW, args);
51 }
52 
60 void ImitatePass::Insert(QString file, QString newValue, bool overwrite) {
61  file = file + ".gpg";
62  transactionHelper trans(this, PASS_INSERT);
63  QStringList recipients = Pass::getRecipientList(file);
64  if (recipients.isEmpty()) {
65  // TODO(bezet): probably throw here
66  emit critical(tr("Can not edit"),
67  tr("Could not read encryption key to use, .gpg-id "
68  "file missing or invalid."));
69  return;
70  }
71  QStringList args = {"--batch", "-eq", "--output", file};
72  for (auto &r : recipients) {
73  args.append("-r");
74  args.append(r);
75  };
76  if (overwrite)
77  args.append("--yes");
78  args.append("-");
79  executeGpg(PASS_INSERT, args, newValue);
81  // TODO(bezet) why not?
82  if (!overwrite)
83  executeGit(GIT_ADD, {"add", file});
84  QString path = QDir(QtPassSettings::getPassStore()).relativeFilePath(file);
85  path.replace(QRegExp("\\.gpg$"), "");
86  QString msg =
87  QString(overwrite ? "Edit" : "Add") + " for " + path + " using QtPass.";
88  GitCommit(file, msg);
89  }
90 }
91 
98 void ImitatePass::GitCommit(const QString &file, const QString &msg) {
99  executeGit(GIT_COMMIT, {"commit", "-m", msg, "--", file});
100 }
101 
105 void ImitatePass::Remove(QString file, bool isDir) {
106  file = QtPassSettings::getPassStore() + file;
107  transactionHelper trans(this, PASS_REMOVE);
108  if (!isDir)
109  file += ".gpg";
110  if (QtPassSettings::isUseGit()) {
111  executeGit(GIT_RM, {"rm", (isDir ? "-rf" : "-f"), file});
112  // TODO(bezet): commit message used to have pass-like file name inside(ie.
113  // getFile(file, true)
114  GitCommit(file, "Remove for " + file + " using QtPass.");
115  } else {
116  if (isDir) {
117 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
118  QDir dir(file);
119  dir.removeRecursively();
120 #else
121  removeDir(QtPassSettings::getPassStore() + file);
122 #endif
123  } else
124  QFile(file).remove();
125  }
126 }
127 
135 void ImitatePass::Init(QString path, const QList<UserInfo> &users) {
136  QString gpgIdFile = path + ".gpg-id";
137  QFile gpgId(gpgIdFile);
138  bool addFile = false;
139  transactionHelper trans(this, PASS_INIT);
140  if (QtPassSettings::isAddGPGId(true)) {
141  QFileInfo checkFile(gpgIdFile);
142  if (!checkFile.exists() || !checkFile.isFile())
143  addFile = true;
144  }
145  if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
146  emit critical(tr("Cannot update"),
147  tr("Failed to open .gpg-id for writing."));
148  return;
149  }
150  bool secret_selected = false;
151  foreach (const UserInfo &user, users) {
152  if (user.enabled) {
153  gpgId.write((user.key_id + "\n").toUtf8());
154  secret_selected |= user.have_secret;
155  }
156  }
157  gpgId.close();
158  if (!secret_selected) {
159  emit critical(
160  tr("Check selected users!"),
161  tr("None of the selected keys have a secret key available.\n"
162  "You will not be able to decrypt any newly added passwords!"));
163  return;
164  }
165 
167  !QtPassSettings::getGitExecutable().isEmpty()) {
168  if (addFile)
169  executeGit(GIT_ADD, {"add", gpgIdFile});
170  QString path = gpgIdFile;
171  path.replace(QRegExp("\\.gpg$"), "");
172  GitCommit(gpgIdFile, "Added " + path + " using QtPass.");
173  }
174  reencryptPath(path);
175 }
176 
182 bool ImitatePass::removeDir(const QString &dirName) {
183  bool result = true;
184  QDir dir(dirName);
185 
186  if (dir.exists(dirName)) {
187  Q_FOREACH (QFileInfo info,
188  dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System |
189  QDir::Hidden | QDir::AllDirs | QDir::Files,
190  QDir::DirsFirst)) {
191  if (info.isDir())
192  result = removeDir(info.absoluteFilePath());
193  else
194  result = QFile::remove(info.absoluteFilePath());
195 
196  if (!result)
197  return result;
198  }
199  result = dir.rmdir(dirName);
200  }
201  return result;
202 }
203 
211 void ImitatePass::reencryptPath(QString dir) {
212  emit statusMsg(tr("Re-encrypting from folder %1").arg(dir), 3000);
213  emit startReencryptPath();
215  // TODO(bezet): move statuses inside actions?
216  emit statusMsg(tr("Updating password-store"), 2000);
217  GitPull_b();
218  }
219  QDir currentDir;
220  QDirIterator gpgFiles(dir, QStringList() << "*.gpg", QDir::Files,
221  QDirIterator::Subdirectories);
222  QStringList gpgId;
223  while (gpgFiles.hasNext()) {
224  QString fileName = gpgFiles.next();
225  if (gpgFiles.fileInfo().path() != currentDir.path()) {
226  gpgId = getRecipientList(fileName);
227  gpgId.sort();
228  }
229  // TODO(bezet): enable --with-colons for better future-proofness?
230  QStringList args = {
231  "-v", "--no-secmem-warning", "--no-permission-warning",
232  "--list-only", "--keyid-format=long", fileName};
233  QString keys, err;
234  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, &keys, &err);
235  QStringList actualKeys;
236  keys += err;
237  QStringList key = keys.split("\n");
238  QListIterator<QString> itr(key);
239  while (itr.hasNext()) {
240  QString current = itr.next();
241  QStringList cur = current.split(" ");
242  if (cur.length() > 4) {
243  QString actualKey = cur.takeAt(4);
244  if (actualKey.length() == 16) {
245  actualKeys << actualKey;
246  }
247  }
248  }
249  actualKeys.sort();
250  if (actualKeys != gpgId) {
251  // dbg()<< actualKeys << gpgId << getRecipientList(fileName);
252  dbg() << "reencrypt " << fileName << " for " << gpgId;
253  QString local_lastDecrypt = "Could not decrypt";
254  args = QStringList{"-d", "--quiet", "--yes", "--no-encrypt-to",
255  "--batch", "--use-agent", fileName};
256  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
257  &local_lastDecrypt);
258 
259  if (!local_lastDecrypt.isEmpty() &&
260  local_lastDecrypt != "Could not decrypt") {
261  if (local_lastDecrypt.right(1) != "\n")
262  local_lastDecrypt += "\n";
263 
264  QStringList recipients = Pass::getRecipientList(fileName);
265  if (recipients.isEmpty()) {
266  emit critical(tr("Can not edit"),
267  tr("Could not read encryption key to use, .gpg-id "
268  "file missing or invalid."));
269  return;
270  }
271  args = QStringList{"--yes", "--batch", "-eq", "--output", fileName};
272  for (auto &i : recipients) {
273  args.append("-r");
274  args.append(i);
275  }
276  args.append("-");
277  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
278  local_lastDecrypt);
279 
281  exec.executeBlocking(QtPassSettings::getGitExecutable(),
282  {"add", fileName});
283  QString path =
284  QDir(QtPassSettings::getPassStore()).relativeFilePath(fileName);
285  path.replace(QRegExp("\\.gpg$"), "");
286  exec.executeBlocking(QtPassSettings::getGitExecutable(),
287  {"commit", fileName, "-m",
288  "Edit for " + path + " using QtPass."});
289  }
290 
291  } else {
292  dbg() << "Decrypt error on re-encrypt";
293  }
294  }
295  }
297  emit statusMsg(tr("Updating password-store"), 2000);
298  // TODO(bezet): this is non-blocking and shall be done outside
299  GitPush();
300  }
301  emit endReencryptPath();
302 }
303 
304 void ImitatePass::Move(const QString src, const QString dest,
305  const bool force) {
306  QFileInfo destFileInfo(dest);
307  transactionHelper trans(this, PASS_MOVE);
308  if (QtPassSettings::isUseGit()) {
309  QStringList args;
310  args << "mv";
311  if (force) {
312  args << "-f";
313  }
314  args << src;
315  args << dest;
316  executeGit(GIT_MOVE, args);
317 
318  QString message = QString("moved from %1 to %2 using QTPass.");
319  message = message.arg(src).arg(dest);
320  GitCommit("", message);
321  } else {
322  QDir qDir;
323  QFileInfo srcFileInfo(src);
324  QString destCopy = dest;
325  if (srcFileInfo.isFile() && destFileInfo.isDir()) {
326  destCopy = destFileInfo.absoluteFilePath() + QDir::separator() +
327  srcFileInfo.fileName();
328  }
329  if (force) {
330  qDir.remove(destCopy);
331  }
332  qDir.rename(src, destCopy);
333  }
334  // reecrypt all files under the new folder
335  if (destFileInfo.isDir()) {
336  reencryptPath(destFileInfo.absoluteFilePath());
337  } else if (destFileInfo.isFile()) {
338  reencryptPath(destFileInfo.dir().path());
339  }
340 }
341 
342 void ImitatePass::Copy(const QString src, const QString dest,
343  const bool force) {
344  QFileInfo destFileInfo(dest);
345  transactionHelper trans(this, PASS_COPY);
346  if (QtPassSettings::isUseGit()) {
347  QStringList args;
348  args << "cp";
349  if (force) {
350  args << "-f";
351  }
352  args << src;
353  args << dest;
354  executeGit(GIT_COPY, args);
355 
356  QString message = QString("copied from %1 to %2 using QTPass.");
357  message = message.arg(src).arg(dest);
358  GitCommit("", message);
359  } else {
360  QDir qDir;
361  if (force) {
362  qDir.remove(dest);
363  }
364  QFile::copy(src, dest);
365  }
366  // reecrypt all files under the new folder
367  if (destFileInfo.isDir()) {
368  reencryptPath(destFileInfo.absoluteFilePath());
369  } else if (destFileInfo.isFile()) {
370  reencryptPath(destFileInfo.dir().path());
371  }
372 }
373 
378 void ImitatePass::executeGpg(PROCESS id, const QStringList &args, QString input,
379  bool readStdout, bool readStderr) {
380  executeWrapper(id, QtPassSettings::getGpgExecutable(), args, input,
381  readStdout, readStderr);
382 }
387 void ImitatePass::executeGit(PROCESS id, const QStringList &args, QString input,
388  bool readStdout, bool readStderr) {
389  executeWrapper(id, QtPassSettings::getGitExecutable(), args, input,
390  readStdout, readStderr);
391 }
392 
403 void ImitatePass::finished(int id, int exitCode, const QString &out,
404  const QString &err) {
405  dbg() << "Imitate Pass";
406  static QString transactionOutput;
407  PROCESS pid = transactionIsOver(static_cast<PROCESS>(id));
408  transactionOutput.append(out);
409 
410  if (exitCode == 0) {
411  if (pid == INVALID)
412  return;
413  } else {
414  while (pid == INVALID) {
415  id = exec.cancelNext();
416  if (id == -1) {
417  // this is probably irrecoverable and shall not happen
418  dbg() << "No such transaction!";
419  return;
420  }
421  pid = transactionIsOver(static_cast<PROCESS>(id));
422  }
423  }
424  Pass::finished(pid, exitCode, transactionOutput, err);
425  transactionOutput.clear();
426 }
427 
437 void ImitatePass::executeWrapper(PROCESS id, const QString &app,
438  const QStringList &args, QString input,
439  bool readStdout, bool readStderr) {
440  transactionAdd(id);
441  Pass::executeWrapper(id, app, args, input, readStdout, readStderr);
442 }
void reencryptPath(QString dir)
ImitatePass::reencryptPath reencrypt all files under the chosen directory.
virtual void Init(QString path, const QList< UserInfo > &list) Q_DECL_OVERRIDE
ImitatePass::Init initialize pass repository.
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...
virtual void GitInit() Q_DECL_OVERRIDE
ImitatePass::GitInit git init wrapper.
Definition: imitatepass.cpp:17
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
PROCESS
Definition: enums.h:16
bool enabled
UserInfo::enabled.
Definition: datahelpers.h:85
#define dbg()
Definition: debughelper.h:7
QString key_id
UserInfo::key_id hexadecimal representation.
Definition: datahelpers.h:71
bool have_secret
UserInfo::have_secret secret key is available (can decrypt with this key)
Definition: datahelpers.h:81
ImitatePass()
ImitatePass::ImitatePass for situaions when pass is not available we imitate the behavior of pass htt...
Definition: imitatepass.cpp:12
static bool isUseWebDav(const bool &defaultValue=QVariant().toBool())
virtual void Remove(QString file, bool isDir=false) Q_DECL_OVERRIDE
ImitatePass::Remove custom implementation of "pass remove".
static bool isAddGPGId(const bool &defaultValue=QVariant().toBool())
virtual void Insert(QString file, QString value, bool overwrite=false) Q_DECL_OVERRIDE
ImitatePass::Insert create new file with encrypted content.
Definition: imitatepass.cpp:60
void Move(const QString src, const QString dest, const bool force=false) Q_DECL_OVERRIDE
static bool isUseGit(const bool &defaultValue=QVariant().toBool())
static QString getPassStore(const QString &defaultValue=QVariant().toString())
virtual void GitPull() Q_DECL_OVERRIDE
ImitatePass::GitPull git init wrapper.
Definition: imitatepass.cpp:24
Enumerators for configuration and runtime items.
static QString getGitExecutable(const QString &defaultValue=QVariant().toString())
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:170
static QStringList getRecipientList(QString for_file)
Pass::getRecipientList return list of gpg-id&#39;s to encrypt for.
Definition: pass.cpp:239
virtual void GitPush() Q_DECL_OVERRIDE
ImitatePass::GitPush git init wrapper.
Definition: imitatepass.cpp:36
void executeWrapper(PROCESS id, const QString &app, const QStringList &args, bool readStdout=true, bool readStderr=true)
Definition: pass.cpp:27
Stores key info lines including validity, creation date and more.
Definition: datahelpers.h:46
static QString getGpgExecutable(const QString &defaultValue=QVariant().toString())
virtual void Show(QString file) Q_DECL_OVERRIDE
ImitatePass::Show shows content of file.
Definition: imitatepass.cpp:46
virtual void GitPull_b() Q_DECL_OVERRIDE
ImitatePass::GitPull_b git pull wrapper.
Definition: imitatepass.cpp:29
void Copy(const QString src, const QString dest, const bool force=false) Q_DECL_OVERRIDE
static bool isAutoPull(const bool &defaultValue=QVariant().toBool())
static bool isAutoPush(const bool &defaultValue=QVariant().toBool())