sailfish-safe

Sailfish frontend for safe(1)
git clone git://git.z3bra.org/sailfish-safe.git
Log | Files | Refs | README | LICENSE

passwordsmodel.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 }