File indexing completed on 2024-04-28 05:34:17

0001 // SPDX-FileCopyrightText: 2018 Daniel Vrátil <dvratil@kde.org>
0002 //
0003 // SPDX-License-Identifier: LGPL-2.1-or-later
0004 
0005 #include "passwordsmodel.h"
0006 #include "passwordprovider.h"
0007 #include "otpprovider.h"
0008 
0009 #include <QDebug>
0010 #include <QPointer>
0011 
0012 using namespace PlasmaPass;
0013 
0014 static constexpr const char *passwordStoreDir = "PASSWORD_STORE_DIR";
0015 
0016 struct PasswordsModel::Node {
0017     explicit Node() = default;
0018     Node(QString name, PasswordsModel::EntryType type, Node *nodeParent)
0019         : name(std::move(name))
0020         , type(type)
0021         , parent(nodeParent)
0022     {
0023         if (parent != nullptr) {
0024             parent->children.push_back(std::unique_ptr<Node>(this));
0025         }
0026     }
0027 
0028     Node(const Node &other) = delete;
0029     Node(Node &&other) = default;
0030     Node &operator=(const Node &other) = delete;
0031     Node &operator=(Node &&other) = delete;
0032 
0033     ~Node() = default;
0034 
0035     QString path() const
0036     {
0037         if (parent == nullptr) {
0038             return name;
0039         }
0040 
0041         QString fileName = name;
0042         if (type == PasswordsModel::PasswordEntry) {
0043             fileName += QStringLiteral(".gpg");
0044         }
0045         return parent->path() + QLatin1Char('/') + fileName;
0046     }
0047 
0048     QString fullName() const
0049     {
0050         if (!mFullName.isNull()) {
0051             return mFullName;
0052         }
0053 
0054         if (parent == nullptr) {
0055             return {};
0056         }
0057         const auto p = parent->fullName();
0058         if (p.isEmpty()) {
0059             mFullName = name;
0060         } else {
0061             mFullName = p + QLatin1Char('/') + name;
0062         }
0063         return mFullName;
0064     }
0065 
0066     QString name;
0067     PasswordsModel::EntryType type = PasswordsModel::FolderEntry;
0068     QPointer<PasswordProvider> provider;
0069     QPointer<OTPProvider> otpProvider;
0070     Node *parent = nullptr;
0071     std::vector<std::unique_ptr<Node>> children;
0072 
0073 private:
0074     mutable QString mFullName;
0075 };
0076 
0077 PasswordsModel::PasswordsModel(QObject *parent)
0078     : QAbstractItemModel(parent)
0079     , mWatcher(this)
0080 {
0081     if (qEnvironmentVariableIsSet(passwordStoreDir)) {
0082         mPassStore = QDir(QString::fromUtf8(qgetenv(passwordStoreDir)));
0083     } else {
0084         mPassStore = QDir(QStringLiteral("%1/.password-store").arg(QDir::homePath()));
0085     }
0086 
0087     // FIXME: Try to figure out what has actually changed and update the model
0088     // accordingly instead of reseting it
0089     connect(&mWatcher, &QFileSystemWatcher::directoryChanged, this, &PasswordsModel::populate);
0090 
0091     populate();
0092 }
0093 
0094 PasswordsModel::~PasswordsModel() = default;
0095 
0096 PasswordsModel::Node *PasswordsModel::node(const QModelIndex &index)
0097 {
0098     return static_cast<Node *>(index.internalPointer());
0099 }
0100 
0101 QHash<int, QByteArray> PasswordsModel::roleNames() const
0102 {
0103     return {{NameRole, "name"},
0104             {EntryTypeRole, "type"},
0105             {FullNameRole, "fullName"},
0106             {PathRole, "path"},
0107             {HasPasswordRole, "hasPassword"},
0108             {PasswordRole, "password"},
0109             {OTPRole, "otp"},
0110             {HasOTPRole, "hasOtp"}};
0111 
0112 }
0113 
0114 int PasswordsModel::rowCount(const QModelIndex &parent) const
0115 {
0116     const auto parentNode = parent.isValid() ? node(parent) : mRoot.get();
0117     return parentNode != nullptr ? static_cast<int>(parentNode->children.size()) : 0;
0118 }
0119 
0120 int PasswordsModel::columnCount(const QModelIndex &parent) const
0121 {
0122     Q_UNUSED(parent)
0123     return 1;
0124 }
0125 
0126 QModelIndex PasswordsModel::index(int row, int column, const QModelIndex &parent) const
0127 {
0128     const auto parentNode = parent.isValid() ? node(parent) : mRoot.get();
0129     if (parentNode == nullptr || row < 0 || static_cast<std::size_t>(row) >= parentNode->children.size() || column != 0) {
0130         return {};
0131     }
0132 
0133     return createIndex(row, column, parentNode->children.at(row).get());
0134 }
0135 
0136 QModelIndex PasswordsModel::parent(const QModelIndex &child) const
0137 {
0138     if (!child.isValid()) {
0139         return {};
0140     }
0141 
0142     const auto childNode = node(child);
0143     if (childNode == nullptr || childNode->parent == nullptr) {
0144         return {};
0145     }
0146     const auto parentNode = childNode->parent;
0147     if (parentNode == mRoot.get()) {
0148         return {};
0149     }
0150 
0151     auto &children = parentNode->parent->children;
0152     const auto it = std::find_if(children.cbegin(), children.cend(), [parentNode](const auto &node) {
0153         return node.get() == parentNode;
0154     });
0155     Q_ASSERT(it != children.cend());
0156     return createIndex(std::distance(children.cbegin(), it), 0, parentNode);
0157 }
0158 
0159 QVariant PasswordsModel::data(const QModelIndex &index, int role) const
0160 {
0161     if (!index.isValid()) {
0162         return {};
0163     }
0164     const auto node = this->node(index);
0165     if (node == nullptr) {
0166         return {};
0167     }
0168 
0169     switch (role) {
0170     case Qt::DisplayRole:
0171         return node->name;
0172     case EntryTypeRole:
0173         return node->type;
0174     case PathRole:
0175         return node->path();
0176     case FullNameRole:
0177         return node->fullName();
0178     case PasswordRole:
0179         if (node->provider == nullptr) {
0180             node->provider = new PasswordProvider(node->path());
0181         }
0182         return QVariant::fromValue(node->provider.data());
0183     case OTPRole:
0184         if (node->otpProvider == nullptr) {
0185             node->otpProvider = new OTPProvider(node->path());
0186         }
0187         return QVariant::fromValue(node->otpProvider.data());
0188     case HasPasswordRole:
0189         return !node->provider.isNull();
0190     case HasOTPRole:
0191         return !node->otpProvider.isNull();
0192     default:
0193         return {};
0194     }
0195 }
0196 
0197 void PasswordsModel::populate()
0198 {
0199     beginResetModel();
0200     mRoot = std::make_unique<Node>();
0201     mRoot->name = mPassStore.absolutePath();
0202     populateDir(mPassStore, mRoot.get());
0203     endResetModel();
0204 }
0205 
0206 void PasswordsModel::populateDir(const QDir &dir, Node *parent)
0207 {
0208     mWatcher.addPath(dir.absolutePath());
0209     auto entries = dir.entryInfoList({QStringLiteral("*.gpg")}, QDir::Files, QDir::NoSort);
0210     for (const auto &entry : qAsConst(entries)) {
0211         new Node(entry.completeBaseName(), PasswordEntry, parent);
0212     }
0213     entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort);
0214     for (const auto &entry : qAsConst(entries)) {
0215         auto node = new Node(entry.fileName(), FolderEntry, parent);
0216         populateDir(entry.absoluteFilePath(), node);
0217     }
0218 }
0219 
0220 #include "moc_passwordsmodel.cpp"