tpasswordsmodel.cpp - sailfish-safe - Sailfish frontend for safe(1)
 (HTM) git clone git://git.z3bra.org/sailfish-safe.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       tpasswordsmodel.cpp (7528B)
       ---
            1 /*
            2  *   Copyright (C) 2018  Daniel Vrátil <dvratil@kde.org>
            3  *                 2021  Willy Goiffon <contact@z3bra.org>
            4  *
            5  *   This program is free software; you can redistribute it and/or modify
            6  *   it under the terms of the GNU Library General Public License as
            7  *   published by the Free Software Foundation; either version 2, or
            8  *   (at your option) any later version.
            9  *
           10  *   This program is distributed in the hope that it will be useful,
           11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
           12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
           13  *   GNU General Public License for more details
           14  *
           15  *   You should have received a copy of the GNU Library General Public
           16  *   License along with this program; if not, write to the
           17  *   Free Software Foundation, Inc.,
           18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
           19  */
           20 
           21 #include "passwordsmodel.h"
           22 #include "passwordprovider.h"
           23 #include "safe.h"
           24 
           25 #include <QDir>
           26 #include <QDebug>
           27 #include <QPointer>
           28 #include <QTemporaryFile>
           29 #include <QFile>
           30 
           31 #define SAFE_DIR "SAFE_DIR"
           32 
           33 class PasswordsModel::Node
           34 {
           35 public:
           36     Node() {}
           37 
           38     Node(const QString &name, PasswordsModel::EntryType type, Node *parent)
           39         : name(name), type(type), parent(parent)
           40     {
           41         if (parent) {
           42             parent->children.append(this);
           43         }
           44     }
           45 
           46     ~Node()
           47     {
           48         if (provider) {
           49             provider->expirePassword();
           50         }
           51         qDeleteAll(children);
           52     }
           53 
           54     QString path() const
           55     {
           56         if (!parent) {
           57             return name;
           58         } else {
           59             QString fileName = name;
           60             return parent->path() + QLatin1Char('/') + fileName;
           61         }
           62     }
           63 
           64     QString fullName() const
           65     {
           66         if (!mFullName.isNull()) {
           67             return mFullName;
           68         }
           69 
           70         if (!parent) {
           71             return {};
           72         }
           73         const auto p = parent->fullName();
           74         if (p.isEmpty()) {
           75             mFullName = name;
           76         } else {
           77             mFullName = p + QLatin1Char('/') + name;
           78         }
           79         return mFullName;
           80     }
           81 
           82     QString name;
           83     PasswordsModel::EntryType type;
           84     QPointer<PasswordProvider> provider;
           85     Node *parent = nullptr;
           86     QVector<Node*> children;
           87 
           88 private:
           89     mutable QString mFullName;
           90 };
           91 
           92 
           93 PasswordsModel::PasswordsModel(QObject *parent)
           94     : QAbstractItemModel(parent)
           95     , mWatcher(this)
           96 {
           97     if (qEnvironmentVariableIsSet(SAFE_DIR)) {
           98         mPassStore = QDir(QString::fromUtf8(qgetenv(SAFE_DIR)));
           99     } else {
          100         mPassStore = QDir(QStringLiteral("%1/.safe.d").arg(QDir::homePath()));
          101     }
          102 
          103     // FIXME: Try to figure out what has actually changed and update the model
          104     // accordingly instead of reseting it
          105     connect(&mWatcher, &QFileSystemWatcher::directoryChanged, this, &PasswordsModel::populate);
          106 
          107     populate();
          108 }
          109 
          110 PasswordsModel::~PasswordsModel()
          111 {
          112     delete mRoot;
          113 }
          114 
          115 PasswordsModel::Node *PasswordsModel::node(const QModelIndex& index) const
          116 {
          117     return static_cast<Node*>(index.internalPointer());
          118 }
          119 
          120 QHash<int, QByteArray> PasswordsModel::roleNames() const
          121 {
          122     return { { NameRole, "name" },
          123              { EntryTypeRole, "type" },
          124              { FullNameRole, "fullName" },
          125              { PathRole, "path" },
          126              { HasPasswordRole, "hasPassword" },
          127              { PasswordRole, "password" } };
          128 }
          129 
          130 int PasswordsModel::rowCount(const QModelIndex &parent) const
          131 {
          132     const auto parentNode = parent.isValid() ? node(parent) : mRoot;
          133     return parentNode ? parentNode->children.count() : 0;
          134 }
          135 
          136 int PasswordsModel::columnCount(const QModelIndex &parent) const
          137 {
          138     Q_UNUSED(parent)
          139     return 1;
          140 }
          141 
          142 QModelIndex PasswordsModel::index(int row, int column, const QModelIndex &parent) const
          143 {
          144     const auto parentNode = parent.isValid() ? node(parent) : mRoot;
          145     if (!parentNode || row < 0 || row >= parentNode->children.count() || column != 0) {
          146         return {};
          147     }
          148 
          149     return createIndex(row, column, parentNode->children.at(row));
          150 }
          151 
          152 QModelIndex PasswordsModel::parent(const QModelIndex &child) const
          153 {
          154     if (!child.isValid()) {
          155         return {};
          156     }
          157 
          158     const auto childNode = node(child);
          159     if (!childNode || !childNode->parent) {
          160         return {};
          161     }
          162     const auto parentNode = childNode->parent;
          163     if (parentNode == mRoot) {
          164         return {};
          165     }
          166     return createIndex(parentNode->parent->children.indexOf(parentNode), 0, parentNode);
          167 }
          168 
          169 QVariant PasswordsModel::data(const QModelIndex &index, int role) const
          170 {
          171     if (!index.isValid()) {
          172         return {};
          173     }
          174     const auto node = this->node(index);
          175     if (!node) {
          176         return {};
          177     }
          178 
          179     switch (role) {
          180     case Qt::DisplayRole:
          181         return node->name;
          182     case EntryTypeRole:
          183         return node->type;
          184     case PathRole:
          185         return node->path();
          186     case FullNameRole:
          187         return node->fullName();
          188     case PasswordRole:
          189         if (!node->provider) {
          190             node->provider = new PasswordProvider(node->fullName());
          191         }
          192         return QVariant::fromValue(node->provider.data());
          193     case HasPasswordRole:
          194         return !node->provider.isNull();
          195     }
          196 
          197     return {};
          198 }
          199 
          200 void PasswordsModel::populate()
          201 {
          202     beginResetModel();
          203     delete mRoot;
          204     mRoot = new Node;
          205     mRoot->name = mPassStore.absolutePath();
          206     populateDir(mPassStore, mRoot);
          207     endResetModel();
          208 }
          209 
          210 void PasswordsModel::populateDir(const QDir& dir, Node *parent)
          211 {
          212     mWatcher.addPath(dir.absolutePath());
          213     auto entries = dir.entryInfoList({ QStringLiteral("*") }, QDir::Files, QDir::NoSort);
          214     Q_FOREACH (const auto &entry, entries) {
          215         new Node(entry.completeBaseName(), PasswordEntry, parent);
          216     }
          217     entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort);
          218     Q_FOREACH (const auto &entry, entries) {
          219         auto node = new Node(entry.fileName(), FolderEntry, parent);
          220         populateDir(entry.absoluteFilePath(), node);
          221     }
          222 }
          223 
          224 // FIXME: This is absolutely not the right place for this piece of code
          225 // Should introduce PasswordManager to abstract password generation,
          226 // creation and access in an asynchronous manner.
          227 void PasswordsModel::addPassword(const QModelIndex &parent, const QString &name,
          228                                  const QString &password, const QString &extras)
          229 {
          230 
          231     QString data = password;
          232     QString secret = name;
          233     if (!extras.isEmpty()) {
          234         data += QStringLiteral("\n%1").arg(extras);
          235     }
          236 
          237     auto *task = Safe::encrypt(secret, data);
          238     connect(task, &Safe::EncryptTask::finished,
          239             this, [secret, task]() {
          240                 if (task->error())  {
          241                     qWarning() << "Error:" << task->errorString();
          242                     return;
          243                 }
          244                 qDebug() << "Successfully stored secret " << secret;
          245             });
          246 }
          247 
          248 void PasswordsModel::setPassphrase(const QString &passphrase)
          249 {
          250     auto *task = Safe::unlock(passphrase);
          251     connect(task, &Safe::UnlockTask::finished,
          252             this, [passphrase, task]() {
          253                 if (task->error())  {
          254                     qWarning() << "Error:" << task->errorString();
          255                     return;
          256                 }
          257                 qDebug() << "Safe unlocked!";
          258             });
          259 }
          260 
          261 void PasswordsModel::forgetPassphrase()
          262 {
          263     auto *task = Safe::lock();
          264     connect(task, &Safe::LockTask::finished,
          265             this, [task]() {
          266                 if (task->error())  {
          267                     qWarning() << "Error:" << task->errorString();
          268                     return;
          269                 }
          270                 qDebug() << "Safe locked!";
          271             });
          272 }