LCOV - code coverage report
Current view: top level - src - imitatepass.cpp (source / functions) Hit Total Coverage
Test: .lcov.total Lines: 1 272 0.4 %
Date: 2018-06-12 11:27:04 Functions: 2 20 10.0 %

          Line data    Source code
       1             : #include "imitatepass.h"
       2             : #include "debughelper.h"
       3             : #include "qtpasssettings.h"
       4             : #include <QDirIterator>
       5             : 
       6             : using namespace Enums;
       7             : 
       8             : /**
       9             :  * @brief ImitatePass::ImitatePass for situaions when pass is not available
      10             :  * we imitate the behavior of pass https://www.passwordstore.org/
      11             :  */
      12           8 : ImitatePass::ImitatePass() {}
      13             : 
      14             : /**
      15             :  * @brief ImitatePass::GitInit git init wrapper
      16             :  */
      17           0 : void ImitatePass::GitInit() {
      18           0 :   executeGit(GIT_INIT, {"init", QtPassSettings::getPassStore()});
      19           0 : }
      20             : 
      21             : /**
      22             :  * @brief ImitatePass::GitPull git init wrapper
      23             :  */
      24           0 : void ImitatePass::GitPull() { executeGit(GIT_PULL, {"pull"}); }
      25             : 
      26             : /**
      27             :  * @brief ImitatePass::GitPull_b git pull wrapper
      28             :  */
      29           0 : void ImitatePass::GitPull_b() {
      30           0 :   exec.executeBlocking(QtPassSettings::getGitExecutable(), {"pull"});
      31           0 : }
      32             : 
      33             : /**
      34             :  * @brief ImitatePass::GitPush git init wrapper
      35             :  */
      36           0 : void ImitatePass::GitPush() {
      37           0 :   if (QtPassSettings::isUseGit()) {
      38           0 :     executeGit(GIT_PUSH, {"push"});
      39           0 :   }
      40           0 : }
      41             : 
      42             : /**
      43             :  * @brief ImitatePass::Show shows content of file
      44             :  */
      45           0 : void ImitatePass::Show(QString file) {
      46           0 :   file = QtPassSettings::getPassStore() + file + ".gpg";
      47           0 :   QStringList args = {"-d",      "--quiet",     "--yes", "--no-encrypt-to",
      48           0 :                       "--batch", "--use-agent", file};
      49           0 :   executeGpg(PASS_SHOW, args);
      50             : 
      51           0 : }
      52             : 
      53             : /**
      54             :  * @brief ImitatePass::OtpGenerate generates an otp code
      55             :  */
      56           0 : void ImitatePass::OtpGenerate(QString file) {}
      57             : 
      58             : /**
      59             :  * @brief ImitatePass::Insert create new file with encrypted content
      60             :  *
      61             :  * @param file      file to be created
      62             :  * @param newValue  value to be stored in file
      63             :  * @param overwrite whether to overwrite existing file
      64             :  */
      65           0 : void ImitatePass::Insert(QString file, QString newValue, bool overwrite) {
      66           0 :   file = file + ".gpg";
      67           0 :   transactionHelper trans(this, PASS_INSERT);
      68           0 :   QStringList recipients = Pass::getRecipientList(file);
      69           0 :   if (recipients.isEmpty()) {
      70             :     //  TODO(bezet): probably throw here
      71           0 :     emit critical(tr("Can not edit"),
      72           0 :                   tr("Could not read encryption key to use, .gpg-id "
      73             :                      "file missing or invalid."));
      74           0 :     return;
      75             :   }
      76           0 :   QStringList args = {"--batch", "-eq", "--output", file};
      77           0 :   for (auto &r : recipients) {
      78           0 :     args.append("-r");
      79           0 :     args.append(r);
      80           0 :   };
      81           0 :   if (overwrite)
      82           0 :     args.append("--yes");
      83           0 :   args.append("-");
      84           0 :   executeGpg(PASS_INSERT, args, newValue);
      85           0 :   if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit()) {
      86             :     //    TODO(bezet) why not?
      87           0 :     if (!overwrite)
      88           0 :       executeGit(GIT_ADD, {"add", file});
      89           0 :     QString path = QDir(QtPassSettings::getPassStore()).relativeFilePath(file);
      90           0 :     path.replace(QRegExp("\\.gpg$"), "");
      91           0 :     QString msg =
      92           0 :         QString(overwrite ? "Edit" : "Add") + " for " + path + " using QtPass.";
      93           0 :     GitCommit(file, msg);
      94           0 :   }
      95           0 : }
      96             : 
      97             : /**
      98             :  * @brief ImitatePass::GitCommit commit a file to git with an appropriate commit
      99             :  * message
     100             :  * @param file
     101             :  * @param msg
     102             :  */
     103           0 : void ImitatePass::GitCommit(const QString &file, const QString &msg) {
     104           0 :   executeGit(GIT_COMMIT, {"commit", "-m", msg, "--", file});
     105           0 : }
     106             : 
     107             : /**
     108             :  * @brief ImitatePass::Remove custom implementation of "pass remove"
     109             :  */
     110           0 : void ImitatePass::Remove(QString file, bool isDir) {
     111           0 :   file = QtPassSettings::getPassStore() + file;
     112           0 :   transactionHelper trans(this, PASS_REMOVE);
     113           0 :   if (!isDir)
     114           0 :     file += ".gpg";
     115           0 :   if (QtPassSettings::isUseGit()) {
     116           0 :     executeGit(GIT_RM, {"rm", (isDir ? "-rf" : "-f"), file});
     117             :     //  TODO(bezet): commit message used to have pass-like file name inside(ie.
     118             :     //  getFile(file, true)
     119           0 :     GitCommit(file, "Remove for " + file + " using QtPass.");
     120           0 :   } else {
     121           0 :     if (isDir) {
     122             : #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
     123           0 :       QDir dir(file);
     124           0 :       dir.removeRecursively();
     125             : #else
     126             :       removeDir(QtPassSettings::getPassStore() + file);
     127             : #endif
     128           0 :     } else
     129           0 :       QFile(file).remove();
     130             :   }
     131           0 : }
     132             : 
     133             : /**
     134             :  * @brief ImitatePass::Init initialize pass repository
     135             :  *
     136             :  * @param path      path in which new password-store will be created
     137             :  * @param users     list of users who shall be able to decrypt passwords in
     138             :  * path
     139             :  */
     140           0 : void ImitatePass::Init(QString path, const QList<UserInfo> &users) {
     141           0 :   QString gpgIdFile = path + ".gpg-id";
     142           0 :   QFile gpgId(gpgIdFile);
     143             :   bool addFile = false;
     144           0 :   transactionHelper trans(this, PASS_INIT);
     145           0 :   if (QtPassSettings::isAddGPGId(true)) {
     146           0 :     QFileInfo checkFile(gpgIdFile);
     147           0 :     if (!checkFile.exists() || !checkFile.isFile())
     148           0 :       addFile = true;
     149           0 :   }
     150           0 :   if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
     151           0 :     emit critical(tr("Cannot update"),
     152           0 :                   tr("Failed to open .gpg-id for writing."));
     153           0 :     return;
     154             :   }
     155             :   bool secret_selected = false;
     156           0 :   foreach (const UserInfo &user, users) {
     157           0 :     if (user.enabled) {
     158           0 :       gpgId.write((user.key_id + "\n").toUtf8());
     159           0 :       secret_selected |= user.have_secret;
     160           0 :     }
     161             :   }
     162           0 :   gpgId.close();
     163           0 :   if (!secret_selected) {
     164           0 :     emit critical(
     165           0 :         tr("Check selected users!"),
     166           0 :         tr("None of the selected keys have a secret key available.\n"
     167             :            "You will not be able to decrypt any newly added passwords!"));
     168           0 :     return;
     169             :   }
     170             : 
     171           0 :   if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit() &&
     172           0 :       !QtPassSettings::getGitExecutable().isEmpty()) {
     173           0 :     if (addFile)
     174           0 :       executeGit(GIT_ADD, {"add", gpgIdFile});
     175           0 :     QString path = gpgIdFile;
     176           0 :     path.replace(QRegExp("\\.gpg$"), "");
     177           0 :     GitCommit(gpgIdFile, "Added " + path + " using QtPass.");
     178           0 :   }
     179           0 :   reencryptPath(path);
     180           0 : }
     181             : 
     182             : /**
     183             :  * @brief ImitatePass::removeDir delete folder recursive.
     184             :  * @param dirName which folder.
     185             :  * @return was removal succesful?
     186             :  */
     187           0 : bool ImitatePass::removeDir(const QString &dirName) {
     188             :   bool result = true;
     189           0 :   QDir dir(dirName);
     190             : 
     191           0 :   if (dir.exists(dirName)) {
     192           0 :     Q_FOREACH (QFileInfo info,
     193             :                dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System |
     194             :                                      QDir::Hidden | QDir::AllDirs | QDir::Files,
     195             :                                  QDir::DirsFirst)) {
     196           0 :       if (info.isDir())
     197           0 :         result = removeDir(info.absoluteFilePath());
     198             :       else
     199           0 :         result = QFile::remove(info.absoluteFilePath());
     200             : 
     201           0 :       if (!result)
     202           0 :         return result;
     203             :     }
     204           0 :     result = dir.rmdir(dirName);
     205           0 :   }
     206           0 :   return result;
     207           0 : }
     208             : 
     209             : /**
     210             :  * @brief ImitatePass::reencryptPath reencrypt all files under the chosen
     211             :  * directory
     212             :  *
     213             :  * This is stil quite experimental..
     214             :  * @param dir
     215             :  */
     216           0 : void ImitatePass::reencryptPath(QString dir) {
     217           0 :   emit statusMsg(tr("Re-encrypting from folder %1").arg(dir), 3000);
     218           0 :   emit startReencryptPath();
     219           0 :   if (QtPassSettings::isAutoPull()) {
     220             :     //  TODO(bezet): move statuses inside actions?
     221           0 :     emit statusMsg(tr("Updating password-store"), 2000);
     222           0 :     GitPull_b();
     223           0 :   }
     224           0 :   QDir currentDir;
     225           0 :   QDirIterator gpgFiles(dir, QStringList() << "*.gpg", QDir::Files,
     226           0 :                         QDirIterator::Subdirectories);
     227           0 :   QStringList gpgId;
     228           0 :   while (gpgFiles.hasNext()) {
     229           0 :     QString fileName = gpgFiles.next();
     230           0 :     if (gpgFiles.fileInfo().path() != currentDir.path()) {
     231           0 :       gpgId = getRecipientList(fileName);
     232           0 :       gpgId.sort();
     233             :     }
     234             :     //  TODO(bezet): enable --with-colons for better future-proofness?
     235           0 :     QStringList args = {
     236           0 :         "-v",          "--no-secmem-warning", "--no-permission-warning",
     237           0 :         "--list-only", "--keyid-format=long", fileName};
     238           0 :     QString keys, err;
     239           0 :     exec.executeBlocking(QtPassSettings::getGpgExecutable(), args, &keys, &err);
     240           0 :     QStringList actualKeys;
     241           0 :     keys += err;
     242           0 :     QStringList key = keys.split("\n");
     243           0 :     QListIterator<QString> itr(key);
     244           0 :     while (itr.hasNext()) {
     245           0 :       QString current = itr.next();
     246           0 :       QStringList cur = current.split(" ");
     247           0 :       if (cur.length() > 4) {
     248           0 :         QString actualKey = cur.takeAt(4);
     249           0 :         if (actualKey.length() == 16) {
     250           0 :           actualKeys << actualKey;
     251             :         }
     252           0 :       }
     253           0 :     }
     254           0 :     actualKeys.sort();
     255           0 :     if (actualKeys != gpgId) {
     256             :       // dbg()<< actualKeys << gpgId << getRecipientList(fileName);
     257             :       dbg() << "reencrypt " << fileName << " for " << gpgId;
     258           0 :       QString local_lastDecrypt = "Could not decrypt";
     259           0 :       args = QStringList{"-d",      "--quiet",     "--yes", "--no-encrypt-to",
     260           0 :                          "--batch", "--use-agent", fileName};
     261           0 :       exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
     262             :                            &local_lastDecrypt);
     263             : 
     264           0 :       if (!local_lastDecrypt.isEmpty() &&
     265           0 :           local_lastDecrypt != "Could not decrypt") {
     266           0 :         if (local_lastDecrypt.right(1) != "\n")
     267           0 :           local_lastDecrypt += "\n";
     268             : 
     269           0 :         QStringList recipients = Pass::getRecipientList(fileName);
     270           0 :         if (recipients.isEmpty()) {
     271           0 :           emit critical(tr("Can not edit"),
     272           0 :                         tr("Could not read encryption key to use, .gpg-id "
     273             :                            "file missing or invalid."));
     274           0 :           return;
     275             :         }
     276           0 :         args = QStringList{"--yes", "--batch", "-eq", "--output", fileName};
     277           0 :         for (auto &i : recipients) {
     278           0 :           args.append("-r");
     279           0 :           args.append(i);
     280           0 :         }
     281           0 :         args.append("-");
     282           0 :         exec.executeBlocking(QtPassSettings::getGpgExecutable(), args,
     283           0 :                              local_lastDecrypt);
     284             : 
     285           0 :         if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit()) {
     286           0 :           exec.executeBlocking(QtPassSettings::getGitExecutable(),
     287           0 :                                {"add", fileName});
     288           0 :           QString path =
     289           0 :               QDir(QtPassSettings::getPassStore()).relativeFilePath(fileName);
     290           0 :           path.replace(QRegExp("\\.gpg$"), "");
     291           0 :           exec.executeBlocking(QtPassSettings::getGitExecutable(),
     292           0 :                                {"commit", fileName, "-m",
     293           0 :                                 "Edit for " + path + " using QtPass."});
     294           0 :         }
     295             : 
     296           0 :       } else {
     297             :         dbg() << "Decrypt error on re-encrypt";
     298             :       }
     299           0 :     }
     300           0 :   }
     301           0 :   if (QtPassSettings::isAutoPush()) {
     302           0 :     emit statusMsg(tr("Updating password-store"), 2000);
     303             :     //  TODO(bezet): this is non-blocking and shall be done outside
     304           0 :     GitPush();
     305             :   }
     306           0 :   emit endReencryptPath();
     307           0 : }
     308             : 
     309           0 : void ImitatePass::Move(const QString src, const QString dest,
     310             :                        const bool force) {
     311           0 :   QFileInfo destFileInfo(dest);
     312           0 :   transactionHelper trans(this, PASS_MOVE);
     313           0 :   if (QtPassSettings::isUseGit()) {
     314           0 :     QStringList args;
     315           0 :     args << "mv";
     316           0 :     if (force) {
     317           0 :       args << "-f";
     318           0 :     }
     319           0 :     args << src;
     320           0 :     args << dest;
     321           0 :     executeGit(GIT_MOVE, args);
     322             : 
     323           0 :     QString message = QString("moved from %1 to %2 using QTPass.");
     324           0 :     message = message.arg(src).arg(dest);
     325           0 :     GitCommit("", message);
     326           0 :   } else {
     327           0 :     QDir qDir;
     328           0 :     QFileInfo srcFileInfo(src);
     329           0 :     QString destCopy = dest;
     330           0 :     if (srcFileInfo.isFile() && destFileInfo.isDir()) {
     331           0 :       destCopy = destFileInfo.absoluteFilePath() + QDir::separator() +
     332           0 :                  srcFileInfo.fileName();
     333           0 :     }
     334           0 :     if (force) {
     335           0 :       qDir.remove(destCopy);
     336             :     }
     337           0 :     qDir.rename(src, destCopy);
     338           0 :   }
     339             :   // reecrypt all files under the new folder
     340           0 :   if (destFileInfo.isDir()) {
     341           0 :     reencryptPath(destFileInfo.absoluteFilePath());
     342           0 :   } else if (destFileInfo.isFile()) {
     343           0 :     reencryptPath(destFileInfo.dir().path());
     344           0 :   }
     345           0 : }
     346             : 
     347           0 : void ImitatePass::Copy(const QString src, const QString dest,
     348             :                        const bool force) {
     349           0 :   QFileInfo destFileInfo(dest);
     350           0 :   transactionHelper trans(this, PASS_COPY);
     351           0 :   if (QtPassSettings::isUseGit()) {
     352           0 :     QStringList args;
     353           0 :     args << "cp";
     354           0 :     if (force) {
     355           0 :       args << "-f";
     356           0 :     }
     357           0 :     args << src;
     358           0 :     args << dest;
     359           0 :     executeGit(GIT_COPY, args);
     360             : 
     361           0 :     QString message = QString("copied from %1 to %2 using QTPass.");
     362           0 :     message = message.arg(src).arg(dest);
     363           0 :     GitCommit("", message);
     364           0 :   } else {
     365           0 :     QDir qDir;
     366           0 :     if (force) {
     367           0 :       qDir.remove(dest);
     368             :     }
     369           0 :     QFile::copy(src, dest);
     370           0 :   }
     371             :   // reecrypt all files under the new folder
     372           0 :   if (destFileInfo.isDir()) {
     373           0 :     reencryptPath(destFileInfo.absoluteFilePath());
     374           0 :   } else if (destFileInfo.isFile()) {
     375           0 :     reencryptPath(destFileInfo.dir().path());
     376           0 :   }
     377           0 : }
     378             : 
     379             : /**
     380             :  * @brief ImitatePass::executeGpg easy wrapper for running gpg commands
     381             :  * @param args
     382             :  */
     383           0 : void ImitatePass::executeGpg(PROCESS id, const QStringList &args, QString input,
     384             :                              bool readStdout, bool readStderr) {
     385           0 :   executeWrapper(id, QtPassSettings::getGpgExecutable(), args, input,
     386             :                  readStdout, readStderr);
     387           0 : }
     388             : /**
     389             :  * @brief ImitatePass::executeGit easy wrapper for running git commands
     390             :  * @param args
     391             :  */
     392           0 : void ImitatePass::executeGit(PROCESS id, const QStringList &args, QString input,
     393             :                              bool readStdout, bool readStderr) {
     394           0 :   executeWrapper(id, QtPassSettings::getGitExecutable(), args, input,
     395             :                  readStdout, readStderr);
     396           0 : }
     397             : 
     398             : /**
     399             :  * @brief ImitatePass::finished this function is overloaded to ensure
     400             :  *                              identical behaviour to RealPass ie. only PASS_*
     401             :  *                              processes are visible inside Pass::finish, so
     402             :  *                              that interface-wise it all looks the same
     403             :  * @param id
     404             :  * @param exitCode
     405             :  * @param out
     406             :  * @param err
     407             :  */
     408           0 : void ImitatePass::finished(int id, int exitCode, const QString &out,
     409             :                            const QString &err) {
     410             :   dbg() << "Imitate Pass";
     411           0 :   static QString transactionOutput;
     412           0 :   PROCESS pid = transactionIsOver(static_cast<PROCESS>(id));
     413           0 :   transactionOutput.append(out);
     414             : 
     415           0 :   if (exitCode == 0) {
     416           0 :     if (pid == INVALID)
     417           0 :       return;
     418             :   } else {
     419           0 :     while (pid == INVALID) {
     420           0 :       id = exec.cancelNext();
     421           0 :       if (id == -1) {
     422             :         //  this is probably irrecoverable and shall not happen
     423             :         dbg() << "No such transaction!";
     424           0 :         return;
     425             :       }
     426           0 :       pid = transactionIsOver(static_cast<PROCESS>(id));
     427             :     }
     428             :   }
     429           0 :   Pass::finished(pid, exitCode, transactionOutput, err);
     430           0 :   transactionOutput.clear();
     431           0 : }
     432             : 
     433             : /**
     434             :  * @brief executeWrapper    overrided so that every execution is a transaction
     435             :  * @param id
     436             :  * @param app
     437             :  * @param args
     438             :  * @param input
     439             :  * @param readStdout
     440             :  * @param readStderr
     441             :  */
     442           0 : void ImitatePass::executeWrapper(PROCESS id, const QString &app,
     443             :                                  const QStringList &args, QString input,
     444             :                                  bool readStdout, bool readStderr) {
     445           0 :   transactionAdd(id);
     446           0 :   Pass::executeWrapper(id, app, args, input, readStdout, readStderr);
     447           0 : }

Generated by: LCOV version 1.13