File indexing completed on 2024-05-05 17:33:37

0001 /**
0002  * SPDX-FileCopyrightText: 2022 Suhaas Joshi <joshiesuhaas0@gmail.com>
0003  * SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
0004  * SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "flatpakpermission.h"
0009 #include "flatpakcommon.h"
0010 #include "flatpakhelper.h"
0011 
0012 #include <KConfig>
0013 #include <KConfigGroup>
0014 #include <KLocalizedString>
0015 #include <QChar>
0016 #include <QDebug>
0017 #include <QDir>
0018 #include <QFileInfo>
0019 #include <QMetaEnum>
0020 #include <QQmlEngine>
0021 #include <QTemporaryFile>
0022 #include <QUrl>
0023 
0024 #include <algorithm>
0025 #include <array>
0026 #include <optional>
0027 #include <variant>
0028 
0029 namespace
0030 {
0031 
0032 /**
0033  * Type of QList mapped with function f over its items.
0034  */
0035 template<typename T, typename F>
0036 using MappedList = QList<typename std::invoke_result_t<F, const T &>::value_type>;
0037 
0038 /**
0039  * Map QList with a function that returns optional values.
0040  */
0041 template<typename T, typename F>
0042 MappedList<T, F> filter_map(const QList<T> &iter, F func)
0043 {
0044     MappedList<T, F> succeeded;
0045 
0046     for (const auto &item : iter) {
0047         const auto optional = func(item);
0048         if (optional.has_value()) {
0049             succeeded.append(optional.value());
0050         }
0051     }
0052 
0053     return succeeded;
0054 }
0055 
0056 /**
0057  * Map QList with a function that returns optional values, but also returns a
0058  * QList of original items that were failed to map.
0059  */
0060 template<typename T, typename F>
0061 std::pair<QList<T>, MappedList<T, F>> try_filter_map(const QList<T> &iter, F func)
0062 {
0063     QList<T> failed;
0064     MappedList<T, F> succeeded;
0065 
0066     for (const auto &item : iter) {
0067         const auto optional = func(item);
0068         if (optional.has_value()) {
0069             succeeded.append(optional.value());
0070         } else {
0071             failed.append(item);
0072         }
0073     }
0074 
0075     return {failed, succeeded};
0076 }
0077 
0078 const QLatin1String SUFFIX_RO = QLatin1String(":ro");
0079 const QLatin1String SUFFIX_RW = QLatin1String(":rw");
0080 const QLatin1String SUFFIX_CREATE = QLatin1String(":create");
0081 const QLatin1Char PREFIX_DENY = QLatin1Char('!');
0082 
0083 using FilesystemPrefix = FlatpakFilesystemsEntry::FilesystemPrefix;
0084 using PathMode = FlatpakFilesystemsEntry::PathMode;
0085 
0086 constexpr FlatpakFilesystemsEntry::TableEntry makeRequiredPath(FilesystemPrefix prefix, const char *prefixString)
0087 {
0088     return FlatpakFilesystemsEntry::TableEntry{prefix, PathMode::Required, QLatin1String(), QLatin1String(prefixString)};
0089 }
0090 
0091 constexpr FlatpakFilesystemsEntry::TableEntry makeOptionalPath(FilesystemPrefix prefix, const char *fixedString, const char *prefixString)
0092 {
0093     return FlatpakFilesystemsEntry::TableEntry{prefix, PathMode::Optional, QLatin1String(fixedString), QLatin1String(prefixString)};
0094 }
0095 
0096 constexpr FlatpakFilesystemsEntry::TableEntry makeInvalidPath(FilesystemPrefix prefix, const char *fixedString)
0097 {
0098     return FlatpakFilesystemsEntry::TableEntry{prefix, PathMode::NoPath, QLatin1String(fixedString), QLatin1String()};
0099 }
0100 
0101 const auto s_filesystems = {
0102     //
0103     makeRequiredPath(FilesystemPrefix::Absolute, "/"),
0104     //
0105     makeOptionalPath(FilesystemPrefix::Home, "~", "~/"),
0106     makeOptionalPath(FilesystemPrefix::Home, "home", "home/"),
0107     //
0108     makeInvalidPath(FilesystemPrefix::Host, "host"),
0109     makeInvalidPath(FilesystemPrefix::HostOs, "host-os"),
0110     makeInvalidPath(FilesystemPrefix::HostEtc, "host-etc"),
0111     //
0112     makeOptionalPath(FilesystemPrefix::XdgDesktop, "xdg-desktop", "xdg-desktop/"),
0113     makeOptionalPath(FilesystemPrefix::XdgDocuments, "xdg-documents", "xdg-documents/"),
0114     makeOptionalPath(FilesystemPrefix::XdgDownload, "xdg-download", "xdg-download/"),
0115     makeOptionalPath(FilesystemPrefix::XdgMusic, "xdg-music", "xdg-music/"),
0116     makeOptionalPath(FilesystemPrefix::XdgPictures, "xdg-pictures", "xdg-pictures/"),
0117     makeOptionalPath(FilesystemPrefix::XdgPublicShare, "xdg-public-share", "xdg-public-share/"),
0118     makeOptionalPath(FilesystemPrefix::XdgVideos, "xdg-videos", "xdg-videos/"),
0119     makeOptionalPath(FilesystemPrefix::XdgTemplates, "xdg-templates", "xdg-templates/"),
0120     //
0121     makeOptionalPath(FilesystemPrefix::XdgCache, "xdg-cache", "xdg-cache/"),
0122     makeOptionalPath(FilesystemPrefix::XdgConfig, "xdg-config", "xdg-config/"),
0123     makeOptionalPath(FilesystemPrefix::XdgData, "xdg-data", "xdg-data/"),
0124     //
0125     makeRequiredPath(FilesystemPrefix::XdgRun, "xdg-run/"),
0126     //
0127     makeRequiredPath(FilesystemPrefix::Unknown, ""),
0128 };
0129 
0130 } // namespace
0131 
0132 FlatpakSimpleEntry::FlatpakSimpleEntry(const QString &name, bool enabled)
0133     : m_name(name)
0134     , m_enabled(enabled)
0135 {
0136 }
0137 
0138 std::optional<FlatpakSimpleEntry> FlatpakSimpleEntry::parse(QStringView entry)
0139 {
0140     bool enabled = true;
0141     if (entry.startsWith(PREFIX_DENY)) {
0142         entry = entry.mid(1);
0143         enabled = false;
0144     }
0145 
0146     // For now we don't do any additional validation
0147     if (entry.isEmpty()) {
0148         return std::nullopt;
0149     }
0150 
0151     const auto name = entry.toString();
0152     return std::optional(FlatpakSimpleEntry(name, enabled));
0153 }
0154 
0155 std::pair<QStringList, QList<FlatpakSimpleEntry>> FlatpakSimpleEntry::getCategory(const KConfigGroup &group, const QString &category)
0156 {
0157     return try_filter_map(group.readXdgListEntry(category), [](const QString &entry) {
0158         return FlatpakSimpleEntry::parse(entry);
0159     });
0160 }
0161 
0162 QList<FlatpakSimpleEntry> FlatpakSimpleEntry::getCategorySkippingInvalidEntries(const KConfigGroup &group, const QString &category)
0163 {
0164     return filter_map(group.readXdgListEntry(category), [](const QString &entry) {
0165         return FlatpakSimpleEntry::parse(entry);
0166     });
0167 }
0168 
0169 std::optional<bool> FlatpakSimpleEntry::isEnabled(const QList<FlatpakSimpleEntry> &entries, const QString &name)
0170 {
0171     for (const auto &entry : entries) {
0172         if (entry.name() == name) {
0173             return std::optional(entry.isEnabled());
0174         }
0175     }
0176     return std::nullopt;
0177 }
0178 
0179 QString FlatpakSimpleEntry::format() const
0180 {
0181     if (m_enabled) {
0182         return m_name;
0183     } else {
0184         return PREFIX_DENY + m_name;
0185     }
0186 }
0187 
0188 bool FlatpakSimpleEntry::isEnabled() const
0189 {
0190     return m_enabled;
0191 }
0192 
0193 void FlatpakSimpleEntry::setEnabled(bool enabled)
0194 {
0195     m_enabled = enabled;
0196 }
0197 
0198 const QString &FlatpakSimpleEntry::name() const
0199 {
0200     return m_name;
0201 }
0202 
0203 bool FlatpakSimpleEntry::operator==(const FlatpakSimpleEntry &other) const
0204 {
0205     return m_name == other.m_name && m_enabled == other.m_enabled;
0206 }
0207 
0208 bool FlatpakSimpleEntry::operator!=(const FlatpakSimpleEntry &other) const
0209 {
0210     return !(*this == other);
0211 }
0212 
0213 FlatpakFilesystemsEntry::FlatpakFilesystemsEntry(FilesystemPrefix prefix, AccessMode mode, const QString &path)
0214     : m_prefix(prefix)
0215     , m_mode(mode)
0216     , m_path(path)
0217 {
0218 }
0219 
0220 std::optional<FlatpakFilesystemsEntry> FlatpakFilesystemsEntry::parse(QStringView entry)
0221 {
0222     std::optional<AccessMode> accessMode = std::nullopt;
0223 
0224     if (entry.endsWith(SUFFIX_RO)) {
0225         entry.chop(SUFFIX_RO.size());
0226         accessMode = std::optional(AccessMode::ReadOnly);
0227     } else if (entry.endsWith(SUFFIX_RW)) {
0228         entry.chop(SUFFIX_RW.size());
0229         accessMode = std::optional(AccessMode::ReadWrite);
0230     } else if (entry.endsWith(SUFFIX_CREATE)) {
0231         entry.chop(SUFFIX_CREATE.size());
0232         accessMode = std::optional(AccessMode::Create);
0233     }
0234 
0235     if (entry.startsWith(PREFIX_DENY)) {
0236         // ensure there is no access mode suffix
0237         if (accessMode.has_value()) {
0238             return std::nullopt;
0239         }
0240         entry = entry.mid(1);
0241         accessMode = std::optional(AccessMode::Deny);
0242     }
0243 
0244     AccessMode effectiveAccessMode = accessMode.value_or(AccessMode::ReadWrite);
0245 
0246     return parse(entry, effectiveAccessMode);
0247 }
0248 
0249 std::optional<FlatpakFilesystemsEntry> FlatpakFilesystemsEntry::parse(QStringView name, AccessMode accessMode)
0250 {
0251     for (const TableEntry &filesystem : s_filesystems) {
0252         // Deliberately not using switch here, because of the overlapping Optional case.
0253         if (filesystem.mode == PathMode::Optional || filesystem.mode == PathMode::Required) {
0254             if (name.startsWith(filesystem.prefixString) || filesystem.prefix == FilesystemPrefix::Unknown) {
0255                 if (filesystem.prefix != FilesystemPrefix::Unknown) {
0256                     name = name.mid(filesystem.prefixString.size());
0257                 }
0258                 QString path;
0259                 if (!name.isEmpty()) {
0260                     path = QUrl(name.toString()).toDisplayString(QUrl::RemoveScheme | QUrl::StripTrailingSlash);
0261                 } else if (filesystem.mode == PathMode::Required) {
0262                     return std::nullopt;
0263                 }
0264                 return std::optional(FlatpakFilesystemsEntry(filesystem.prefix, accessMode, path));
0265             }
0266         }
0267         if (filesystem.mode == PathMode::NoPath || filesystem.mode == PathMode::Optional) {
0268             if (name == filesystem.fixedString) {
0269                 return std::optional(FlatpakFilesystemsEntry(filesystem.prefix, accessMode, QString()));
0270             }
0271         }
0272     }
0273 
0274     return std::nullopt;
0275 }
0276 
0277 QString FlatpakFilesystemsEntry::name() const
0278 {
0279     const auto it = std::find_if(s_filesystems.begin(), s_filesystems.end(), [this](const TableEntry &filesystem) {
0280         if (filesystem.prefix != m_prefix) {
0281             return false;
0282         }
0283         // home/path should be serialized as ~/path, and fixed string "~" as "home".
0284         // So either the path should be empty, XOR current table entry is the "~" variant.
0285         if (filesystem.prefix == FilesystemPrefix::Home) {
0286             return m_path.isEmpty() != (filesystem.fixedString == QLatin1String("~"));
0287         }
0288         return true;
0289     });
0290     if (it == s_filesystems.end()) {
0291         // all prefixes are covered in the table, so this should never happen.
0292         Q_UNREACHABLE();
0293         return {};
0294     }
0295 
0296     if ((m_path.isEmpty() && it->mode == PathMode::Required) || (!m_path.isEmpty() && it->mode == PathMode::NoPath)) {
0297         return {};
0298     }
0299 
0300     return m_path.isEmpty() ? QString(it->fixedString) : it->prefixString + m_path;
0301 }
0302 
0303 QString FlatpakFilesystemsEntry::format() const
0304 {
0305     const QString path = name();
0306     if (path.isEmpty()) {
0307         return {};
0308     }
0309 
0310     switch (m_mode) {
0311     case AccessMode::ReadOnly:
0312         return path + SUFFIX_RO;
0313     case AccessMode::ReadWrite:
0314         // Omit default value
0315         return path;
0316     case AccessMode::Create:
0317         return path + SUFFIX_CREATE;
0318     case AccessMode::Deny:
0319         return PREFIX_DENY + path;
0320     }
0321     return {};
0322 }
0323 
0324 FlatpakFilesystemsEntry::FilesystemPrefix FlatpakFilesystemsEntry::prefix() const
0325 {
0326     return m_prefix;
0327 }
0328 
0329 QString FlatpakFilesystemsEntry::path() const
0330 {
0331     return m_path;
0332 }
0333 
0334 FlatpakFilesystemsEntry::AccessMode FlatpakFilesystemsEntry::mode() const
0335 {
0336     return m_mode;
0337 }
0338 
0339 bool FlatpakFilesystemsEntry::operator==(const FlatpakFilesystemsEntry &other) const
0340 {
0341     return other.m_prefix == m_prefix && other.m_mode == m_mode && other.m_path == m_path;
0342 }
0343 
0344 bool FlatpakFilesystemsEntry::operator!=(const FlatpakFilesystemsEntry &other) const
0345 {
0346     return !(*this == other);
0347 }
0348 
0349 PolicyChoicesModel::PolicyChoicesModel(QVector<Entry> &&policies, QObject *parent)
0350     : QAbstractListModel(parent)
0351     , m_policies(policies)
0352 {
0353 }
0354 
0355 QHash<int, QByteArray> PolicyChoicesModel::roleNames() const
0356 {
0357     return {
0358         {Qt::DisplayRole, "display"},
0359         {Roles::ValueRole, "value"},
0360     };
0361 }
0362 
0363 int PolicyChoicesModel::rowCount(const QModelIndex &parent) const
0364 {
0365     if (parent.isValid()) {
0366         return 0;
0367     }
0368     return m_policies.count();
0369 }
0370 
0371 QVariant PolicyChoicesModel::data(const QModelIndex &index, int role) const
0372 {
0373     if (!index.isValid() || index.row() < 0 || index.row() >= m_policies.count()) {
0374         return {};
0375     }
0376 
0377     const auto policy = m_policies.at(index.row());
0378 
0379     switch (role) {
0380     case Qt::DisplayRole:
0381         return policy.display;
0382     case Roles::ValueRole:
0383         return policy.value;
0384     }
0385 
0386     return {};
0387 }
0388 
0389 FilesystemChoicesModel::FilesystemChoicesModel(QObject *parent)
0390     : PolicyChoicesModel(
0391         QVector<Entry>{
0392             {static_cast<int>(FlatpakFilesystemsEntry::AccessMode::ReadOnly), i18n("read-only")},
0393             {static_cast<int>(FlatpakFilesystemsEntry::AccessMode::ReadWrite), i18n("read/write")},
0394             {static_cast<int>(FlatpakFilesystemsEntry::AccessMode::Create), i18n("create")},
0395             {static_cast<int>(FlatpakFilesystemsEntry::AccessMode::Deny), i18n("OFF")},
0396         },
0397         parent)
0398 {
0399 }
0400 
0401 DBusPolicyChoicesModel::DBusPolicyChoicesModel(QObject *parent)
0402     : PolicyChoicesModel(
0403         QVector<Entry>{
0404             {FlatpakPolicy::FLATPAK_POLICY_NONE, i18n("None")},
0405             {FlatpakPolicy::FLATPAK_POLICY_SEE, i18n("see")},
0406             {FlatpakPolicy::FLATPAK_POLICY_TALK, i18n("talk")},
0407             {FlatpakPolicy::FLATPAK_POLICY_OWN, i18n("own")},
0408         },
0409         parent)
0410 {
0411 }
0412 
0413 Q_GLOBAL_STATIC(FilesystemChoicesModel, s_FilesystemPolicies);
0414 Q_GLOBAL_STATIC(DBusPolicyChoicesModel, s_DBusPolicies);
0415 
0416 FlatpakPermission::ValueType FlatpakPermission::valueTypeFromSectionType(FlatpakPermissionsSectionType::Type section)
0417 {
0418     switch (section) {
0419     case FlatpakPermissionsSectionType::Filesystems:
0420         return FlatpakPermission::ValueType::Filesystems;
0421     case FlatpakPermissionsSectionType::SessionBus:
0422     case FlatpakPermissionsSectionType::SystemBus:
0423         return FlatpakPermission::ValueType::Bus;
0424     case FlatpakPermissionsSectionType::Environment:
0425         return FlatpakPermission::ValueType::Environment;
0426     case FlatpakPermissionsSectionType::Basic:
0427     case FlatpakPermissionsSectionType::Advanced:
0428     case FlatpakPermissionsSectionType::SubsystemsShared:
0429     case FlatpakPermissionsSectionType::Sockets:
0430     case FlatpakPermissionsSectionType::Devices:
0431     case FlatpakPermissionsSectionType::Features:
0432         break;
0433     }
0434     return FlatpakPermission::ValueType::Simple;
0435 }
0436 
0437 FlatpakPermission::FlatpakPermission(FlatpakPermissionsSectionType::Type section)
0438     : FlatpakPermission(section, QString(), QString(), QString(), false)
0439 {
0440     m_originType = OriginType::Dummy;
0441 }
0442 
0443 FlatpakPermission::FlatpakPermission(FlatpakPermissionsSectionType::Type section,
0444                                      const QString &name,
0445                                      const QString &category,
0446                                      const QString &description,
0447                                      bool isDefaultEnabled,
0448                                      const Variant &defaultValue)
0449     : m_section(section)
0450     , m_name(name)
0451     , m_category(category)
0452     , m_description(description)
0453     //
0454     , m_originType(OriginType::BuiltIn)
0455     //
0456     , m_defaultEnable(isDefaultEnabled)
0457     , m_overrideEnable(isDefaultEnabled)
0458     , m_effectiveEnable(isDefaultEnabled)
0459     //
0460     , m_defaultValue(defaultValue)
0461     , m_overrideValue(defaultValue)
0462     , m_effectiveValue(defaultValue)
0463 {
0464 }
0465 
0466 FlatpakPermissionsSectionType::Type FlatpakPermission::section() const
0467 {
0468     return m_section;
0469 }
0470 
0471 const QString &FlatpakPermission::name() const
0472 {
0473     return m_name;
0474 }
0475 
0476 const QString &FlatpakPermission::category() const
0477 {
0478     return m_category;
0479 }
0480 
0481 const QString &FlatpakPermission::description() const
0482 {
0483     return m_description;
0484 }
0485 
0486 FlatpakPermission::ValueType FlatpakPermission::valueType() const
0487 {
0488     return valueTypeFromSectionType(m_section);
0489 }
0490 
0491 FlatpakPermission::OriginType FlatpakPermission::originType() const
0492 {
0493     return m_originType;
0494 }
0495 
0496 void FlatpakPermission::setOriginType(OriginType type)
0497 {
0498     m_originType = type;
0499 }
0500 
0501 bool FlatpakPermission::isDefaultEnabled() const
0502 {
0503     return m_defaultEnable;
0504 }
0505 
0506 void FlatpakPermission::setOverrideEnabled(bool enabled)
0507 {
0508     m_overrideEnable = enabled;
0509 }
0510 
0511 bool FlatpakPermission::canBeDisabled() const
0512 {
0513     return valueType() == ValueType::Simple || !m_defaultEnable;
0514 }
0515 
0516 bool FlatpakPermission::isEffectiveEnabled() const
0517 {
0518     return m_effectiveEnable;
0519 }
0520 
0521 void FlatpakPermission::setEffectiveEnabled(bool enabled)
0522 {
0523     if (canBeDisabled()) {
0524         m_effectiveEnable = enabled;
0525     }
0526 }
0527 
0528 const FlatpakPermission::Variant FlatpakPermission::defaultValue() const
0529 {
0530     return m_defaultValue;
0531 }
0532 
0533 void FlatpakPermission::setDefaultValue(const Variant &value)
0534 {
0535     m_defaultValue = value;
0536 }
0537 
0538 void FlatpakPermission::setOverrideValue(const Variant &value)
0539 {
0540     m_overrideValue = value;
0541 }
0542 
0543 const FlatpakPermission::Variant FlatpakPermission::effectiveValue() const
0544 {
0545     return m_effectiveValue;
0546 }
0547 
0548 void FlatpakPermission::setEffectiveValue(const Variant &value)
0549 {
0550     m_effectiveValue = value;
0551 }
0552 
0553 bool FlatpakPermission::isSaveNeeded() const
0554 {
0555     if (m_originType == FlatpakPermission::OriginType::Dummy) {
0556         return false;
0557     }
0558 
0559     const bool enableDiffers = m_effectiveEnable != m_overrideEnable;
0560     if (valueType() != FlatpakPermission::ValueType::Simple) {
0561         const bool valueDiffers = m_effectiveValue != m_overrideValue;
0562         return enableDiffers || valueDiffers;
0563     }
0564     return enableDiffers;
0565 }
0566 
0567 bool FlatpakPermission::isDefaults() const
0568 {
0569     if (m_originType == FlatpakPermission::OriginType::Dummy) {
0570         return true;
0571     }
0572 
0573     const auto enableIsTheSame = m_effectiveEnable == m_defaultEnable;
0574     if (valueType() != FlatpakPermission::ValueType::Simple) {
0575         const auto customEntryIsMarkedForRemoval = !m_defaultEnable && !m_effectiveEnable;
0576         const auto valueIsTheSame = m_effectiveValue == m_defaultValue;
0577         // For disabled custom entries (i.e. marked for removal) value does not matter.
0578         return customEntryIsMarkedForRemoval || (enableIsTheSame && valueIsTheSame);
0579     }
0580     return enableIsTheSame;
0581 }
0582 
0583 static QString mapDBusFlatpakPolicyEnumValueToConfigString(FlatpakPolicy value)
0584 {
0585     switch (value) {
0586     case FlatpakPolicy::FLATPAK_POLICY_SEE:
0587         return QStringLiteral("see");
0588     case FlatpakPolicy::FLATPAK_POLICY_TALK:
0589         return QStringLiteral("talk");
0590     case FlatpakPolicy::FLATPAK_POLICY_OWN:
0591         return QStringLiteral("own");
0592     case FlatpakPolicy::FLATPAK_POLICY_NONE:
0593         break;
0594     }
0595     return QStringLiteral("none");
0596 }
0597 
0598 static FlatpakPolicy mapDBusFlatpakPolicyConfigStringToEnumValue(const QString &value)
0599 {
0600     if (value == QStringLiteral("see")) {
0601         return FlatpakPolicy::FLATPAK_POLICY_SEE;
0602     }
0603     if (value == QStringLiteral("talk")) {
0604         return FlatpakPolicy::FLATPAK_POLICY_TALK;
0605     }
0606     if (value == QStringLiteral("own")) {
0607         return FlatpakPolicy::FLATPAK_POLICY_OWN;
0608     }
0609     if (value != QStringLiteral("none")) {
0610         qWarning() << "Unsupported Flatpak D-Bus policy:" << value;
0611     }
0612     return FlatpakPolicy::FLATPAK_POLICY_NONE;
0613 }
0614 
0615 namespace FlatpakOverrides
0616 {
0617 
0618 KConfigPtr loadAndMerge(const QStringList &filenames)
0619 {
0620     auto config = std::make_unique<KConfig>(QString(), KConfig::SimpleConfig);
0621     for (const auto &filename : filenames) {
0622         merge(*config, filename);
0623     }
0624     return config;
0625 }
0626 
0627 void merge(KConfig &target, const QString &filename)
0628 {
0629     if (!QFileInfo::exists(filename)) {
0630         return;
0631     }
0632     const KConfig config(filename, KConfig::SimpleConfig);
0633     merge(target, config);
0634 }
0635 
0636 void merge(KConfig &target, const KConfig &source)
0637 {
0638     /***/ auto targetContextGroup = target.group(QLatin1String(FLATPAK_METADATA_GROUP_CONTEXT));
0639     const auto sourceContextGroup = source.group(QLatin1String(FLATPAK_METADATA_GROUP_CONTEXT));
0640 
0641     const std::array simpleCategories = {
0642         QLatin1String(FLATPAK_METADATA_KEY_SHARED),
0643         QLatin1String(FLATPAK_METADATA_KEY_SOCKETS),
0644         QLatin1String(FLATPAK_METADATA_KEY_DEVICES),
0645         QLatin1String(FLATPAK_METADATA_KEY_FEATURES),
0646     };
0647 
0648     for (const auto &category : simpleCategories) {
0649         const auto targetEntries = FlatpakSimpleEntry::getCategorySkippingInvalidEntries(targetContextGroup, category);
0650         const auto sourceEntries = FlatpakSimpleEntry::getCategorySkippingInvalidEntries(sourceContextGroup, category);
0651 
0652         QMap<QString, bool> entriesMap;
0653 
0654         for (const auto &entries : {targetEntries, sourceEntries}) {
0655             for (const auto &entry : entries) {
0656                 entriesMap.insert(entry.name(), entry.isEnabled());
0657             }
0658         }
0659 
0660         QStringList entriesList;
0661 
0662         for (auto it = entriesMap.constKeyValueBegin(); it != entriesMap.constKeyValueEnd(); it++) {
0663             const auto [name, enabled] = *it;
0664             entriesList.append(FlatpakSimpleEntry(name, enabled).format());
0665         }
0666 
0667         targetContextGroup.writeXdgListEntry(category, entriesList, KConfig::WriteConfigFlags{});
0668     }
0669 
0670     /* Filesystems */
0671     {
0672         const auto category = QLatin1String(FLATPAK_METADATA_KEY_FILESYSTEMS);
0673 
0674         const auto targetRawFilesystems = targetContextGroup.readXdgListEntry(category);
0675         const auto sourceRawFilesystems = sourceContextGroup.readXdgListEntry(category);
0676 
0677         QMap<QString, FlatpakFilesystemsEntry::AccessMode> entriesMap;
0678 
0679         for (const auto &rawFilesystems : {targetRawFilesystems, sourceRawFilesystems}) {
0680             const auto entries = filter_map(rawFilesystems, [](const QString &entry) {
0681                 return FlatpakFilesystemsEntry::parse(entry);
0682             });
0683             for (const auto &entry : entries) {
0684                 entriesMap.insert(entry.name(), entry.mode());
0685             }
0686         }
0687 
0688         QStringList entriesList;
0689 
0690         for (auto it = entriesMap.constKeyValueBegin(); it != entriesMap.constKeyValueEnd(); it++) {
0691             const auto [name, accessMode] = *it;
0692             if (const auto entry = FlatpakFilesystemsEntry::parse(name, accessMode); entry.has_value()) {
0693                 entriesList.append(entry.value().format());
0694             }
0695         }
0696 
0697         targetContextGroup.writeXdgListEntry(category, entriesList, KConfig::WriteConfigFlags());
0698     }
0699 
0700     const std::array categories = {
0701         QLatin1String(FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY),
0702         QLatin1String(FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY),
0703         QLatin1String(FLATPAK_METADATA_GROUP_ENVIRONMENT),
0704     };
0705     for (const auto &category : categories) {
0706         /***/ auto targetGroup = target.group(category);
0707         const auto sourceGroup = source.group(category);
0708 
0709         QMap<QString, QString> entriesMap;
0710 
0711         entriesMap.insert(targetGroup.entryMap());
0712         entriesMap.insert(sourceGroup.entryMap());
0713 
0714         for (auto it = entriesMap.constKeyValueBegin(); it != entriesMap.constKeyValueEnd(); it++) {
0715             const auto &[key, value] = *it;
0716             targetGroup.writeEntry(key, value, KConfig::WriteConfigFlags());
0717         }
0718     }
0719 }
0720 
0721 };
0722 
0723 FlatpakPermissionModel::FlatpakPermissionModel(QObject *parent)
0724     : QAbstractListModel(parent)
0725     , m_showAdvanced(false)
0726 {
0727 }
0728 
0729 int FlatpakPermissionModel::rowCount(const QModelIndex &parent) const
0730 {
0731     if (parent.isValid()) {
0732         return 0;
0733     }
0734     return rowCount(m_showAdvanced);
0735 }
0736 
0737 QVariant FlatpakPermissionModel::data(const QModelIndex &index, int role) const
0738 {
0739     if (!index.isValid()) {
0740         return QVariant();
0741     }
0742 
0743     const auto permission = m_permissions.at(index.row());
0744 
0745     switch (role) {
0746     case Roles::Section:
0747         return permission.section();
0748     case Roles::Name:
0749         return permission.name();
0750     case Roles::Description:
0751         return permission.description();
0752     //
0753     case Roles::IsNotDummy:
0754         return permission.originType() != FlatpakPermission::OriginType::Dummy;
0755     //
0756     case Roles::CanBeDisabled:
0757         return permission.canBeDisabled();
0758     case Roles::IsDefaultEnabled:
0759         return permission.isDefaultEnabled();
0760     case Roles::IsEffectiveEnabled:
0761         return permission.isEffectiveEnabled();
0762     case Roles::DefaultValue:
0763         return QVariant::fromStdVariant(permission.defaultValue());
0764     case Roles::EffectiveValue:
0765         return QVariant::fromStdVariant(permission.effectiveValue());
0766     //
0767     case Roles::ValuesModel:
0768         return QVariant::fromValue(FlatpakPermissionModel::valuesModelForSectionType(permission.section()));
0769     }
0770 
0771     return QVariant();
0772 }
0773 
0774 QHash<int, QByteArray> FlatpakPermissionModel::roleNames() const
0775 {
0776     QHash<int, QByteArray> roles;
0777     //
0778     roles[Roles::Section] = "section";
0779     roles[Roles::Name] = "name";
0780     roles[Roles::Description] = "description";
0781     //
0782     roles[Roles::IsNotDummy] = "isNotDummy";
0783     //
0784     roles[Roles::CanBeDisabled] = "canBeDisabled";
0785     roles[Roles::IsDefaultEnabled] = "isDefaultEnabled";
0786     roles[Roles::IsEffectiveEnabled] = "isEffectiveEnabled";
0787     roles[Roles::DefaultValue] = "defaultValue";
0788     roles[Roles::EffectiveValue] = "effectiveValue";
0789     //
0790     roles[Roles::ValuesModel] = "valuesModel";
0791     return roles;
0792 }
0793 
0794 void FlatpakPermissionModel::loadDefaultValues()
0795 {
0796     const auto defaults = m_reference->defaultsFiles();
0797     auto parser = FlatpakOverrides::loadAndMerge(defaults);
0798 
0799     const auto contextGroup = parser->group(QLatin1String(FLATPAK_METADATA_GROUP_CONTEXT));
0800 
0801     QString category;
0802     QList<FlatpakSimpleEntry> simpleEntries;
0803     int basicIndex = 0;
0804 
0805     const auto insertSimpleEntry = [&](FlatpakPermissionsSectionType::Type section, const QString &name, const QString &description) {
0806         const auto isEnabledByDefault = FlatpakSimpleEntry::isEnabled(simpleEntries, name).value_or(false);
0807         const auto permission = FlatpakPermission(section, name, category, description, isEnabledByDefault);
0808         if (section == FlatpakPermissionsSectionType::Basic) {
0809             m_permissions.insert(basicIndex, permission);
0810             basicIndex += 1;
0811         } else {
0812             m_permissions.append(permission);
0813         }
0814     };
0815 
0816     /* SHARED category */
0817     category = QLatin1String(FLATPAK_METADATA_KEY_SHARED);
0818     simpleEntries = FlatpakSimpleEntry::getCategorySkippingInvalidEntries(contextGroup, category);
0819     insertSimpleEntry(FlatpakPermissionsSectionType::Basic, QStringLiteral("network"), i18n("Internet Connection"));
0820     insertSimpleEntry(FlatpakPermissionsSectionType::SubsystemsShared, QStringLiteral("ipc"), i18n("Inter-process Communication"));
0821     /* SHARED category */
0822 
0823     /* SOCKETS category */
0824     category = QLatin1String(FLATPAK_METADATA_KEY_SOCKETS);
0825     simpleEntries = FlatpakSimpleEntry::getCategorySkippingInvalidEntries(contextGroup, category);
0826     insertSimpleEntry(FlatpakPermissionsSectionType::Sockets, QStringLiteral("x11"), i18n("X11 Windowing System"));
0827     insertSimpleEntry(FlatpakPermissionsSectionType::Sockets, QStringLiteral("wayland"), i18n("Wayland Windowing System"));
0828     insertSimpleEntry(FlatpakPermissionsSectionType::Sockets, QStringLiteral("fallback-x11"), i18n("Fallback to X11 Windowing System"));
0829     insertSimpleEntry(FlatpakPermissionsSectionType::Basic, QStringLiteral("pulseaudio"), i18n("Pulseaudio Sound Server"));
0830     insertSimpleEntry(FlatpakPermissionsSectionType::Sockets, QStringLiteral("session-bus"), i18n("Session Bus Access"));
0831     insertSimpleEntry(FlatpakPermissionsSectionType::Sockets, QStringLiteral("system-bus"), i18n("System Bus Access"));
0832     insertSimpleEntry(FlatpakPermissionsSectionType::Basic, QStringLiteral("ssh-auth"), i18n("Remote Login Access"));
0833     insertSimpleEntry(FlatpakPermissionsSectionType::Basic, QStringLiteral("pcsc"), i18n("Smart Card Access"));
0834     insertSimpleEntry(FlatpakPermissionsSectionType::Basic, QStringLiteral("cups"), i18n("Print System Access"));
0835     /* SOCKETS category */
0836 
0837     /* DEVICES category */
0838     category = QLatin1String(FLATPAK_METADATA_KEY_DEVICES);
0839     simpleEntries = FlatpakSimpleEntry::getCategorySkippingInvalidEntries(contextGroup, category);
0840     insertSimpleEntry(FlatpakPermissionsSectionType::Devices, QStringLiteral("kvm"), i18n("Kernel-based Virtual Machine Access"));
0841     insertSimpleEntry(FlatpakPermissionsSectionType::Devices, QStringLiteral("dri"), i18n("Direct Graphic Rendering"));
0842     insertSimpleEntry(FlatpakPermissionsSectionType::Devices, QStringLiteral("shm"), i18n("Host dev/shm"));
0843     insertSimpleEntry(FlatpakPermissionsSectionType::Basic, QStringLiteral("all"), i18n("Device Access"));
0844     /* DEVICES category */
0845 
0846     /* FEATURES category */
0847     category = QLatin1String(FLATPAK_METADATA_KEY_FEATURES);
0848     simpleEntries = FlatpakSimpleEntry::getCategorySkippingInvalidEntries(contextGroup, category);
0849     insertSimpleEntry(FlatpakPermissionsSectionType::Features, QStringLiteral("devel"), i18n("System Calls by Development Tools"));
0850     insertSimpleEntry(FlatpakPermissionsSectionType::Features, QStringLiteral("multiarch"), i18n("Run Multiarch/Multilib Binaries"));
0851     insertSimpleEntry(FlatpakPermissionsSectionType::Basic, QStringLiteral("bluetooth"), i18n("Bluetooth"));
0852     insertSimpleEntry(FlatpakPermissionsSectionType::Features, QStringLiteral("canbus"), i18n("Canbus Socket Access"));
0853     insertSimpleEntry(FlatpakPermissionsSectionType::Features,
0854                       QStringLiteral("per-app-dev-shm"),
0855                       i18n("Share dev/shm across all instances of an app per user ID"));
0856     /* FEATURES category */
0857 
0858     /* FILESYSTEM category */
0859     category = QLatin1String(FLATPAK_METADATA_KEY_FILESYSTEMS);
0860     const auto rawFilesystems = contextGroup.readXdgListEntry(category);
0861     const auto filesystems = filter_map(rawFilesystems, [](const QString &entry) {
0862         return FlatpakFilesystemsEntry::parse(entry);
0863     });
0864 
0865     std::optional<FlatpakFilesystemsEntry::AccessMode> homeVal = std::nullopt;
0866     std::optional<FlatpakFilesystemsEntry::AccessMode> hostVal = std::nullopt;
0867     std::optional<FlatpakFilesystemsEntry::AccessMode> hostOsVal = std::nullopt;
0868     std::optional<FlatpakFilesystemsEntry::AccessMode> hostEtcVal = std::nullopt;
0869 
0870     QVector<FlatpakFilesystemsEntry> nonStandardFilesystems;
0871 
0872     static const auto ignoredFilesystems = QList<FlatpakFilesystemsEntry>{
0873         FlatpakFilesystemsEntry(FlatpakFilesystemsEntry::FilesystemPrefix::XdgConfig,
0874                                 FlatpakFilesystemsEntry::AccessMode::ReadOnly,
0875                                 QLatin1String("kdeglobals")),
0876     };
0877 
0878     for (const auto &filesystem : filesystems) {
0879         if (ignoredFilesystems.contains(filesystem)) {
0880             continue;
0881         }
0882 
0883         if (filesystem.path().isEmpty()) {
0884             switch (filesystem.prefix()) {
0885             case FlatpakFilesystemsEntry::FilesystemPrefix::Home:
0886                 homeVal = filesystem.mode();
0887                 continue;
0888             case FlatpakFilesystemsEntry::FilesystemPrefix::Host:
0889                 hostVal = filesystem.mode();
0890                 continue;
0891             case FlatpakFilesystemsEntry::FilesystemPrefix::HostOs:
0892                 hostOsVal = filesystem.mode();
0893                 continue;
0894             case FlatpakFilesystemsEntry::FilesystemPrefix::HostEtc:
0895                 hostEtcVal = filesystem.mode();
0896                 continue;
0897             default:
0898                 break;
0899             }
0900         }
0901         nonStandardFilesystems.append(filesystem);
0902     }
0903 
0904     const auto insertStandardFilesystemsEntry =
0905         [&](const QString &name, const QString &description, std::optional<FlatpakFilesystemsEntry::AccessMode> accessMode) {
0906             const auto enabled = accessMode.has_value();
0907             const auto effectiveAccessMode = accessMode.value_or(FlatpakFilesystemsEntry::AccessMode::ReadOnly);
0908             m_permissions.insert(basicIndex,
0909                                  FlatpakPermission(FlatpakPermissionsSectionType::Filesystems, name, category, description, enabled, effectiveAccessMode));
0910             basicIndex += 1;
0911         };
0912 
0913     insertStandardFilesystemsEntry(QStringLiteral("home"), i18n("All User Files"), homeVal);
0914     insertStandardFilesystemsEntry(QStringLiteral("host"), i18n("All System Files"), hostVal);
0915     insertStandardFilesystemsEntry(QStringLiteral("host-os"), i18n("All System Libraries, Executables and Binaries"), hostOsVal);
0916     insertStandardFilesystemsEntry(QStringLiteral("host-etc"), i18n("All System Configurations"), hostEtcVal);
0917 
0918     for (const auto &filesystem : std::as_const(nonStandardFilesystems)) {
0919         const auto name = filesystem.name();
0920         const auto accessMode = filesystem.mode();
0921         m_permissions.insert(basicIndex, FlatpakPermission(FlatpakPermissionsSectionType::Filesystems, name, category, name, true, accessMode));
0922         basicIndex += 1;
0923     }
0924     /* FILESYSTEM category */
0925 
0926     /* DUMMY ADVANCED category */
0927     m_permissions.insert(basicIndex, FlatpakPermission(FlatpakPermissionsSectionType::Advanced));
0928     basicIndex += 1;
0929     /* DUMMY ADVANCED category */
0930 
0931     /* SESSION BUS category */
0932     {
0933         category = QLatin1String(FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY);
0934         const auto group = parser->group(category);
0935         if (const auto keys = group.keyList(); !keys.isEmpty()) {
0936             for (const auto &name : keys) {
0937                 const auto policyString = group.readEntry(name);
0938                 const auto policyValue = mapDBusFlatpakPolicyConfigStringToEnumValue(policyString);
0939                 m_permissions.append(FlatpakPermission(FlatpakPermissionsSectionType::SessionBus, name, category, name, true, policyValue));
0940             }
0941         } else {
0942             m_permissions.append(FlatpakPermission(FlatpakPermissionsSectionType::SessionBus));
0943         }
0944     }
0945     /* SESSION BUS category */
0946 
0947     /* SYSTEM BUS category */
0948     {
0949         category = QLatin1String(FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY);
0950         const auto group = parser->group(category);
0951         if (const auto keys = group.keyList(); !keys.isEmpty()) {
0952             for (const auto &name : keys) {
0953                 const auto policyString = group.readEntry(name);
0954                 const auto policyValue = mapDBusFlatpakPolicyConfigStringToEnumValue(policyString);
0955                 m_permissions.append(FlatpakPermission(FlatpakPermissionsSectionType::SystemBus, name, category, name, true, policyValue));
0956             }
0957         } else {
0958             m_permissions.append(FlatpakPermission(FlatpakPermissionsSectionType::SystemBus));
0959         }
0960     }
0961     /* SYSTEM BUS category */
0962 
0963     /* ENVIRONMENT category */
0964     {
0965         category = QLatin1String(FLATPAK_METADATA_GROUP_ENVIRONMENT);
0966         const auto group = parser->group(category);
0967         if (const auto keys = group.keyList(); !keys.isEmpty()) {
0968             for (const auto &name : keys) {
0969                 const auto value = group.readEntry(name);
0970                 m_permissions.append(FlatpakPermission(FlatpakPermissionsSectionType::Environment, name, category, name, true, value));
0971             }
0972         } else {
0973             m_permissions.append(FlatpakPermission(FlatpakPermissionsSectionType::Environment));
0974         }
0975     }
0976     /* ENVIRONMENT category */
0977 }
0978 
0979 void FlatpakPermissionModel::loadCurrentValues()
0980 {
0981     const auto &userAppOverrides = m_reference->userLevelPerAppOverrideFile();
0982 
0983     /* all permissions are at default, so nothing to load */
0984     if (!QFileInfo::exists(userAppOverrides)) {
0985         return;
0986     }
0987 
0988     const KConfig parser(userAppOverrides, KConfig::SimpleConfig);
0989     const auto contextGroup = parser.group(QLatin1String(FLATPAK_METADATA_GROUP_CONTEXT));
0990 
0991     // Mapping to valid entries. Invalid ones go into m_unparsableEntriesByCategory.
0992     QHash<QString, QList<FlatpakSimpleEntry>> entriesByCategory;
0993 
0994     const std::array simpleCategories = {
0995         QLatin1String(FLATPAK_METADATA_KEY_SHARED),
0996         QLatin1String(FLATPAK_METADATA_KEY_SOCKETS),
0997         QLatin1String(FLATPAK_METADATA_KEY_DEVICES),
0998         QLatin1String(FLATPAK_METADATA_KEY_FEATURES),
0999     };
1000 
1001     for (const auto &category : simpleCategories) {
1002         const auto [unparsable, entries] = FlatpakSimpleEntry::getCategory(contextGroup, category);
1003         if (!unparsable.isEmpty()) {
1004             m_unparsableEntriesByCategory.insert(category, unparsable);
1005         }
1006         if (!entries.isEmpty()) {
1007             entriesByCategory.insert(category, entries);
1008         }
1009     }
1010 
1011     const auto rawFilesystems = contextGroup.readXdgListEntry(QLatin1String(FLATPAK_METADATA_KEY_FILESYSTEMS));
1012     const auto [unparsableFilesystems, filesystems] = try_filter_map(rawFilesystems, [](const QString &entry) {
1013         return FlatpakFilesystemsEntry::parse(entry);
1014     });
1015     if (!unparsableFilesystems.isEmpty()) {
1016         m_unparsableEntriesByCategory.insert(QLatin1String(FLATPAK_METADATA_KEY_FILESYSTEMS), unparsableFilesystems);
1017     }
1018 
1019     int fsIndex = -1;
1020 
1021     for (int i = 0; i < m_permissions.length(); ++i) {
1022         FlatpakPermission &permission = m_permissions[i];
1023 
1024         switch (permission.valueType()) {
1025         case FlatpakPermission::ValueType::Simple: {
1026             if (!entriesByCategory.contains(permission.category())) {
1027                 continue;
1028             }
1029             const auto &entries = entriesByCategory[permission.category()];
1030             if (const auto entry = FlatpakSimpleEntry::isEnabled(entries, permission.name()); entry.has_value()) {
1031                 const auto isEnabled = entry.value();
1032                 permission.setEffectiveEnabled(isEnabled);
1033                 permission.setOverrideEnabled(isEnabled);
1034             }
1035             break;
1036         }
1037         case FlatpakPermission::ValueType::Filesystems: {
1038             const auto it = std::find_if(filesystems.constBegin(), filesystems.constEnd(), [=](const FlatpakFilesystemsEntry &filesystem) {
1039                 return filesystem.name() == permission.name();
1040             });
1041             if (it != filesystems.constEnd()) {
1042                 const auto &filesystem = *it;
1043 
1044                 permission.setOverrideEnabled(true);
1045                 permission.setEffectiveEnabled(true);
1046 
1047                 permission.setOverrideValue(filesystem.mode());
1048                 permission.setEffectiveValue(filesystem.mode());
1049             }
1050             fsIndex = i + 1;
1051             break;
1052         }
1053         case FlatpakPermission::ValueType::Bus:
1054         case FlatpakPermission::ValueType::Environment: {
1055             const auto group = parser.group(permission.category());
1056             if (group.hasKey(permission.name())) {
1057                 const auto value = group.readEntry(permission.name());
1058 
1059                 if (permission.valueType() == FlatpakPermission::ValueType::Bus) {
1060                     const auto policyValue = mapDBusFlatpakPolicyConfigStringToEnumValue(value);
1061                     permission.setOverrideValue(policyValue);
1062                     permission.setEffectiveValue(policyValue);
1063                 } else {
1064                     permission.setOverrideValue(value);
1065                     permission.setEffectiveValue(value);
1066                 }
1067 
1068                 permission.setOverrideEnabled(true);
1069                 permission.setEffectiveEnabled(true);
1070             }
1071             break;
1072         }
1073         } // end of switch
1074     }
1075 
1076     if (!filesystems.isEmpty()) {
1077         const auto section = FlatpakPermissionsSectionType::Filesystems;
1078         const auto category = QLatin1String(FLATPAK_METADATA_KEY_FILESYSTEMS);
1079         for (const auto &filesystem : filesystems) {
1080             const auto name = filesystem.name();
1081             const auto accessMode = filesystem.mode();
1082             if (!permissionExists(section, name)) {
1083                 m_permissions.insert(fsIndex, FlatpakPermission(section, name, category, name, false, accessMode));
1084                 m_permissions[fsIndex].setOverrideEnabled(true);
1085                 m_permissions[fsIndex].setEffectiveEnabled(true);
1086                 fsIndex++;
1087             }
1088         }
1089     }
1090 
1091     const std::array categories = {
1092         std::make_tuple(FlatpakPermissionsSectionType::SessionBus, QLatin1String(FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY)),
1093         std::make_tuple(FlatpakPermissionsSectionType::SystemBus, QLatin1String(FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY)),
1094         std::make_tuple(FlatpakPermissionsSectionType::Environment, QLatin1String(FLATPAK_METADATA_GROUP_ENVIRONMENT)),
1095     };
1096     for (const auto &[section, category] : categories) {
1097         const auto group = parser.group(category);
1098         if (!group.exists()) {
1099             continue;
1100         }
1101         const auto valueType = FlatpakPermission::valueTypeFromSectionType(section);
1102         // Model signals are not needed during load/reset.
1103         int insertIndex = findIndexToInsertRowAndRemoveDummyRowIfNeeded(section, false);
1104 
1105         const auto keys = group.keyList();
1106         for (const auto &name : keys) {
1107             if (!permissionExists(section, name)) {
1108                 const auto value = group.readEntry(name);
1109 
1110                 if (valueType == FlatpakPermission::ValueType::Bus) {
1111                     const auto policyValue = mapDBusFlatpakPolicyConfigStringToEnumValue(value);
1112                     m_permissions.insert(insertIndex, FlatpakPermission(section, name, category, name, false, policyValue));
1113                 } else {
1114                     m_permissions.insert(insertIndex, FlatpakPermission(section, name, category, name, false, value));
1115                 }
1116 
1117                 m_permissions[insertIndex].setOverrideEnabled(true);
1118                 m_permissions[insertIndex].setEffectiveEnabled(true);
1119                 insertIndex++;
1120             }
1121         }
1122     }
1123 }
1124 
1125 FlatpakReference *FlatpakPermissionModel::reference() const
1126 {
1127     return m_reference;
1128 }
1129 
1130 void FlatpakPermissionModel::setReference(FlatpakReference *reference)
1131 {
1132     if (reference != m_reference) {
1133         beginResetModel();
1134         if (m_reference) {
1135             m_reference->setPermissionsModel(nullptr);
1136         }
1137         m_reference = reference;
1138         if (m_reference) {
1139             m_reference->setPermissionsModel(this);
1140         }
1141         endResetModel();
1142         Q_EMIT referenceChanged();
1143     }
1144 }
1145 
1146 bool FlatpakPermissionModel::showAdvanced() const
1147 {
1148     return m_showAdvanced;
1149 }
1150 
1151 void FlatpakPermissionModel::setShowAdvanced(bool show)
1152 {
1153     if (m_showAdvanced != show) {
1154         const int whenHidden = rowCount(false);
1155         const int whenShown = rowCount(true);
1156 
1157         if (show) {
1158             beginInsertRows(QModelIndex(), whenHidden, whenShown - 1);
1159         } else {
1160             beginRemoveRows(QModelIndex(), whenHidden, whenShown - 1);
1161         }
1162 
1163         m_showAdvanced = show;
1164 
1165         if (show) {
1166             endInsertRows();
1167         } else {
1168             endRemoveRows();
1169         }
1170 
1171         Q_EMIT showAdvancedChanged();
1172     }
1173 }
1174 
1175 int FlatpakPermissionModel::rowCount(bool showAdvanced) const
1176 {
1177     if (showAdvanced) {
1178         return m_permissions.count();
1179     } else {
1180         int count = 0;
1181         for (const auto &permission : m_permissions) {
1182             if (permission.section() == FlatpakPermissionsSectionType::Basic || permission.section() == FlatpakPermissionsSectionType::Filesystems
1183                 || permission.section() == FlatpakPermissionsSectionType::Advanced) {
1184                 count += 1;
1185             } else {
1186                 break;
1187             }
1188         }
1189         return count;
1190     }
1191 }
1192 
1193 void FlatpakPermissionModel::load()
1194 {
1195     beginResetModel();
1196     {
1197         m_permissions.clear();
1198         m_unparsableEntriesByCategory.clear();
1199         loadDefaultValues();
1200         loadCurrentValues();
1201     }
1202     endResetModel();
1203 }
1204 
1205 void FlatpakPermissionModel::save()
1206 {
1207     for (auto &permission : m_permissions) {
1208         permission.setOverrideEnabled(permission.isEffectiveEnabled());
1209         if (permission.valueType() != FlatpakPermission::ValueType::Simple) {
1210             permission.setOverrideValue(permission.effectiveValue());
1211             if (!permission.isDefaultEnabled()) {
1212                 permission.setDefaultValue(permission.effectiveValue());
1213             }
1214         }
1215     }
1216     Q_EMIT dataChanged(index(0), index(rowCount() - 1), {Roles::DefaultValue});
1217     writeToFile();
1218 }
1219 
1220 void FlatpakPermissionModel::defaults()
1221 {
1222     for (auto &permission : m_permissions) {
1223         permission.setEffectiveEnabled(permission.isDefaultEnabled());
1224         if (permission.valueType() != FlatpakPermission::ValueType::Simple) {
1225             permission.setEffectiveValue(permission.defaultValue());
1226         }
1227     }
1228     Q_EMIT dataChanged(index(0), index(rowCount() - 1), {Roles::IsEffectiveEnabled, Roles::EffectiveValue});
1229 }
1230 
1231 bool FlatpakPermissionModel::isDefaults() const
1232 {
1233     return std::all_of(m_permissions.constBegin(), m_permissions.constEnd(), [](const FlatpakPermission &permission) {
1234         return permission.isDefaults();
1235     });
1236 }
1237 
1238 bool FlatpakPermissionModel::isSaveNeeded() const
1239 {
1240     return std::any_of(m_permissions.constBegin(), m_permissions.constEnd(), [](const FlatpakPermission &permission) {
1241         return permission.isSaveNeeded();
1242     });
1243 }
1244 
1245 PolicyChoicesModel *FlatpakPermissionModel::valuesModelForSectionType(int /*FlatpakPermissionsSectionType::Type*/ rawSection)
1246 {
1247     if (!QMetaEnum::fromType<FlatpakPermissionsSectionType::Type>().valueToKey(rawSection)) {
1248         return {};
1249     }
1250     // SAFETY: QMetaEnum above ensures that coercion is valid.
1251     const auto section = static_cast<FlatpakPermissionsSectionType::Type>(rawSection);
1252 
1253     switch (section) {
1254     case FlatpakPermissionsSectionType::Filesystems:
1255         return valuesModelForFilesystemsSection();
1256     case FlatpakPermissionsSectionType::SessionBus:
1257     case FlatpakPermissionsSectionType::SystemBus:
1258         return valuesModelForBusSections();
1259     case FlatpakPermissionsSectionType::Basic:
1260     case FlatpakPermissionsSectionType::Advanced:
1261     case FlatpakPermissionsSectionType::SubsystemsShared:
1262     case FlatpakPermissionsSectionType::Sockets:
1263     case FlatpakPermissionsSectionType::Devices:
1264     case FlatpakPermissionsSectionType::Features:
1265     case FlatpakPermissionsSectionType::Environment:
1266         break;
1267     }
1268 
1269     return {};
1270 }
1271 
1272 PolicyChoicesModel *FlatpakPermissionModel::valuesModelForFilesystemsSection()
1273 {
1274     QQmlEngine::setObjectOwnership(s_FilesystemPolicies, QQmlEngine::CppOwnership);
1275     return s_FilesystemPolicies;
1276 }
1277 
1278 PolicyChoicesModel *FlatpakPermissionModel::valuesModelForBusSections()
1279 {
1280     QQmlEngine::setObjectOwnership(s_DBusPolicies, QQmlEngine::CppOwnership);
1281     return s_DBusPolicies;
1282 }
1283 
1284 Q_INVOKABLE QString FlatpakPermissionModel::sectionHeaderForSectionType(int /*FlatpakPermissionsSectionType::Type*/ rawSection)
1285 {
1286     if (!QMetaEnum::fromType<FlatpakPermissionsSectionType::Type>().valueToKey(rawSection)) {
1287         return {};
1288     }
1289     // SAFETY: QMetaEnum above ensures that coercion is valid.
1290     const auto section = static_cast<FlatpakPermissionsSectionType::Type>(rawSection);
1291 
1292     switch (section) {
1293     case FlatpakPermissionsSectionType::Basic:
1294         return i18n("Basic Permissions");
1295     case FlatpakPermissionsSectionType::Filesystems:
1296         return i18n("Filesystem Access");
1297     case FlatpakPermissionsSectionType::Advanced:
1298         return i18n("Advanced Permissions");
1299     case FlatpakPermissionsSectionType::SubsystemsShared:
1300         return i18n("Subsystems Shared");
1301     case FlatpakPermissionsSectionType::Sockets:
1302         return i18n("Sockets");
1303     case FlatpakPermissionsSectionType::Devices:
1304         return i18n("Device Access");
1305     case FlatpakPermissionsSectionType::Features:
1306         return i18n("Features Allowed");
1307     case FlatpakPermissionsSectionType::SessionBus:
1308         return i18n("Session Bus Policy");
1309     case FlatpakPermissionsSectionType::SystemBus:
1310         return i18n("System Bus Policy");
1311     case FlatpakPermissionsSectionType::Environment:
1312         return i18n("Environment");
1313     }
1314 
1315     Q_UNREACHABLE();
1316     return {};
1317 }
1318 
1319 Q_INVOKABLE QString FlatpakPermissionModel::sectionAddButtonToolTipTextForSectionType(int /*FlatpakPermissionsSectionType::Type*/ rawSection)
1320 {
1321     if (!QMetaEnum::fromType<FlatpakPermissionsSectionType::Type>().valueToKey(rawSection)) {
1322         return {};
1323     }
1324     // SAFETY: QMetaEnum above ensures that coercion is valid.
1325     const auto section = static_cast<FlatpakPermissionsSectionType::Type>(rawSection);
1326 
1327     switch (section) {
1328     case FlatpakPermissionsSectionType::Filesystems:
1329         return i18n("Add a new filesystem path");
1330     case FlatpakPermissionsSectionType::SessionBus:
1331         return i18n("Add a new session bus");
1332     case FlatpakPermissionsSectionType::SystemBus:
1333         return i18n("Add a new system bus");
1334     case FlatpakPermissionsSectionType::Environment:
1335         return i18n("Add a new environment variable");
1336     case FlatpakPermissionsSectionType::Basic:
1337     case FlatpakPermissionsSectionType::Advanced:
1338     case FlatpakPermissionsSectionType::SubsystemsShared:
1339     case FlatpakPermissionsSectionType::Sockets:
1340     case FlatpakPermissionsSectionType::Devices:
1341     case FlatpakPermissionsSectionType::Features:
1342         break;
1343     }
1344 
1345     return {};
1346 }
1347 
1348 bool FlatpakPermissionModel::permissionExists(int /*FlatpakPermissionsSectionType::Type*/ rawSection, const QString &name) const
1349 {
1350     if (!QMetaEnum::fromType<FlatpakPermissionsSectionType::Type>().valueToKey(rawSection)) {
1351         return false;
1352     }
1353     // SAFETY: QMetaEnum above ensures that coercion is valid.
1354     const auto section = static_cast<FlatpakPermissionsSectionType::Type>(rawSection);
1355 
1356     return permissionExists(section, name);
1357 }
1358 
1359 bool FlatpakPermissionModel::permissionExists(FlatpakPermissionsSectionType::Type section, const QString &name) const
1360 {
1361     return findPermissionRow(section, name).has_value();
1362 }
1363 
1364 std::optional<int> FlatpakPermissionModel::findPermissionRow(FlatpakPermissionsSectionType::Type section, const QString &name) const
1365 {
1366     for (int i = 0; i < m_permissions.length(); i++) {
1367         const auto &permission = m_permissions[i];
1368         if (permission.section() == section && permission.name() == name) {
1369             return std::optional(i);
1370         }
1371     }
1372     return std::nullopt;
1373 }
1374 
1375 QModelIndex FlatpakPermissionModel::findPermissionIndex(FlatpakPermissionsSectionType::Type section, const QString &name) const
1376 {
1377     auto row = findPermissionRow(section, name);
1378     if (row.has_value()) {
1379         return index(row.value());
1380     }
1381     return QModelIndex();
1382 }
1383 
1384 bool FlatpakPermissionModel::isFilesystemNameValid(const QString &name)
1385 {
1386     // Force parsing the name part only, pass dummy access mode.
1387     return FlatpakFilesystemsEntry::parse(name, FlatpakFilesystemsEntry::AccessMode::ReadWrite).has_value();
1388 }
1389 
1390 bool FlatpakPermissionModel::isDBusServiceNameValid(const QString &name)
1391 {
1392     return FlatpakHelper::verifyDBusName(name);
1393 }
1394 
1395 bool FlatpakPermissionModel::isEnvironmentVariableNameValid(const QString &name)
1396 {
1397     return !name.isEmpty() && !name.contains(QLatin1Char('='));
1398 }
1399 
1400 void FlatpakPermissionModel::togglePermissionAtRow(int row)
1401 {
1402     if (row < 0 || row >= m_permissions.length()) {
1403         return;
1404     }
1405 
1406     FlatpakPermission &permission = m_permissions[row];
1407 
1408     const auto wasEnabled = permission.isEffectiveEnabled();
1409     const auto shouldBecomeEnabled = !wasEnabled;
1410 
1411     if (!shouldBecomeEnabled && !permission.canBeDisabled()) {
1412         qWarning() << "Illegal operation: Permission provided by upstream can not be toggled:" << permission.category() << permission.name();
1413         return;
1414     }
1415 
1416     permission.setEffectiveEnabled(shouldBecomeEnabled);
1417 
1418     Q_EMIT dataChanged(index(row), index(row), {Roles::IsEffectiveEnabled});
1419 }
1420 
1421 void FlatpakPermissionModel::setPermissionValueAtRow(int row, const QVariant &value)
1422 {
1423     if (row < 0 || row >= m_permissions.length()) {
1424         return;
1425     }
1426 
1427     FlatpakPermission &permission = m_permissions[row];
1428 
1429     switch (permission.section()) {
1430     case FlatpakPermissionsSectionType::Filesystems:
1431         if (!value.canConvert<FlatpakFilesystemsEntry::AccessMode>()) {
1432             qWarning() << "Wrong data type assigned to Filesystem entry:" << value;
1433             return;
1434         }
1435         permission.setEffectiveValue(value.value<FlatpakFilesystemsEntry::AccessMode>());
1436         break;
1437     case FlatpakPermissionsSectionType::SessionBus:
1438     case FlatpakPermissionsSectionType::SystemBus:
1439         if (!value.canConvert<FlatpakPolicy>()) {
1440             qWarning() << "Wrong data type assigned to D-Bus entry:" << value;
1441             return;
1442         }
1443         permission.setEffectiveValue(value.value<FlatpakPolicy>());
1444         break;
1445     case FlatpakPermissionsSectionType::Environment:
1446         if (!value.canConvert<QString>()) {
1447             qWarning() << "Wrong data type assigned to Environment entry:" << value;
1448             return;
1449         }
1450         permission.setEffectiveValue(value.toString());
1451         break;
1452     case FlatpakPermissionsSectionType::Basic:
1453     case FlatpakPermissionsSectionType::Advanced:
1454     case FlatpakPermissionsSectionType::SubsystemsShared:
1455     case FlatpakPermissionsSectionType::Sockets:
1456     case FlatpakPermissionsSectionType::Devices:
1457     case FlatpakPermissionsSectionType::Features:
1458         return;
1459     }
1460     permission.setEffectiveEnabled(true);
1461 
1462     Q_EMIT dataChanged(index(row), index(row), {Roles::IsEffectiveEnabled, Roles::EffectiveValue});
1463 }
1464 
1465 void FlatpakPermissionModel::addUserEnteredPermission(int /*FlatpakPermissionsSectionType::Type*/ rawSection, const QString &name, const QVariant &value)
1466 {
1467     if (!QMetaEnum::fromType<FlatpakPermissionsSectionType::Type>().valueToKey(rawSection)) {
1468         return;
1469     }
1470     // SAFETY: QMetaEnum above ensures that coercion is valid.
1471     const auto section = static_cast<FlatpakPermissionsSectionType::Type>(rawSection);
1472     QString category;
1473     FlatpakPermission::Variant variant;
1474 
1475     if (permissionExists(section, name)) {
1476         qWarning() << "Tried to add duplicate entry" << section << name;
1477         return;
1478     }
1479 
1480     switch (section) {
1481     case FlatpakPermissionsSectionType::Filesystems:
1482         if (!isFilesystemNameValid(name)) {
1483             qWarning() << "Tried to add Filesystem entry with invalid name:" << name;
1484             return;
1485         }
1486         if (!value.canConvert<FlatpakFilesystemsEntry::AccessMode>()) {
1487             qWarning() << "Tried to add Filesystem entry with wrong data type:" << value;
1488             return;
1489         }
1490         category = QLatin1String(FLATPAK_METADATA_KEY_FILESYSTEMS);
1491         variant = value.value<FlatpakFilesystemsEntry::AccessMode>();
1492         break;
1493     case FlatpakPermissionsSectionType::SessionBus:
1494     case FlatpakPermissionsSectionType::SystemBus:
1495         if (!isDBusServiceNameValid(name)) {
1496             qWarning() << "Tried to add D-Bus entry with invalid service name or prefix:" << name;
1497             return;
1498         }
1499         if (!value.canConvert<FlatpakPolicy>()) {
1500             qWarning() << "Wrong data type assigned to D-Bus entry:" << value;
1501             return;
1502         }
1503         category = (section == FlatpakPermissionsSectionType::SessionBus) //
1504             ? QLatin1String(FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY)
1505             : QLatin1String(FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY);
1506         variant = value.value<FlatpakPolicy>();
1507         break;
1508     case FlatpakPermissionsSectionType::Environment:
1509         if (!isEnvironmentVariableNameValid(name)) {
1510             qWarning() << "Tried to add Environment entry with invalid name:" << name;
1511             return;
1512         }
1513         if (!value.canConvert<QString>()) {
1514             qWarning() << "Wrong data type assigned to Environment entry:" << value;
1515             return;
1516         }
1517         category = QLatin1String(FLATPAK_METADATA_GROUP_ENVIRONMENT);
1518         variant = value.toString();
1519         break;
1520     case FlatpakPermissionsSectionType::Basic:
1521     case FlatpakPermissionsSectionType::Advanced:
1522     case FlatpakPermissionsSectionType::SubsystemsShared:
1523     case FlatpakPermissionsSectionType::Sockets:
1524     case FlatpakPermissionsSectionType::Devices:
1525     case FlatpakPermissionsSectionType::Features:
1526         return;
1527     }
1528 
1529     FlatpakPermission permission(section, name, category, name, false, variant);
1530     permission.setOriginType(FlatpakPermission::OriginType::UserDefined);
1531     permission.setEffectiveEnabled(true);
1532 
1533     int index = findIndexToInsertRowAndRemoveDummyRowIfNeeded(section, true);
1534     beginInsertRows(QModelIndex(), index, index);
1535     {
1536         m_permissions.insert(index, permission);
1537     }
1538     endInsertRows();
1539 }
1540 
1541 int FlatpakPermissionModel::findIndexToInsertRowAndRemoveDummyRowIfNeeded(FlatpakPermissionsSectionType::Type section, bool emitModelSignals)
1542 {
1543     int i = 0;
1544     while (i < m_permissions.length()) {
1545         const auto *permission = &m_permissions.at(i);
1546         if (permission->section() == section) {
1547             if (permission->originType() == FlatpakPermission::OriginType::Dummy) {
1548                 if (emitModelSignals) {
1549                     beginRemoveRows(QModelIndex(), i, i);
1550                 }
1551                 permission = nullptr;
1552                 m_permissions.remove(i, 1);
1553                 if (emitModelSignals) {
1554                     endRemoveRows();
1555                 }
1556             }
1557             break;
1558         }
1559         i++;
1560     }
1561     while (i < m_permissions.length()) {
1562         const auto &permission = m_permissions.at(i);
1563         if (permission.section() != section) {
1564             break;
1565         }
1566         i++;
1567     }
1568     return i;
1569 }
1570 
1571 void FlatpakPermissionModel::writeToFile() const
1572 {
1573     const auto &userAppOverrides = m_reference->userLevelPerAppOverrideFile();
1574     if (isDefaults()) {
1575         QFile::remove(userAppOverrides);
1576     } else {
1577         // Ensure directory exists before creating a config in it.
1578         const auto dir = QFileInfo(userAppOverrides).dir();
1579         QDir().mkpath(dir.path());
1580 
1581         KConfig config(userAppOverrides, KConfig::SimpleConfig);
1582         if (!config.isConfigWritable(true)) {
1583             return;
1584         }
1585         // Clear the config first
1586         if (const auto groups = config.groupList(); !groups.isEmpty()) {
1587             for (const auto &group : groups) {
1588                 config.deleteGroup(group);
1589             }
1590         }
1591         writeToKConfig(config);
1592     }
1593 }
1594 
1595 void FlatpakPermissionModel::writeToKConfig(KConfig &config) const
1596 {
1597     QHash<QString, QStringList> entriesByCategory = m_unparsableEntriesByCategory;
1598 
1599     for (const auto &permission : m_permissions) {
1600         if (permission.isDefaults()) {
1601             continue;
1602         }
1603         switch (permission.valueType()) {
1604         case FlatpakPermission::ValueType::Simple: {
1605             auto &entries = entriesByCategory[permission.category()];
1606             const auto entry = FlatpakSimpleEntry(permission.name(), permission.isEffectiveEnabled());
1607             entries.append(entry.format());
1608             break;
1609         }
1610         case FlatpakPermission::ValueType::Filesystems: {
1611             auto &entries = entriesByCategory[permission.category()];
1612             const auto accessMode = std::get<FlatpakFilesystemsEntry::AccessMode>(permission.effectiveValue());
1613             FlatpakFilesystemsEntry::parse(permission.name(), accessMode);
1614             if (const auto entry = FlatpakFilesystemsEntry::parse(permission.name(), accessMode); entry.has_value()) {
1615                 entries.append(entry.value().format());
1616             }
1617             break;
1618         }
1619         case FlatpakPermission::ValueType::Bus: {
1620             const auto policyValue = std::get<FlatpakPolicy>(permission.effectiveValue());
1621             const auto policyString = mapDBusFlatpakPolicyEnumValueToConfigString(policyValue);
1622             auto group = config.group(permission.category());
1623             group.writeEntry(permission.name(), policyString);
1624             break;
1625         }
1626         case FlatpakPermission::ValueType::Environment: {
1627             const auto value = std::get<QString>(permission.effectiveValue());
1628             auto group = config.group(permission.category());
1629             group.writeEntry(permission.name(), value);
1630             break;
1631         }
1632         } // switch
1633     }
1634 
1635     auto contextGroup = config.group(QLatin1String(FLATPAK_METADATA_GROUP_CONTEXT));
1636     for (auto it = entriesByCategory.constKeyValueBegin(); it != entriesByCategory.constKeyValueEnd(); it++) {
1637         const auto [category, entries] = *it;
1638         contextGroup.writeXdgListEntry(category, entries);
1639     }
1640 }