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