File indexing completed on 2025-01-05 04:58:17
0001 /* 0002 * SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 0003 * SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kdab.com> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "aclmanager.h" 0009 #include "aclentrydialog_p.h" 0010 #include "aclmodifyjob.h" 0011 #include "aclutils_p.h" 0012 #include "imapaclattribute.h" 0013 #include "imapresourcesettings.h" 0014 #include "pimcommonakonadi_debug.h" 0015 #include "util/pimutil.h" 0016 0017 #include <Akonadi/CollectionFetchJob> 0018 #include <Akonadi/ServerManager> 0019 0020 #include <KEmailAddress> 0021 #include <KLocalizedString> 0022 #include <KMessageBox> 0023 0024 #include <QAbstractListModel> 0025 #include <QAction> 0026 #include <QDBusInterface> 0027 #include <QDBusReply> 0028 #include <QItemSelectionModel> 0029 0030 using namespace PimCommon; 0031 0032 class AclModel : public QAbstractListModel 0033 { 0034 public: 0035 enum Role { 0036 UserIdRole = Qt::UserRole + 1, 0037 PermissionsRole, 0038 PermissionsTextRole, 0039 }; 0040 0041 AclModel(QObject *parent = nullptr) 0042 : QAbstractListModel(parent) 0043 { 0044 } 0045 0046 [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override 0047 { 0048 if (index.row() < 0 || index.row() >= mRights.count()) { 0049 return {}; 0050 } 0051 0052 const QPair<QByteArray, KIMAP::Acl::Rights> right = mRights.at(index.row()); 0053 switch (role) { 0054 case Qt::DisplayRole: 0055 return QStringLiteral("%1: %2").arg(QString::fromLatin1(right.first), AclUtils::permissionsToUserString(right.second)); 0056 case UserIdRole: 0057 return QString::fromLatin1(right.first); 0058 case PermissionsRole: 0059 return {static_cast<int>(right.second)}; 0060 case PermissionsTextRole: 0061 return AclUtils::permissionsToUserString(right.second); 0062 default: 0063 return {}; 0064 } 0065 } 0066 0067 bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override 0068 { 0069 if (index.row() < 0 || index.row() >= mRights.count()) { 0070 return false; 0071 } 0072 0073 QPair<QByteArray, KIMAP::Acl::Rights> &right = mRights[index.row()]; 0074 switch (role) { 0075 case UserIdRole: 0076 right.first = value.toByteArray(); 0077 Q_EMIT dataChanged(index, index); 0078 return true; 0079 case PermissionsRole: 0080 right.second = static_cast<KIMAP::Acl::Rights>(value.toInt()); 0081 Q_EMIT dataChanged(index, index); 0082 return true; 0083 default: 0084 return false; 0085 } 0086 0087 return false; 0088 } 0089 0090 [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override 0091 { 0092 if (parent.isValid()) { 0093 return 0; 0094 } else { 0095 return mRights.count(); 0096 } 0097 } 0098 0099 void setRights(const QMap<QByteArray, KIMAP::Acl::Rights> &rights) 0100 { 0101 beginResetModel(); 0102 0103 mRights.clear(); 0104 0105 QMap<QByteArray, KIMAP::Acl::Rights>::const_iterator it = rights.cbegin(); 0106 const QMap<QByteArray, KIMAP::Acl::Rights>::const_iterator itEnd = rights.cend(); 0107 for (; it != itEnd; ++it) { 0108 mRights.append(qMakePair(it.key(), it.value())); 0109 } 0110 0111 endResetModel(); 0112 } 0113 0114 [[nodiscard]] QMap<QByteArray, KIMAP::Acl::Rights> rights() const 0115 { 0116 QMap<QByteArray, KIMAP::Acl::Rights> result; 0117 0118 using RightPair = QPair<QByteArray, KIMAP::Acl::Rights>; 0119 for (const RightPair &right : std::as_const(mRights)) { 0120 result.insert(right.first, right.second); 0121 } 0122 0123 return result; 0124 } 0125 0126 protected: 0127 bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override 0128 { 0129 beginInsertRows(parent, row, row + count - 1); 0130 for (int i = 0; i < count; ++i) { 0131 mRights.insert(row, qMakePair(QByteArray(), KIMAP::Acl::Rights())); 0132 } 0133 endInsertRows(); 0134 0135 return true; 0136 } 0137 0138 bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override 0139 { 0140 beginRemoveRows(parent, row, row + count - 1); 0141 for (int i = 0; i < count; ++i) { 0142 mRights.remove(row, count); 0143 } 0144 endRemoveRows(); 0145 0146 return true; 0147 } 0148 0149 private: 0150 QList<QPair<QByteArray, KIMAP::Acl::Rights>> mRights; 0151 }; 0152 0153 class Q_DECL_HIDDEN PimCommon::AclManager::AclManagerPrivate 0154 { 0155 public: 0156 AclManagerPrivate(AclManager *qq) 0157 : q(qq) 0158 { 0159 mAddAction = new QAction(i18n("Add Entry..."), q); 0160 q->connect(mAddAction, &QAction::triggered, q, [this]() { 0161 addAcl(); 0162 }); 0163 0164 mEditAction = new QAction(i18n("Edit Entry..."), q); 0165 mEditAction->setEnabled(false); 0166 q->connect(mEditAction, &QAction::triggered, q, [this]() { 0167 editAcl(); 0168 }); 0169 0170 mDeleteAction = new QAction(i18n("Remove Entry"), q); 0171 mDeleteAction->setEnabled(false); 0172 q->connect(mDeleteAction, &QAction::triggered, q, [this]() { 0173 deleteAcl(); 0174 }); 0175 0176 mModel = new AclModel(q); 0177 0178 mSelectionModel = new QItemSelectionModel(mModel); 0179 q->connect(mSelectionModel, &QItemSelectionModel::selectionChanged, q, [this]() { 0180 selectionChanged(); 0181 }); 0182 } 0183 0184 ~AclManagerPrivate() = default; 0185 0186 void selectionChanged() 0187 { 0188 const bool itemSelected = !mSelectionModel->selectedIndexes().isEmpty(); 0189 0190 bool canAdmin = (mUserRights & KIMAP::Acl::Admin); 0191 0192 bool canAdminThisItem = canAdmin; 0193 if (canAdmin && itemSelected) { 0194 const QModelIndex index = mSelectionModel->selectedIndexes().first(); 0195 const QString userId = index.data(AclModel::UserIdRole).toString(); 0196 const KIMAP::Acl::Rights rights = static_cast<KIMAP::Acl::Rights>(index.data(AclModel::PermissionsRole).toInt()); 0197 0198 // Don't allow users to remove their own admin permissions - there's no way back 0199 if (mImapUserName == userId && (rights & KIMAP::Acl::Admin)) { 0200 canAdminThisItem = false; 0201 } 0202 } 0203 0204 mAddAction->setEnabled(canAdmin); 0205 mEditAction->setEnabled(itemSelected && canAdminThisItem); 0206 mDeleteAction->setEnabled(itemSelected && canAdminThisItem); 0207 } 0208 0209 void addAcl() 0210 { 0211 AclEntryDialog dlg; 0212 dlg.setWindowTitle(i18nc("@title:window", "Add ACL")); 0213 0214 if (!dlg.exec()) { 0215 return; 0216 } 0217 const QString userId = dlg.userId(); 0218 const QStringList lstAddresses = KEmailAddress::splitAddressList(userId); 0219 for (const QString &addr : lstAddresses) { 0220 if (mModel->insertRow(mModel->rowCount())) { 0221 const QModelIndex index = mModel->index(mModel->rowCount() - 1, 0); 0222 const QString extractedAddress = KEmailAddress::extractEmailAddress(addr); 0223 mModel->setData(index, extractedAddress, AclModel::UserIdRole); 0224 mModel->setData(index, static_cast<int>(dlg.permissions()), AclModel::PermissionsRole); 0225 0226 mChanged = true; 0227 } 0228 } 0229 } 0230 0231 void editAcl() 0232 { 0233 if (mEditAction->isEnabled()) { 0234 const QModelIndex index = mSelectionModel->selectedIndexes().first(); 0235 const QString userId = index.data(AclModel::UserIdRole).toString(); 0236 const KIMAP::Acl::Rights permissions = static_cast<KIMAP::Acl::Rights>(index.data(AclModel::PermissionsRole).toInt()); 0237 0238 AclEntryDialog dlg; 0239 dlg.setWindowTitle(i18nc("@title:window", "Edit ACL")); 0240 dlg.setUserId(userId); 0241 dlg.setPermissions(permissions); 0242 0243 if (!dlg.exec()) { 0244 return; 0245 } 0246 const QStringList lstAddresses = KEmailAddress::splitAddressList(dlg.userId()); 0247 if (lstAddresses.count() == 1) { 0248 mModel->setData(index, KEmailAddress::extractEmailAddress(lstAddresses.at(0)), AclModel::UserIdRole); 0249 mModel->setData(index, static_cast<int>(dlg.permissions()), AclModel::PermissionsRole); 0250 mChanged = true; 0251 } else { 0252 bool firstElement = true; 0253 for (const QString &addr : lstAddresses) { 0254 if (firstElement) { 0255 mModel->setData(index, KEmailAddress::extractEmailAddress(lstAddresses.at(0)), AclModel::UserIdRole); 0256 mModel->setData(index, static_cast<int>(dlg.permissions()), AclModel::PermissionsRole); 0257 firstElement = false; 0258 } else { 0259 if (mModel->insertRow(mModel->rowCount())) { 0260 const QModelIndex rowindex = mModel->index(mModel->rowCount() - 1, 0); 0261 mModel->setData(rowindex, KEmailAddress::extractEmailAddress(addr), AclModel::UserIdRole); 0262 mModel->setData(rowindex, static_cast<int>(dlg.permissions()), AclModel::PermissionsRole); 0263 } 0264 } 0265 } 0266 mChanged = true; 0267 } 0268 } 0269 } 0270 0271 void deleteAcl() 0272 { 0273 const QModelIndex index = mSelectionModel->selectedIndexes().first(); 0274 const QString userId = index.data(AclModel::UserIdRole).toString(); 0275 0276 if (mImapUserName == userId) { 0277 if (KMessageBox::Cancel 0278 == KMessageBox::warningContinueCancel(nullptr, 0279 i18n("Do you really want to remove your own permissions for this folder? " 0280 "You will not be able to access it afterwards."), 0281 i18nc("@title:window", "Remove"))) { 0282 return; 0283 } 0284 } else { 0285 if (KMessageBox::Cancel 0286 == KMessageBox::warningContinueCancel(nullptr, 0287 i18n("Do you really want to remove these permissions for this folder?"), 0288 i18nc("@title:window", "Remove"))) { 0289 return; 0290 } 0291 } 0292 0293 mModel->removeRow(index.row(), QModelIndex()); 0294 mChanged = true; 0295 } 0296 0297 void setCollection(const Akonadi::Collection &collection) 0298 { 0299 mCollection = collection; 0300 mChanged = false; 0301 0302 const auto attribute = collection.attribute<PimCommon::ImapAclAttribute>(); 0303 const QMap<QByteArray, KIMAP::Acl::Rights> rights = attribute->rights(); 0304 0305 QString resource = collection.resource(); 0306 if (resource.contains(QLatin1StringView("akonadi_kolabproxy_resource"))) { 0307 const QString basename = Akonadi::ServerManager::agentServiceName(Akonadi::ServerManager::Agent, QStringLiteral("akonadi_kolabproxy_resource")); 0308 0309 QDBusInterface interface(basename, QStringLiteral("/KolabProxy")); 0310 if (interface.isValid()) { 0311 QDBusReply<QString> reply = interface.call(QStringLiteral("imapResourceForCollection"), collection.remoteId().toLongLong()); 0312 if (reply.isValid()) { 0313 resource = reply; 0314 } 0315 } 0316 } 0317 OrgKdeAkonadiImapSettingsInterface *imapSettingsInterface = PimCommon::Util::createImapSettingsInterface(resource); 0318 0319 QString loginName; 0320 QString serverName; 0321 if (imapSettingsInterface && imapSettingsInterface->isValid()) { 0322 QDBusReply<QString> reply = imapSettingsInterface->userName(); 0323 if (reply.isValid()) { 0324 loginName = reply; 0325 } 0326 0327 reply = imapSettingsInterface->imapServer(); 0328 if (reply.isValid()) { 0329 serverName = reply; 0330 } 0331 } else { 0332 qCDebug(PIMCOMMONAKONADI_LOG) << " collection has not imap as resources: " << collection.resource(); 0333 } 0334 delete imapSettingsInterface; 0335 0336 mImapUserName = loginName; 0337 if (!rights.contains(loginName.toUtf8())) { 0338 const QString guessedUserName = AclUtils::guessUserName(loginName, serverName); 0339 if (rights.contains(guessedUserName.toUtf8())) { 0340 mImapUserName = guessedUserName; 0341 } 0342 } 0343 0344 mUserRights = rights[mImapUserName.toUtf8()]; 0345 0346 mModel->setRights(rights); 0347 selectionChanged(); 0348 } 0349 0350 AclManager *const q; 0351 AclModel *mModel = nullptr; 0352 QItemSelectionModel *mSelectionModel = nullptr; 0353 QAction *mAddAction = nullptr; 0354 QAction *mEditAction = nullptr; 0355 QAction *mDeleteAction = nullptr; 0356 0357 Akonadi::Collection mCollection; 0358 QString mImapUserName; 0359 KIMAP::Acl::Rights mUserRights; 0360 bool mChanged = false; 0361 }; 0362 0363 AclManager::AclManager(QObject *parent) 0364 : QObject(parent) 0365 , d(new AclManagerPrivate(this)) 0366 { 0367 } 0368 0369 AclManager::~AclManager() = default; 0370 0371 void AclManager::setCollection(const Akonadi::Collection &collection) 0372 { 0373 d->setCollection(collection); 0374 Q_EMIT collectionChanged(d->mCollection); 0375 Q_EMIT collectionCanBeAdministrated(d->mUserRights & KIMAP::Acl::Admin); 0376 } 0377 0378 Akonadi::Collection AclManager::collection() const 0379 { 0380 return d->mCollection; 0381 } 0382 0383 QAbstractItemModel *AclManager::model() const 0384 { 0385 return d->mModel; 0386 } 0387 0388 QItemSelectionModel *AclManager::selectionModel() const 0389 { 0390 return d->mSelectionModel; 0391 } 0392 0393 QAction *AclManager::addAction() const 0394 { 0395 return d->mAddAction; 0396 } 0397 0398 QAction *AclManager::editAction() const 0399 { 0400 return d->mEditAction; 0401 } 0402 0403 QAction *AclManager::deleteAction() const 0404 { 0405 return d->mDeleteAction; 0406 } 0407 0408 void AclManager::save(bool recursive) 0409 { 0410 if (!d->mCollection.isValid() || !d->mChanged) { 0411 return; 0412 } 0413 0414 // refresh the collection, it might be outdated in the meantime 0415 auto job = new Akonadi::CollectionFetchJob(d->mCollection, Akonadi::CollectionFetchJob::Base); 0416 if (!job->exec()) { 0417 qCDebug(PIMCOMMONAKONADI_LOG) << " collection Fetch error" << job->errorString(); 0418 return; 0419 } 0420 0421 if (job->collections().isEmpty()) { 0422 qCDebug(PIMCOMMONAKONADI_LOG) << " collection list Fetched is Empty "; 0423 return; 0424 } 0425 0426 d->mCollection = job->collections().at(0); 0427 0428 d->mChanged = false; 0429 0430 auto modifyAclJob = new PimCommon::AclModifyJob; 0431 modifyAclJob->setCurrentRight(d->mModel->rights()); 0432 modifyAclJob->setTopLevelCollection(d->mCollection); 0433 modifyAclJob->setRecursive(recursive); 0434 modifyAclJob->start(); 0435 } 0436 0437 void AclManager::setChanged(bool b) 0438 { 0439 d->mChanged = b; 0440 } 0441 0442 #include "moc_aclmanager.cpp"