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 }