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"