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"