File indexing completed on 2024-09-08 07:56:39
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(QList<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 QList<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 QList<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 QList<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 } 1641 1642 #include "moc_flatpakpermission.cpp"