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 "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 
45 void ImitatePass::Show(QString file) {
46  file = QtPassSettings::getPassStore() + file + ".gpg";
47  QStringList args = {"-d", "--quiet", "--yes", "--no-encrypt-to",
48  "--batch", "--use-agent", file};
49  executeGpg(PASS_SHOW, args);
50 }
51 
55 void ImitatePass::OtpGenerate(QString file) {
56  dbg() << "No OTP generation code for fake pass yet, attempting for file: " +
57  file;
58 }
59 
67 void ImitatePass::Insert(QString file, QString newValue, bool overwrite) {
68  file = file + ".gpg";
69  transactionHelper trans(this, PASS_INSERT);
70  QStringList recipients = Pass::getRecipientList(file);
71  if (recipients.isEmpty()) {
72  // TODO(bezet): probably throw here
73  emit critical(tr("Can not edit"),
74  tr("Could not read encryption key to use, .gpg-id "
75  "file missing or invalid."));
76  return;
77  }
78  QStringList args = {"--batch", "-eq", "--output", file};
79  for (auto &r : recipients) {
80  args.append("-r");
81  args.append(r);
82  };
83  if (overwrite)
84  args.append("--yes");
85  args.append("-");
86  executeGpg(PASS_INSERT, args, newValue);
88  // TODO(bezet) why not?
89  if (!overwrite)
90  executeGit(GIT_ADD, {"add", file});
91  QString path = QDir(QtPassSettings::getPassStore()).relativeFilePath(file);
92  path.replace(QRegExp("\\.gpg$"), "");
93  QString msg =
94  QString(overwrite ? "Edit" : "Add") + " for " + path + " using QtPass.";
95  GitCommit(file, msg);
96  }
97 }
98 
105 void ImitatePass::GitCommit(const QString &file, const QString &msg) {
106  executeGit(GIT_COMMIT, {"commit", "-m", msg, "--", file});
107 }
108 
112 void ImitatePass::Remove(QString file, bool isDir) {
113  file = QtPassSettings::getPassStore() + file;
114  transactionHelper trans(this, PASS_REMOVE);
115  if (!isDir)
116  file += ".gpg";
117  if (QtPassSettings::isUseGit()) {
118  executeGit(GIT_RM, {"rm", (isDir ? "-rf" : "-f"), file});
119  // TODO(bezet): commit message used to have pass-like file name inside(ie.
120  // getFile(file, true)
121  GitCommit(file, "Remove for " + file + " using QtPass.");
122  } else {
123  if (isDir) {
124 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
125  QDir dir(file);
126  dir.removeRecursively();
127 #else
128  removeDir(QtPassSettings::getPassStore() + file);
129 #endif
130  } else
131  QFile(file).remove();
132  }
133 }
134 
142 void ImitatePass::Init(QString path, const QList<UserInfo> &users) {
143  QString gpgIdFile = path + ".gpg-id";
144  QFile gpgId(gpgIdFile);
145  bool addFile = false;
146  transactionHelper trans(this, PASS_INIT);
147  if (QtPassSettings::isAddGPGId(true)) {
148  QFileInfo checkFile(gpgIdFile);
149  if (!checkFile.exists() || !checkFile.isFile())
150  addFile = true;
151  }
152  if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
153  emit critical(tr("Cannot update"),
154  tr("Failed to open .gpg-id for writing."));
155  return;
156  }
157  bool secret_selected = false;
158  foreach (const UserInfo &user, users) {
159  if (user.enabled) {
160  gpgId.write((user.key_id + "\n").toUtf8());
161  secret_selected |= user.have_secret;
162  }
163  }
164  gpgId.close();
165  if (!secret_selected) {
166  emit critical(
167  tr("Check selected users!"),
168  tr("None of the selected keys have a secret key available.\n"
169  "You will not be able to decrypt any newly added passwords!"));
170  return;
171  }
172 
174  !QtPassSettings::getGitExecutable().isEmpty()) {
175  if (addFile)
176  executeGit(GIT_ADD, {"add", gpgIdFile});
177  QString path = gpgIdFile;
178  path.replace(QRegExp("\\.gpg$"), "");
179  GitCommit(gpgIdFile, "Added " + path + " using QtPass.");
180  }
181  reencryptPath(path);
182 }
183 
189 bool ImitatePass::removeDir(const QString &dirName) {
190  bool result = true;
191  QDir dir(dirName);
192 
193  if (dir.exists(dirName)) {
194  Q_FOREACH (QFileInfo info,
195  dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System |
196  QDir::Hidden | QDir::AllDirs | QDir::Files,
197  QDir::DirsFirst)) {
198  if (info.isDir())
199  result = removeDir(info.absoluteFilePath());
200  else
201  result = QFile::remove(info.absoluteFilePath());
202 
203  if (!result)
204  return result;
205  }
206  result = dir.rmdir(dirName);
207  }
208  return result;
209 }
210 
218 void ImitatePass::reencryptPath(QString dir) {
219  emit statusMsg(tr("Re-encrypting from folder %1").arg(dir), 3000);
220  emit startReencryptPath();
222  // TODO(bezet): move statuses inside actions?
223  emit statusMsg(tr("Updating password-store"), 2000);
224  GitPull_b();
225  }
226  QDir currentDir;
227  QDirIterator gpgFiles(dir, QStringList() << "*.gpg", QDir::Files,
228  QDirIterator::Subdirectories);
229  QStringList gpgId;
230  while (gpgFiles.hasNext()) {
231  QString fileName = gpgFiles.next();
232  if (gpgFiles.fileInfo().path() != currentDir.path()) {
233  gpgId = getRecipientList(fileName);
234  gpgId.sort();
235  }
236  // TODO(bezet): enable --with-colons for better future-proofness?
237  QStringList args = {
238  "-v", "--no-secmem-warning", "--no-permission-warning",
239  "--list-only", "--keyid-format=long", fileName};
240  QString keys, err;
241  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, &keys, &err);
242  QStringList actualKeys;
243  keys += err;
244  QStringList key = keys.split("\n");
245  QListIterator<QString> itr(key);
246  while (itr.hasNext()) {
247  QString current = itr.next();
248  QStringList cur = current.split(" ");
249  if (cur.length() > 4) {
250  QString actualKey = cur.takeAt(4);
251  if (actualKey.length() == 16) {
252  actualKeys << actualKey;
253  }
254  }
255  }
256  actualKeys.sort();
257  if (actualKeys != gpgId) {
258  // dbg()<< actualKeys << gpgId << getRecipientList(fileName);
259  dbg() << "reencrypt " << fileName << " for " << gpgId;
260  QString local_lastDecrypt = "Could not decrypt";
261  args = QStringList{"-d", "--quiet", "--yes", "--no-encrypt-to",
262  "--batch", "--use-agent", fileName};
263  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
264  &local_lastDecrypt);
265 
266  if (!local_lastDecrypt.isEmpty() &&
267  local_lastDecrypt != "Could not decrypt") {
268  if (local_lastDecrypt.right(1) != "\n")
269  local_lastDecrypt += "\n";
270 
271  QStringList recipients = Pass::getRecipientList(fileName);
272  if (recipients.isEmpty()) {
273  emit critical(tr("Can not edit"),
274  tr("Could not read encryption key to use, .gpg-id "
275  "file missing or invalid."));
276  return;
277  }
278  args = QStringList{"--yes", "--batch", "-eq", "--output", fileName};
279  for (auto &i : recipients) {
280  args.append("-r");
281  args.append(i);
282  }
283  args.append("-");
284  exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
285  local_lastDecrypt);
286 
288  exec.executeBlocking(QtPassSettings::getGitExecutable(),
289  {"add", fileName});
290  QString path =
291  QDir(QtPassSettings::getPassStore()).relativeFilePath(fileName);
292  path.replace(QRegExp("\\.gpg$"), "");
293  exec.executeBlocking(QtPassSettings::getGitExecutable(),
294  {"commit", fileName, "-m",
295  "Edit for " + path + " using QtPass."});
296  }
297 
298  } else {
299  dbg() << "Decrypt error on re-encrypt";
300  }
301  }
302  }
304  emit statusMsg(tr("Updating password-store"), 2000);
305  // TODO(bezet): this is non-blocking and shall be done outside
306  GitPush();
307  }
308  emit endReencryptPath();
309 }
310 
311 void ImitatePass::Move(const QString src, const QString dest,
312  const bool force) {
313  QFileInfo destFileInfo(dest);
314  transactionHelper trans(this, PASS_MOVE);
315  if (QtPassSettings::isUseGit()) {
316  QStringList args;
317  args << "mv";
318  if (force) {
319  args << "-f";
320  }
321  args << src;
322  args << dest;
323  executeGit(GIT_MOVE, args);
324 
325  QString message = QString("moved from %1 to %2 using QTPass.");
326  message = message.arg(src).arg(dest);
327  GitCommit("", message);
328  } else {
329  QDir qDir;
330  QFileInfo srcFileInfo(src);
331  QString destCopy = dest;
332  if (srcFileInfo.isFile() && destFileInfo.isDir()) {
333  destCopy = destFileInfo.absoluteFilePath() + QDir::separator() +
334  srcFileInfo.fileName();
335  }
336  if (force) {
337  qDir.remove(destCopy);
338  }
339  qDir.rename(src, destCopy);
340  }
341  // reecrypt all files under the new folder
342  if (destFileInfo.isDir()) {
343  reencryptPath(destFileInfo.absoluteFilePath());
344  } else if (destFileInfo.isFile()) {
345  reencryptPath(destFileInfo.dir().path());
346  }
347 }
348 
349 void ImitatePass::Copy(const QString src, const QString dest,
350  const bool force) {
351  QFileInfo destFileInfo(dest);
352  transactionHelper trans(this, PASS_COPY);
353  if (QtPassSettings::isUseGit()) {
354  QStringList args;
355  args << "cp";
356  if (force) {
357  args << "-f";
358  }
359  args << src;
360  args << dest;
361  executeGit(GIT_COPY, args);
362 
363  QString message = QString("copied from %1 to %2 using QTPass.");
364  message = message.arg(src).arg(dest);
365  GitCommit("", message);
366  } else {
367  QDir qDir;
368  if (force) {
369  qDir.remove(dest);
370  }
371  QFile::copy(src, dest);
372  }
373  // reecrypt all files under the new folder
374  if (destFileInfo.isDir()) {
375  reencryptPath(destFileInfo.absoluteFilePath());
376  } else if (destFileInfo.isFile()) {
377  reencryptPath(destFileInfo.dir().path());
378  }
379 }
380 
385 void ImitatePass::executeGpg(PROCESS id, const QStringList &args, QString input,
386  bool readStdout, bool readStderr) {
387  executeWrapper(id, QtPassSettings::getGpgExecutable(), args, input,
388  readStdout, readStderr);
389 }
394 void ImitatePass::executeGit(PROCESS id, const QStringList &args, QString input,
395  bool readStdout, bool readStderr) {
396  executeWrapper(id, QtPassSettings::getGitExecutable(), args, input,
397  readStdout, readStderr);
398 }
399 
410 void ImitatePass::finished(int id, int exitCode, const QString &out,
411  const QString &err) {
412  dbg() << "Imitate Pass";
413  static QString transactionOutput;
414  PROCESS pid = transactionIsOver(static_cast<PROCESS>(id));
415  transactionOutput.append(out);
416 
417  if (exitCode == 0) {
418  if (pid == INVALID)
419  return;
420  } else {
421  while (pid == INVALID) {
422  id = exec.cancelNext();
423  if (id == -1) {
424  // this is probably irrecoverable and shall not happen
425  dbg() << "No such transaction!";
426  return;
427  }
428  pid = transactionIsOver(static_cast<PROCESS>(id));
429  }
430  }
431  Pass::finished(pid, exitCode, transactionOutput, err);
432  transactionOutput.clear();
433 }
434 
444 void ImitatePass::executeWrapper(PROCESS id, const QString &app,
445  const QStringList &args, QString input,
446  bool readStdout, bool readStderr) {
447  transactionAdd(id);
448  Pass::executeWrapper(id, app, args, input, readStdout, readStderr);
449 }
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:55
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: 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...
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:67
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:178
static QStringList getRecipientList(QString for_file)
Pass::getRecipientList return list of gpg-id&#39;s to encrypt for.
Definition: pass.cpp:247
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:25
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:45
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())