QtPass  1.2.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 
22  executeGit(GIT_INIT, {"init", QtPassSettings::getPassStore()});
23 }
24 
28 void ImitatePass::GitPull() { executeGit(GIT_PULL, {"pull"}); }
29 
34  exec.executeBlocking(QtPassSettings::getGitExecutable(), {"pull"});
35 }
36 
42  executeGit(GIT_PUSH, {"push"});
43  }
44 }
45 
49 void ImitatePass::Show(QString file) {
50  file = QtPassSettings::getPassStore() + file + ".gpg";
51  QStringList args = {"-d", "--quiet", "--yes", "--no-encrypt-to",
52  "--batch", "--use-agent", file};
53  executeGpg(PASS_SHOW, args);
54 }
55 
59 void ImitatePass::OtpGenerate(QString file) {
60 #ifdef QT_DEBUG
61  dbg() << "No OTP geconst neratio&n code for fake pass yet, attempting for file: " +
62  file;
63 #else
64  Q_UNUSED(file)
65 #endif
66 }
67 
75 void ImitatePass::Insert(QString file, QString newValue, bool overwrite) {
76  file = file + ".gpg";
77  transactionHelper trans(this, PASS_INSERT);
78  QStringList recipients = Pass::getRecipientList(file);
79  if (recipients.isEmpty()) {
80  // TODO(bezet): probably throw here
81  emit critical(tr("Can not edit"),
82  tr("Could not read encryption key to use, .gpg-id "
83  "file missing or invalid."));
84  return;
85  }
86  QStringList args = {"--batch", "-eq", "--output", file};
87  for (auto &r : recipients) {
88  args.append("-r");
89  args.append(r);
90  };
91  if (overwrite)
92  args.append("--yes");
93  args.append("-");
94  executeGpg(PASS_INSERT, args, newValue);
96  // TODO(bezet) why not?
97  if (!overwrite)
98  executeGit(GIT_ADD, {"add", file});
99  QString path = QDir(QtPassSettings::getPassStore()).relativeFilePath(file);
100  path.replace(QRegExp("\\.gpg$"), "");
101  QString msg =
102  QString(overwrite ? "Edit" : "Add") + " for " + path + " using QtPass.";
103  GitCommit(file, msg);
104  }
105 }
106 
113 void ImitatePass::GitCommit(const QString &file, const QString &msg) {
114  executeGit(GIT_COMMIT, {"commit", "-m", msg, "--", file});
115 }
116 
120 void ImitatePass::Remove(QString file, bool isDir) {
121  file = QtPassSettings::getPassStore() + file;
122  transactionHelper trans(this, PASS_REMOVE);
123  if (!isDir)
124  file += ".gpg";
125  if (QtPassSettings::isUseGit()) {
126  executeGit(GIT_RM, {"rm", (isDir ? "-rf" : "-f"), file});
127  // TODO(bezet): commit message used to have pass-like file name inside(ie.
128  // getFile(file, true)
129  GitCommit(file, "Remove for " + file + " using QtPass.");
130  } else {
131  if (isDir) {
132 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
133  QDir dir(file);
134  dir.removeRecursively();
135 #else
136  removeDir(QtPassSettings::getPassStore() + file);
137 #endif
138  } else
139  QFile(file).remove();
140  }
141 }
142 
150 void ImitatePass::Init(QString path, const QList<UserInfo> &users) {
151  QString gpgIdFile = path + ".gpg-id";
152  QFile gpgId(gpgIdFile);
153  bool addFile = false;
154  transactionHelper trans(this, PASS_INIT);
155  if (QtPassSettings::isAddGPGId(true)) {
156  QFileInfo checkFile(gpgIdFile);
157  if (!checkFile.exists() || !checkFile.isFile())
158  addFile = true;
159  }
160  if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
161  emit critical(tr("Cannot update"),
162  tr("Failed to open .gpg-id for writing."));
163  return;
164  }
165  bool secret_selected = false;
166  foreach (const UserInfo &user, users) {
167  if (user.enabled) {
168  gpgId.write((user.key_id + "\n").toUtf8());
169  secret_selected |= user.have_secret;
170  }
171  }
172  gpgId.close();
173  if (!secret_selected) {
174  emit critical(
175  tr("Check selected users!"),
176  tr("None of the selected keys have a secret key available.\n"
177  "You will not be able to decrypt any newly added passwords!"));
178  return;
179  }
180 
182  !QtPassSettings::getGitExecutable().isEmpty()) {
183  if (addFile)
184  executeGit(GIT_ADD, {"add", gpgIdFile});
185  QString path = gpgIdFile;
186  path.replace(QRegExp("\\.gpg$"), "");
187  GitCommit(gpgIdFile, "Added " + path + " using QtPass.");
188  }
189  reencryptPath(path);
190 }
191 
197 bool ImitatePass::removeDir(const QString &dirName) {
198  bool result = true;
199  QDir dir(dirName);
200 
201  if (dir.exists(dirName)) {
202  Q_FOREACH (QFileInfo info,
203  dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System |
204  QDir::Hidden | QDir::AllDirs | QDir::Files,
205  QDir::DirsFirst)) {
206  if (info.isDir())
207  result = removeDir(info.absoluteFilePath());
208  else
209  result = QFile::remove(info.absoluteFilePath());
210 
211  if (!result)
212  return result;
213  }
214  result = dir.rmdir(dirName);
215  }
216  return result;
217 }
218 
226 void ImitatePass::reencryptPath(const QString& dir) {
227  emit statusMsg(tr("Re-encrypting from folder %1").arg(dir), 3000);
228  emit startReencryptPath();
230  // TODO(bezet): move statuses inside actions?
231  emit statusMsg(tr("Updating password-store"), 2000);
232  GitPull_b();
233  }
234  QDir currentDir;
235  QDirIterator gpgFiles(dir, QStringList() << "*.gpg", QDir::Files,
236  QDirIterator::Subdirectories);
237  QStringList gpgId;
238  while (gpgFiles.hasNext()) {
239  QString fileName = gpgFiles.next();
240  if (gpgFiles.fileInfo().path() != currentDir.path()) {
241  gpgId = getRecipientList(fileName);
242  gpgId.sort();
243  }
244  // TODO(bezet): enable --with-colons for better future-proofness?
245  QStringList args = {
246  "-v", "--no-secmem-warning", "--no-permission-warning",
247  "--list-only", "--keyid-format=long", fileName};
248  QString keys, err;
249  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, &keys, &err);
250  QStringList actualKeys;
251  keys += err;
252  QStringList key = keys.split("\n");
253  QListIterator<QString> itr(key);
254  while (itr.hasNext()) {
255  QString current = itr.next();
256  QStringList cur = current.split(" ");
257  if (cur.length() > 4) {
258  QString actualKey = cur.takeAt(4);
259  if (actualKey.length() == 16) {
260  actualKeys << actualKey;
261  }
262  }
263  }
264  actualKeys.sort();
265  if (actualKeys != gpgId) {
266  // dbg()<< actualKeys << gpgId << getRecipientList(fileName);
267 #ifdef QT_DEBUG
268  dbg() << "reencrypt " << fileName << " for " << gpgId;
269 #endif
270  QString local_lastDecrypt = "Could not decrypt";
271  args = QStringList{"-d", "--quiet", "--yes", "--no-encrypt-to",
272  "--batch", "--use-agent", fileName};
273  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
274  &local_lastDecrypt);
275 
276  if (!local_lastDecrypt.isEmpty() &&
277  local_lastDecrypt != "Could not decrypt") {
278  if (local_lastDecrypt.right(1) != "\n")
279  local_lastDecrypt += "\n";
280 
281  QStringList recipients = Pass::getRecipientList(fileName);
282  if (recipients.isEmpty()) {
283  emit critical(tr("Can not edit"),
284  tr("Could not read encryption key to use, .gpg-id "
285  "file missing or invalid."));
286  return;
287  }
288  args = QStringList{"--yes", "--batch", "-eq", "--output", fileName};
289  for (auto &i : recipients) {
290  args.append("-r");
291  args.append(i);
292  }
293  args.append("-");
294  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
295  local_lastDecrypt);
296 
298  exec.executeBlocking(QtPassSettings::getGitExecutable(),
299  {"add", fileName});
300  QString path =
301  QDir(QtPassSettings::getPassStore()).relativeFilePath(fileName);
302  path.replace(QRegExp("\\.gpg$"), "");
303  exec.executeBlocking(QtPassSettings::getGitExecutable(),
304  {"commit", fileName, "-m",
305  "Edit for " + path + " using QtPass."});
306  }
307 
308  } else {
309 #ifdef QT_DEBUG
310  dbg() << "Decrypt error on re-encrypt";
311 #endif
312  }
313  }
314  }
316  emit statusMsg(tr("Updating password-store"), 2000);
317  // TODO(bezet): this is non-blocking and shall be done outside
318  GitPush();
319  }
320  emit endReencryptPath();
321 }
322 
323 void ImitatePass::Move(const QString src, const QString dest,
324  const bool force) {
325  QFileInfo destFileInfo(dest);
326  transactionHelper trans(this, PASS_MOVE);
327  if (QtPassSettings::isUseGit()) {
328  QStringList args;
329  args << "mv";
330  if (force) {
331  args << "-f";
332  }
333  args << src;
334  args << dest;
335  executeGit(GIT_MOVE, args);
336 
337  QString message = QString("moved from %1 to %2 using QTPass.");
338  message = message.arg(src).arg(dest);
339  GitCommit("", message);
340  } else {
341  QDir qDir;
342  QFileInfo srcFileInfo(src);
343  QString destCopy = dest;
344  if (srcFileInfo.isFile() && destFileInfo.isDir()) {
345  destCopy = destFileInfo.absoluteFilePath() + QDir::separator() +
346  srcFileInfo.fileName();
347  }
348  if (force) {
349  qDir.remove(destCopy);
350  }
351  qDir.rename(src, destCopy);
352  }
353  // reecrypt all files under the new folder
354  if (destFileInfo.isDir()) {
355  reencryptPath(destFileInfo.absoluteFilePath());
356  } else if (destFileInfo.isFile()) {
357  reencryptPath(destFileInfo.dir().path());
358  }
359 }
360 
361 void ImitatePass::Copy(const QString src, const QString dest,
362  const bool force) {
363  QFileInfo destFileInfo(dest);
364  transactionHelper trans(this, PASS_COPY);
365  if (QtPassSettings::isUseGit()) {
366  QStringList args;
367  args << "cp";
368  if (force) {
369  args << "-f";
370  }
371  args << src;
372  args << dest;
373  executeGit(GIT_COPY, args);
374 
375  QString message = QString("copied from %1 to %2 using QTPass.");
376  message = message.arg(src).arg(dest);
377  GitCommit("", message);
378  } else {
379  QDir qDir;
380  if (force) {
381  qDir.remove(dest);
382  }
383  QFile::copy(src, dest);
384  }
385  // reecrypt all files under the new folder
386  if (destFileInfo.isDir()) {
387  reencryptPath(destFileInfo.absoluteFilePath());
388  } else if (destFileInfo.isFile()) {
389  reencryptPath(destFileInfo.dir().path());
390  }
391 }
392 
397 void ImitatePass::executeGpg(PROCESS id, const QStringList &args, QString input,
398  bool readStdout, bool readStderr) {
399  executeWrapper(id, QtPassSettings::getGpgExecutable(), args, std::move(input),
400  readStdout, readStderr);
401 }
406 void ImitatePass::executeGit(PROCESS id, const QStringList &args, QString input,
407  bool readStdout, bool readStderr) {
408  executeWrapper(id, QtPassSettings::getGitExecutable(), args, std::move(input),
409  readStdout, readStderr);
410 }
411 
422 void ImitatePass::finished(int id, int exitCode, const QString &out,
423  const QString &err) {
424 #ifdef QT_DEBUG
425  dbg() << "Imitate Pass";
426 #endif
427  static QString transactionOutput;
428  PROCESS pid = transactionIsOver(static_cast<PROCESS>(id));
429  transactionOutput.append(out);
430 
431  if (exitCode == 0) {
432  if (pid == INVALID)
433  return;
434  } else {
435  while (pid == INVALID) {
436  id = exec.cancelNext();
437  if (id == -1) {
438  // this is probably irrecoverable and shall not happen
439 #ifdef QT_DEBUG
440  dbg() << "No such transaction!";
441 #endif
442  return;
443  }
444  pid = transactionIsOver(static_cast<PROCESS>(id));
445  }
446  }
447  Pass::finished(pid, exitCode, transactionOutput, err);
448  transactionOutput.clear();
449 }
450 
460 void ImitatePass::executeWrapper(PROCESS id, const QString &app,
461  const QStringList &args, QString input,
462  bool readStdout, bool readStderr) {
463  transactionAdd(id);
464  Pass::executeWrapper(id, app, args, input, readStdout, readStderr);
465 }
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 OtpGenerate(QString file) Q_DECL_OVERRIDE
ImitatePass::OtpGenerate generates an otp code.
Definition: imitatepass.cpp:59
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:21
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: userinfo.h:50
#define dbg()
Definition: debughelper.h:7
QString key_id
UserInfo::key_id hexadecimal representation.
Definition: userinfo.h:36
bool have_secret
UserInfo::have_secret secret key is available (can decrypt with this key)
Definition: userinfo.h:46
ImitatePass()
ImitatePass::ImitatePass for situaions when pass is not available we imitate the behavior of pass htt...
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:75
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:28
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:187
static QStringList getRecipientList(QString for_file)
Pass::getRecipientList return list of gpg-id&#39;s to encrypt for.
Definition: pass.cpp:258
virtual void GitPush() Q_DECL_OVERRIDE
ImitatePass::GitPush git init wrapper.
Definition: imitatepass.cpp:40
void executeWrapper(PROCESS id, const QString &app, const QStringList &args, bool readStdout=true, bool readStderr=true)
Definition: pass.cpp:30
Stores key info lines including validity, creation date and more.
Definition: userinfo.h:11
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:49
virtual void GitPull_b() Q_DECL_OVERRIDE
ImitatePass::GitPull_b git pull wrapper.
Definition: imitatepass.cpp:33
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())