1 /*
2 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
3 SPDX-FileCopyrightText: 2011 Rodrigo Belem <rclbelem@gmail.com>
4 SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
5 */
6
7 #include <kuser.h>
8
9 #include <QFile>
10 #include <QRegularExpression>
11 #include <QMetaEnum>
12
13 #include <sys/stat.h>
14
15 #include "model.h"
16 #include "usermanager.h"
17
UserPermissionModel(const KSambaShareData & shareData,UserManager * userManager,QObject * parent)18 UserPermissionModel::UserPermissionModel(const KSambaShareData &shareData, UserManager *userManager, QObject *parent)
19 : QAbstractTableModel(parent)
20 , m_userManager(userManager)
21 , m_shareData(shareData)
22 , m_usersAcl()
23 {
24 QMetaObject::invokeMethod(this, &UserPermissionModel::setupData);
25 }
26
setupData()27 void UserPermissionModel::setupData()
28 {
29 const QStringList acl = m_shareData.acl().split(QLatin1Char(','),
30 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
31 QString::SkipEmptyParts);
32 #else
33 Qt::SkipEmptyParts);
34 #endif
35
36 QList<QString>::const_iterator itr;
37 for (itr = acl.constBegin(); itr != acl.constEnd(); ++itr) {
38 const QStringList userInfo = (*itr).trimmed().split(QLatin1Char(':'));
39 m_usersAcl.insert(userInfo.at(0), userInfo.at(1));
40 }
41 if (m_usersAcl.isEmpty()) {
42 m_usersAcl.insert(QStringLiteral("Everyone"), QStringLiteral("R"));
43 }
44 }
45
46
47
48
rowCount(const QModelIndex & parent) const49 int UserPermissionModel::rowCount(const QModelIndex &parent) const
50 {
51 Q_UNUSED(parent)
52 return m_userManager->users().count();
53 }
54
columnCount(const QModelIndex & parent) const55 int UserPermissionModel::columnCount(const QModelIndex &parent) const
56 {
57 Q_UNUSED(parent)
58 return QMetaEnum::fromType<Column>().keyCount();
59 }
60
data(const QModelIndex & index,int role) const61 QVariant UserPermissionModel::data(const QModelIndex &index, int role) const
62 {
63 if ((role == Qt::DisplayRole) && (index.column() == ColumnUsername)) {
64 return QVariant(m_userManager->users().at(index.row())->name());
65 }
66
67 if ((role == Qt::DisplayRole || role == Qt::EditRole) && (index.column() == ColumnAccess)) {
68 QMap<QString, QVariant>::ConstIterator itr;
69 for (itr = m_usersAcl.constBegin(); itr != m_usersAcl.constEnd(); ++itr) {
70 if (itr.key().endsWith(m_userManager->users().at(index.row())->name())) {
71 return itr.value();
72 }
73 }
74 }
75
76 return QVariant();
77 }
78
flags(const QModelIndex & index) const79 Qt::ItemFlags UserPermissionModel::flags(const QModelIndex &index) const
80 {
81 if (index.column() == ColumnUsername) {
82 return Qt::ItemIsSelectable;
83 }
84
85 if (index.column() == ColumnAccess) {
86 return (Qt::ItemIsEnabled | Qt::ItemIsEditable);
87 }
88
89 return Qt::NoItemFlags;
90 }
91
setData(const QModelIndex & index,const QVariant & value,int role)92 bool UserPermissionModel::setData(const QModelIndex &index, const QVariant &value, int role)
93 {
94 if ((role != Qt::EditRole) || (index.column() != ColumnAccess)) {
95 return false;
96 }
97
98 QString key;
99 QMap<QString, QVariant>::ConstIterator itr;
100 for (itr = m_usersAcl.constBegin(); itr != m_usersAcl.constEnd(); ++itr) {
101 if (itr.key().endsWith(m_userManager->users().at(index.row())->name())) {
102 key = itr.key();
103 break;
104 }
105 }
106
107 if (key.isEmpty()) {
108 key = m_userManager->users().at(index.row())->name();
109 }
110
111 if (value.isNull()) {
112 m_usersAcl.take(key);
113 } else {
114 m_usersAcl.insert(key, value);
115 }
116
117 Q_EMIT dataChanged(index, index);
118 return true;
119 }
120
getAcl() const121 QString UserPermissionModel::getAcl() const
122 {
123 // ACE order matters. Based on testing samba behaves the following way when checking if a user may do something:
124 // - rights granted stack up until the first applicable denial (r+f = f)
125 // - denials end the lookup BUT all permissions until then are applied!
126 // We always put Everyone at the beginning and do not support groups so effectively we behave the same way
127 // as on Windows. Everyone is to mean **everyone** and the user rules add on top but the Everyone ACE is the
128 // baseline permission for everyone. By extension because of the samba resolution behavior Everyone:D will disable
129 // the share pretty much.
130 // This has another more important element though: Everyone:R must not be before denials because samba applies
131 // any matched reads when it encounters a denial. e.g. Everyone:R,foo:D means foo can still read because D merely
132 // ends the lookup, it doesn't take already granted permissions away. With that in mind we need to reshuffle
133 // the ACEs so *all* D come first followed by all R and last all F.
134 // https://docs.microsoft.com/en-us/windows/win32/secauthz/how-dacls-control-access-to-an-object
135 // https://docs.microsoft.com/en-us/windows/win32/secauthz/order-of-aces-in-a-dacl
136
137 // NOTE: D < R < F ordering would also be fine should we ever grow group support I think
138
139 QStringList denials;
140 QStringList readables;
141 QStringList fulls;
142 for (auto it = m_usersAcl.constBegin(); it != m_usersAcl.constEnd(); ++it) {
143 const QString &userName = it.key();
144 const QString access = it->value<QString>();
145 if (access.isEmpty()) {
146 continue; // --- undefined (no access)
147 }
148 // NB: we do not append access because samba is being inconsistent with itself. `net usershare info`
149 // uses capital letters, but `net usershare add` will (for example) not accept capital D, so if you were to
150 // take the output of info and feed it to add it'd error out -.- ... force everything lower case here
151 // so it definitely works with add
152 if (access == QLatin1String("D")) {
153 denials << userName + QStringLiteral(":d");
154 } else if (access == QLatin1String("R")) {
155 readables << userName + QStringLiteral(":r");
156 } else if (access == QLatin1String("F")) {
157 fulls << userName + QStringLiteral(":f");
158 } else {
159 Q_UNREACHABLE(); // unmapped value WTH
160 }
161 }
162
163 return (denials + readables + fulls).join(QLatin1Char(','));
164 }
165