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 }