File indexing completed on 2025-01-05 04:35:36
0001 /* 0002 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0003 SPDX-FileCopyrightText: 2021 Danil Shein <dshein@altlinux.org> 0004 SPDX-FileCopyrightText: 2021 Slava Aseev <nullptrnine@basealt.ru> 0005 SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org> 0006 */ 0007 0008 #include "permissionshelper.h" 0009 0010 #include <KFileItem> 0011 #include <KIO/StatJob> 0012 #include <KUser> 0013 #include <QDebug> 0014 #include <QFileInfo> 0015 #include <QMetaEnum> 0016 #include <KLocalizedString> 0017 0018 #include <QCoro/QCoroSignal> 0019 0020 #include "model.h" 0021 #include "usermanager.h" 0022 0023 static QString getUserPrimaryGroup(const QString &user) 0024 { 0025 const QStringList groups = KUser(user).groupNames(); 0026 if (!groups.isEmpty()) { 0027 return groups.at(0); 0028 } 0029 // if we can't fetch the user's groups then assume the group name is the same as the user name 0030 return user; 0031 } 0032 0033 static QCoro::Task<KFileItem> getCompleteFileItem(const QString &path) 0034 { 0035 const QUrl url = QUrl::fromLocalFile(path); 0036 auto job = KIO::stat(url); 0037 0038 co_await qCoro(job, &KJob::result); 0039 0040 KIO::UDSEntry entry = job->statResult(); 0041 KFileItem item(entry, url); 0042 co_return item; 0043 } 0044 0045 static QString permissionsToString(QFile::Permissions perm) 0046 { 0047 const char permString[] = {(perm & QFileDevice::ReadOwner) ? 'r' : '-', 0048 (perm & QFileDevice::WriteOwner) ? 'w' : '-', 0049 (perm & QFileDevice::ExeOwner) ? 'x' : '-', 0050 (perm & QFileDevice::ReadGroup) ? 'r' : '-', 0051 (perm & QFileDevice::WriteGroup) ? 'w' : '-', 0052 (perm & QFileDevice::ExeGroup) ? 'x' : '-', 0053 (perm & QFileDevice::ReadOther) ? 'r' : '-', 0054 (perm & QFileDevice::WriteOther) ? 'w' : '-', 0055 (perm & QFileDevice::ExeOther) ? 'x' : '-'}; 0056 0057 const int permsAsNumber = ((perm & QFileDevice::ReadOwner) ? S_IRUSR : 0) 0058 + ((perm & QFileDevice::WriteOwner) ? S_IWUSR : 0) 0059 + ((perm & QFileDevice::ExeOwner) ? S_IXUSR : 0) 0060 + ((perm & QFileDevice::ReadGroup) ? S_IRGRP : 0) 0061 + ((perm & QFileDevice::WriteGroup) ? S_IWGRP : 0) 0062 + ((perm & QFileDevice::ExeGroup) ? S_IXGRP : 0) 0063 + ((perm & QFileDevice::ReadOther) ? S_IROTH : 0) 0064 + ((perm & QFileDevice::WriteOther) ? S_IWOTH : 0) 0065 + ((perm & QFileDevice::ExeOther) ? S_IXOTH : 0); 0066 0067 return QString::fromLatin1(permString, sizeof(permString)) + QStringLiteral(" (0%1)").arg(QString::number(permsAsNumber, 8)); 0068 } 0069 0070 PermissionsHelperModel::PermissionsHelperModel(PermissionsHelper *helper) 0071 : QAbstractTableModel(helper) 0072 , parent(helper) 0073 { 0074 } 0075 0076 int PermissionsHelperModel::rowCount(const QModelIndex &) const 0077 { 0078 return parent->affectedPaths().count() + 1 /* header */; 0079 } 0080 0081 int PermissionsHelperModel::columnCount(const QModelIndex &) const 0082 { 0083 return QMetaEnum::fromType<Column>().keyCount(); 0084 } 0085 0086 QVariant PermissionsHelperModel::data(const QModelIndex &index, int role) const 0087 { 0088 if (!index.isValid()) { 0089 return {}; 0090 } 0091 0092 if (index.row() == 0 /* header */) { 0093 switch (index.column()) { 0094 case ColumnPath: 0095 return i18nc("@title", "File Path"); 0096 case ColumnOldPermissions: 0097 return i18nc("@title", "Current Permissions"); 0098 case ColumnNewPermissions: 0099 return i18nc("@title", "Required Permissions"); 0100 }; 0101 } 0102 0103 if (role == Qt::DisplayRole) { 0104 const int row = index.row() - 1 /* header */; 0105 const auto &affectedPath = parent->affectedPaths().at(row); 0106 0107 switch (index.column()) { 0108 case ColumnPath: 0109 return affectedPath.path; 0110 case ColumnOldPermissions: 0111 return QVariant::fromValue(permissionsToString(affectedPath.oldPerm)); 0112 case ColumnNewPermissions: 0113 return QVariant::fromValue(permissionsToString(affectedPath.newPerm)); 0114 }; 0115 } 0116 0117 return {}; 0118 } 0119 0120 Qt::ItemFlags PermissionsHelperModel::flags(const QModelIndex &) const 0121 { 0122 return Qt::NoItemFlags; 0123 } 0124 0125 bool PermissionsHelperModel::setData(const QModelIndex &, const QVariant &, int) 0126 { 0127 return false; 0128 } 0129 0130 void PermissionsHelper::addPath(const QFileInfo &fileInfo, QFile::Permissions requiredPermissions) 0131 { 0132 auto oldPerm = fileInfo.permissions(); 0133 auto newPerm = oldPerm | requiredPermissions; 0134 m_affectedPaths.append({fileInfo.filePath(), oldPerm, newPerm}); 0135 } 0136 0137 PermissionsHelper::PermissionsHelper(const QString &path, const UserManager *userManager, const UserPermissionModel *permissionModel, QObject *parent) 0138 : QObject(parent) 0139 , m_path(path) 0140 , m_userManager(userManager) 0141 , m_permissionModel(permissionModel) 0142 , m_model(new PermissionsHelperModel(this)) 0143 { 0144 } 0145 0146 void PermissionsHelper::reload() { 0147 reloadInternal(); 0148 } 0149 0150 QCoro::Task<void> PermissionsHelper::reloadInternal() { 0151 if (!m_userManager->currentUser()) { 0152 qWarning() << "PermissionsHelper::reload() failed: current user is null"; 0153 co_return; 0154 } 0155 0156 m_affectedPaths.clear(); 0157 m_filesWithPosixACL.clear(); 0158 0159 QString user = m_userManager->currentUser()->name(); 0160 0161 QFile::Permissions permsForShare; 0162 QFile::Permissions permsForSharePath; 0163 0164 auto usersACEs = m_permissionModel->getUsersACEs(); 0165 for (auto it = usersACEs.constBegin(); it != usersACEs.constEnd(); ++it) { 0166 const auto &aceUser = it.key(); 0167 const auto &access = it.value(); 0168 0169 if (aceUser != user) { 0170 if (getUserPrimaryGroup(aceUser) == getUserPrimaryGroup(user)) { 0171 if (access == QLatin1String("R")) { 0172 permsForShare |= (QFile::ExeGroup | QFile::ReadGroup); 0173 } else if (access == QLatin1String("F")) { 0174 permsForShare |= (QFile::ExeGroup | QFile::ReadGroup | QFile::WriteGroup); 0175 } 0176 permsForSharePath |= QFile::ExeGroup; 0177 } else { 0178 if (access == QLatin1String("R")) { 0179 permsForShare |= (QFile::ExeOther | QFile::ReadOther); 0180 } else if (access == QLatin1String("F")) { 0181 permsForShare |= (QFile::ExeOther | QFile::ReadOther | QFile::WriteOther); 0182 } 0183 permsForSharePath |= QFile::ExeOther; 0184 } 0185 } 0186 } 0187 0188 // store share path if permissions are insufficient 0189 QFileInfo fileInfo(m_path); 0190 if (!fileInfo.permission(permsForShare)) { 0191 addPath(fileInfo, permsForShare); 0192 } 0193 // check and store share POSIX ACL 0194 KFileItem fileItem = co_await getCompleteFileItem(m_path); 0195 0196 if (fileItem.hasExtendedACL()) { 0197 m_filesWithPosixACL.append(m_path); 0198 } 0199 0200 // check if share path could be resolved (has 'g+x' or 'o+x' all the way through) 0201 if (permsForShare) { 0202 QStringList pathParts = m_path.split(QStringLiteral("/"), Qt::SkipEmptyParts); 0203 if (pathParts.count() > 1) { 0204 pathParts.removeLast(); 0205 QString currentPath; 0206 0207 for (const auto &it : qAsConst(pathParts)) { 0208 currentPath.append(QStringLiteral("/") + it); 0209 fileInfo = QFileInfo(currentPath); 0210 if (!fileInfo.permission(permsForSharePath)) { 0211 addPath(fileInfo, permsForSharePath); 0212 } 0213 // check and store share path element's POSIX ACL 0214 KFileItem fileItem = co_await getCompleteFileItem(m_path); 0215 if (fileItem.hasExtendedACL()) { 0216 m_filesWithPosixACL.append(currentPath); 0217 } 0218 } 0219 } 0220 } 0221 0222 Q_EMIT permissionsChanged(); 0223 } 0224 0225 const QList<PermissionsHelper::PermissionsChangeInfo> &PermissionsHelper::affectedPaths() const 0226 { 0227 return m_affectedPaths; 0228 } 0229 0230 Q_INVOKABLE QStringList PermissionsHelper::changePermissions() 0231 { 0232 QStringList failedPaths; 0233 0234 for (const auto &affected : m_affectedPaths) { 0235 // do not break the loop to collecting all possible failed paths 0236 if (!QFile::setPermissions(affected.path, affected.newPerm)) { 0237 failedPaths += affected.path; 0238 } 0239 } 0240 0241 // roll back files permissions if some paths failed 0242 if (!failedPaths.isEmpty()) { 0243 for (const auto &affected : m_affectedPaths) { 0244 if (!QFile::setPermissions(affected.path, affected.oldPerm)) { 0245 qWarning() << "SharePermissionsHelper::sharePermsChange: failed to restore permissions for " << affected.path; 0246 } 0247 } 0248 } else { 0249 m_affectedPaths.clear(); 0250 Q_EMIT permissionsChanged(); 0251 } 0252 0253 return failedPaths; 0254 } 0255 0256 bool PermissionsHelper::permissionsChangeRequired() const 0257 { 0258 return !m_affectedPaths.empty(); 0259 } 0260 0261 bool PermissionsHelper::hasPosixACL() const 0262 { 0263 return !m_filesWithPosixACL.empty(); 0264 }