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

0001 /**
0002  * SPDX-FileCopyrightText: 2022 Suhaas Joshi <joshiesuhaas0@gmail.com>
0003  * SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #pragma once
0008 
0009 #include "flatpakcommon.h"
0010 #include "flatpakreference.h"
0011 
0012 #include <KConfigGroup>
0013 #include <QAbstractItemModel>
0014 #include <QAbstractListModel>
0015 #include <QPointer>
0016 #include <QString>
0017 
0018 #include <optional>
0019 #include <variant>
0020 
0021 class FlatpakReference;
0022 
0023 /** For exporting enum to QML */
0024 class FlatpakPermissionsSectionType : public QObject
0025 {
0026     Q_OBJECT
0027 
0028 public:
0029     enum Type {
0030         Basic,
0031         Filesystems,
0032         Advanced,
0033         SubsystemsShared,
0034         Sockets,
0035         Devices,
0036         Features,
0037         SessionBus,
0038         SystemBus,
0039         Environment,
0040     };
0041     Q_ENUM(Type)
0042 };
0043 
0044 /**
0045  * Boolean entry of one of the `shared=`, `sockets=`, `devices=` or `features=`
0046  * lists of the `[Context]` group.
0047  *
0048  * Such entries are either ON or OFF. They are turned OFF if and only if their
0049  * string representation is prefixed with a `!` bang character.
0050  */
0051 class FlatpakSimpleEntry
0052 {
0053     Q_GADGET
0054 public:
0055     // Default constructor is required for meta-type registration.
0056     /** Default constructor. Creates an invalid entry. */
0057     FlatpakSimpleEntry() = default;
0058 
0059     /**
0060      * Construct new entry directly from raw unvalidated data.
0061      */
0062     explicit FlatpakSimpleEntry(const QString &name, bool enabled = true);
0063 
0064     /**
0065      * Parse list entry into data structure. Might fail for various reasons,
0066      * such as ill-formed access mode, so returns an optional value.
0067      */
0068     static std::optional<FlatpakSimpleEntry> parse(QStringView entry);
0069 
0070     /**
0071      * Parse list of entries. Returns list of successfully parsed valid entries,
0072      * as well as a list of the rest unparsable strings.
0073      */
0074     static std::pair<QStringList, QList<FlatpakSimpleEntry>> getCategory(const KConfigGroup &group, const QString &category);
0075 
0076     /**
0077      * Parse list of entries. Returns only list of successfully parsed valid entries.
0078      */
0079     static QList<FlatpakSimpleEntry> getCategorySkippingInvalidEntries(const KConfigGroup &group, const QString &category);
0080 
0081     static std::optional<bool> isEnabled(const QList<FlatpakSimpleEntry> &entries, const QString &name);
0082 
0083     /**
0084      * Format this entry into string. Opposite of parse().
0085      */
0086     QString format() const;
0087 
0088     bool isEnabled() const;
0089     void setEnabled(bool enabled);
0090 
0091     const QString &name() const;
0092 
0093     // TODO C++20: use `= default` implementation.
0094     bool operator==(const FlatpakSimpleEntry &other) const;
0095     bool operator!=(const FlatpakSimpleEntry &other) const;
0096 
0097 private:
0098     QString m_name;
0099     bool m_enabled = true;
0100 };
0101 
0102 /**
0103  * Entry of the `filesystems=` list.
0104  *
0105  * "Deny" access mode represents an entry prefixes with a `!` bang character.
0106  * Other modes correspond to `:ro`, `:rw` and `:create` suffixes.
0107  *
0108  * Path should be without trailing `/` slash character.
0109  * See flatpak-metadata(5) for possible values.
0110  */
0111 class FlatpakFilesystemsEntry
0112 {
0113     Q_GADGET
0114 public:
0115     enum class AccessMode {
0116         /** Make the given directory available read-only. */
0117         ReadOnly,
0118         /** Make the given directory available read/write. This is the default. */
0119         ReadWrite,
0120         /** Make the given directory available read/write, and create it if it does not already exist. */
0121         Create,
0122         /** Don't expose filesystem to app. */
0123         Deny,
0124     };
0125     Q_ENUM(AccessMode)
0126 
0127     enum class PathMode {
0128         Required,
0129         Optional,
0130         NoPath,
0131     };
0132 
0133     enum class FilesystemPrefix {
0134         /** Path is required. */
0135         Absolute,
0136         /** Path is optional. */
0137         Home,
0138         /** Path is invalid. */
0139         Host,
0140         HostOs,
0141         HostEtc,
0142         /** Path is optional. */
0143         XdgDesktop,
0144         XdgDocuments,
0145         XdgDownload,
0146         XdgMusic,
0147         XdgPictures,
0148         XdgPublicShare,
0149         XdgVideos,
0150         XdgTemplates,
0151         /** Path is optional. */
0152         XdgCache,
0153         XdgConfig,
0154         XdgData,
0155         /** Path is required. */
0156         XdgRun,
0157         // Future-proof fallback variant. Prefix is empty, so path mode is
0158         // required, which also ensures that is will be non-empty.
0159         Unknown,
0160     };
0161 
0162     // For Required path mode, fixed string is empty because it is meaningless.
0163     // For Invalid path mode, second, prefix string is empty.
0164     struct TableEntry {
0165         FilesystemPrefix prefix;
0166         PathMode mode;
0167         // Fixed magic filesystem name
0168         QLatin1String fixedString;
0169         // Same as fixed, but with '/' appended.
0170         QLatin1String prefixString;
0171     };
0172 
0173     // Default constructor is required for meta-type registration.
0174     /** Default constructor. Creates an invalid entry. */
0175     FlatpakFilesystemsEntry() = default;
0176 
0177     /**
0178      * Construct new entry directly from raw unvalidated data.
0179      */
0180     explicit FlatpakFilesystemsEntry(FilesystemPrefix prefix, AccessMode mode, const QString &path = QString());
0181 
0182     /**
0183      * Parse list entry into data structure. Might fail for various reasons,
0184      * such as ill-formed access mode, so returns an optional value.
0185      */
0186     static std::optional<FlatpakFilesystemsEntry> parse(QStringView entry);
0187 
0188     /**
0189      * Parse name of a list entry into data structure. Access mode is passed
0190      * separately and not expected to be a part of the name. Might fail for
0191      * various reasons, so returns an optional value.
0192      *
0193      * See also: name()
0194      */
0195     static std::optional<FlatpakFilesystemsEntry> parse(QStringView name, AccessMode accessMode);
0196 
0197     /**
0198      * Formatted prefix and path only, without access mode prefixes or suffixes. Suitable for comparison.
0199      *
0200      * See also: parse(QStringView, AccessMode)
0201      */
0202     QString name() const;
0203 
0204     /**
0205      * Format this entry into string. Opposite of parse(). Omits default `:rw` suffix.
0206      */
0207     QString format() const;
0208 
0209     FilesystemPrefix prefix() const;
0210     QString path() const;
0211     AccessMode mode() const;
0212 
0213     // TODO C++20: use `= default` implementation.
0214     bool operator==(const FlatpakFilesystemsEntry &other) const;
0215     bool operator!=(const FlatpakFilesystemsEntry &other) const;
0216 
0217 private:
0218     FilesystemPrefix m_prefix = FilesystemPrefix::Absolute;
0219     AccessMode m_mode = AccessMode::ReadWrite;
0220     // Depending on prefix type, path can be optional, required or illegal.
0221     QString m_path;
0222 };
0223 
0224 class PolicyChoicesModel : public QAbstractListModel
0225 {
0226     Q_OBJECT
0227 public:
0228     enum Roles {
0229         // Uses standard Qt::DisplayRole for text
0230         ValueRole = Qt::UserRole + 1,
0231     };
0232 
0233     QHash<int, QByteArray> roleNames() const override;
0234     int rowCount(const QModelIndex &parent) const override;
0235     QVariant data(const QModelIndex &index, int role) const override;
0236 
0237 protected:
0238     struct Entry {
0239         int value;
0240         QString display;
0241     };
0242 
0243     explicit PolicyChoicesModel(QVector<Entry> &&policies, QObject *parent = nullptr);
0244 
0245 private:
0246     const QVector<Entry> m_policies;
0247 };
0248 
0249 class FilesystemChoicesModel : public PolicyChoicesModel
0250 {
0251     Q_OBJECT
0252 public:
0253     explicit FilesystemChoicesModel(QObject *parent = nullptr);
0254 };
0255 
0256 class DBusPolicyChoicesModel : public PolicyChoicesModel
0257 {
0258     Q_OBJECT
0259 public:
0260     explicit DBusPolicyChoicesModel(QObject *parent = nullptr);
0261 };
0262 
0263 /**
0264  * @class FlatpakPermission describes a single configurable entry in the list model of permissions.
0265  *
0266  * The content of instances of this class can be interpretted in different ways depending on their
0267  * value type(), permission pType() and section sType().
0268  *
0269  * See flatpak-metadata(5) for more.
0270  */
0271 class FlatpakPermission
0272 {
0273 public:
0274     enum class ValueType {
0275         /**
0276          * This type is for permission entries representing simple boolean
0277          * toggles.
0278          *
0279          * Name of such entry is one of the predefined resource names, e.g.:
0280          * "bluetooth" from "features" category, "kvm" from "devices" category
0281          * etc, "pulseaudio" from "sockets" category etc.
0282          */
0283         Simple,
0284         /**
0285          * Filesystem permissions fall into the "Basic" section type, i.e.
0286          * always shown.
0287          *
0288          * Name of such entry is an actual filesystem path, and the value is
0289          * one of the suffixes: ":ro", ":rw" (default), ":create".
0290          */
0291         Filesystems,
0292         /**
0293          * Name of such permission entry is a D-Bus bus name or prefix thereof,
0294          * for example org.gnome.SessionManager or org.freedesktop.portal.*
0295          *
0296          * The possible values for entry are: "none", "see", "talk" or "own".
0297          */
0298         Bus,
0299         /**
0300          * Name and value of such permission entry are name and value of an
0301          * environment variable.
0302          */
0303         Environment
0304     };
0305 
0306     static ValueType valueTypeFromSectionType(FlatpakPermissionsSectionType::Type section);
0307 
0308     enum class OriginType {
0309         /**
0310          * Built-in type is for all pre-defined system resources (permissions)
0311          * as found in flatpak-metadata(5) man page, and any other additional
0312          * resources declared in app metadata.
0313          *
0314          * They shall not be removed from the list of permissions when
0315          * unchecked.
0316          *
0317          * Predefined resources come with translated description.
0318          */
0319         // TODO: Instead of unchecking there should be more obvious UI. For Bus
0320         // type, there's a "none" policy. For environment we should implement
0321         // "unset-environment" category.
0322         BuiltIn,
0323         /**
0324          * User-defined permissions are resources that user has manually added
0325          * in their overrides. In other words, they are not present in app
0326          * metadata manifest, and can be removed completely when unchecked.
0327          */
0328         // TODO: Same as in BuiltIn, consider "Remove" button instead of unchecking.
0329         UserDefined,
0330         /**
0331          * Empty permissions, just for showing section headers for categories
0332          * that don't have permissions.
0333          */
0334         Dummy
0335     };
0336 
0337     using Variant = std::variant<QString, FlatpakPolicy, FlatpakFilesystemsEntry::AccessMode>;
0338 
0339     // Default constructor is required for meta-type registration.
0340     /** Default constructor. Creates an invalid entry. */
0341     FlatpakPermission() = default;
0342 
0343     /**
0344      * Create a Dummy entry for the Advanced and user-editable sections, just so
0345      * that ListView shows a section header even if there are no permission row
0346      * entries in it.
0347      */
0348     explicit FlatpakPermission(FlatpakPermissionsSectionType::Type section);
0349 
0350     explicit FlatpakPermission(FlatpakPermissionsSectionType::Type section,
0351                                const QString &name,
0352                                const QString &category,
0353                                const QString &description,
0354                                bool isDefaultEnabled,
0355                                const Variant &defaultValue = QString());
0356 
0357     /** Section type for QtQuick/ListView. */
0358     FlatpakPermissionsSectionType::Type section() const;
0359 
0360     /**
0361      * Technical untranslated name of the resource managed by this permission entry.
0362      *
0363      * See ValueType enum for more.
0364      */
0365     const QString &name() const;
0366 
0367     /**
0368      * Technical untranslated category name of the resource managed by this permission entry.
0369      *
0370      * See ValueType enum for more.
0371      */
0372     const QString &category() const;
0373 
0374     /**
0375      * Untranslate section heading back into category identifier. It's a hack
0376      * until the model is refactored to only operate on identifiers, and all
0377      * i18n stuff is moved elsewhere.
0378      */
0379     static QString categoryHeadingToRawCategory(const QString &section);
0380 
0381     /**
0382      * User-facing translated description of the resource managed by this permission entry.
0383      *
0384      * See ValueType enum for more.
0385      */
0386     const QString &description() const;
0387 
0388     /**
0389      * Return type of value of this entry, inferred from its SectionType.
0390      */
0391     ValueType valueType() const;
0392 
0393     /**
0394      * Type of permission this entry represents.
0395      */
0396     OriginType originType() const;
0397 
0398     /**
0399      * Set which type of permissions this entry represents.
0400      */
0401     // TODO: This method should be replaced with constructor argument.
0402     void setOriginType(OriginType type);
0403 
0404     /**
0405      * System default "enabled" status of this permission. It can not be modified.
0406      */
0407     bool isDefaultEnabled() const;
0408 
0409     /** Set user override */
0410     void setOverrideEnabled(bool enabled);
0411 
0412     /**
0413      * This property reports the current effective "enabled" status of this
0414      * permission in KCM.
0415      *
0416      * For ValueType::Simple permissions, if current enabled status matches
0417      * system defaults, it will be removed from user overrides.
0418      *
0419      * For user-defined permissions of other ValueType types, disabling them
0420      * would mark them for removal but only if they are disabled (not present)
0421      * in defaults.
0422      */
0423     bool canBeDisabled() const;
0424     bool isEffectiveEnabled() const;
0425     void setEffectiveEnabled(bool enabled);
0426 
0427     /**
0428      * System default value for this permission. It can only be modified for
0429      * entries which are disabled (not present) by default.
0430      *
0431      * Applicable for any permissions other than ValueType::Simple.
0432      */
0433     const Variant defaultValue() const;
0434 
0435     void setDefaultValue(const Variant &value);
0436 
0437     /** Set user override. Affects isSaveNeeded() state. */
0438     void setOverrideValue(const Variant &value);
0439 
0440     /**
0441      * This property holds the current effective value of this permission in
0442      * KCM.
0443      *
0444      * See ValueType enum for more.
0445      */
0446     const Variant effectiveValue() const;
0447     void setEffectiveValue(const Variant &value);
0448 
0449     /** Integration with KCM. */
0450     bool isSaveNeeded() const;
0451     bool isDefaults() const;
0452 
0453 private:
0454     /** Section type for QtQuick/ListView. */
0455     FlatpakPermissionsSectionType::Type m_section;
0456 
0457     /**
0458      * Untranslatable identifier of permission.
0459      *
0460      * For ValueType::Simple permissions, it's the name of the entry in the list of togglable options in that category.
0461      * For ValueType::Filesystems it's either one of the pre-defined symbolic names or the absolute filepath.
0462      * For ValueType::Bus permissions it's the the name or glob pattern of D-Bus service(s).
0463      * For ValueType::Environment permissions it's the name of environment variable.
0464      */
0465     QString m_name;
0466     /** Untranslatable name of [Category] as seen in metadata and override ini-style files. */
0467     QString m_category;
0468     /** Human-readable description of the permission, or whatever to be displayed in UI. */
0469     QString m_description;
0470 
0471     /* Attempts to classify permissions into various types and groups. */
0472 
0473     OriginType m_originType;
0474 
0475     /* Applicable for all ValueType permissions. */
0476 
0477     /** System defaults */
0478     bool m_defaultEnable;
0479     /** User overrides */
0480     bool m_overrideEnable;
0481     /** Current value in KCM */
0482     bool m_effectiveEnable;
0483 
0484     /* Applicable for any permissions other than ValueType::Simple. */
0485 
0486     /** System defaults */
0487     Variant m_defaultValue;
0488     /** User overrides */
0489     Variant m_overrideValue;
0490     /** Current value in KCM */
0491     Variant m_effectiveValue;
0492 };
0493 
0494 namespace FlatpakOverrides
0495 {
0496 
0497 using KConfigPtr = std::unique_ptr<KConfig>;
0498 
0499 KConfigPtr loadAndMerge(const QStringList &filenames);
0500 void merge(KConfig &target, const QString &filename);
0501 void merge(KConfig &target, const KConfig &source);
0502 };
0503 
0504 class FlatpakPermissionModel : public QAbstractListModel
0505 {
0506     friend class FlatpakPermissionModelTest;
0507     Q_OBJECT
0508     Q_PROPERTY(FlatpakReference *reference READ reference WRITE setReference NOTIFY referenceChanged)
0509     Q_PROPERTY(bool showAdvanced READ showAdvanced WRITE setShowAdvanced NOTIFY showAdvancedChanged)
0510 public:
0511     FlatpakPermissionModel(QObject *parent = nullptr);
0512 
0513     enum Roles {
0514         Section = Qt::UserRole + 1,
0515         Name,
0516         Description,
0517         //
0518         IsNotDummy,
0519         //
0520         CanBeDisabled,
0521         IsDefaultEnabled,
0522         IsEffectiveEnabled,
0523         DefaultValue,
0524         EffectiveValue,
0525         //
0526         ValuesModel,
0527     };
0528     Q_ENUM(Roles)
0529 
0530     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
0531     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
0532     QHash<int, QByteArray> roleNames() const override;
0533 
0534     void loadDefaultValues();
0535     void loadCurrentValues();
0536 
0537     FlatpakReference *reference() const;
0538     void setReference(FlatpakReference *ref);
0539 
0540     bool showAdvanced() const;
0541     void setShowAdvanced(bool);
0542     /** Helper function to count actual rows regardless of current "showAdvanced" setting. */
0543     int rowCount(bool showAdvanced) const;
0544 
0545     void load();
0546     void save();
0547     void defaults();
0548     bool isDefaults() const;
0549     bool isSaveNeeded() const;
0550 
0551     /**
0552      * Static list of user-facing translated policy names for this kind of
0553      * permission.
0554      *
0555      * Applicable for ValueType::Filesystems and ValueType::Bus only.
0556      */
0557     Q_INVOKABLE static PolicyChoicesModel *valuesModelForSectionType(int /*FlatpakPermissionsSectionType::Type*/ rawSection);
0558     Q_INVOKABLE static PolicyChoicesModel *valuesModelForFilesystemsSection();
0559     Q_INVOKABLE static PolicyChoicesModel *valuesModelForBusSections();
0560 
0561     Q_INVOKABLE static QString sectionHeaderForSectionType(int /*FlatpakPermissionsSectionType::Type*/ rawSection);
0562     Q_INVOKABLE static QString sectionAddButtonToolTipTextForSectionType(int /*FlatpakPermissionsSectionType::Type*/ rawSection);
0563 
0564     Q_INVOKABLE bool permissionExists(int /*FlatpakPermissionsSectionType::Type*/ rawSection, const QString &name) const;
0565     bool permissionExists(FlatpakPermissionsSectionType::Type section, const QString &name) const;
0566 
0567     std::optional<int> findPermissionRow(FlatpakPermissionsSectionType::Type section, const QString &name) const;
0568     QModelIndex findPermissionIndex(FlatpakPermissionsSectionType::Type section, const QString &name) const;
0569 
0570     /**
0571      * Validators to check that names comply with section-specific rules.
0572      */
0573     Q_INVOKABLE static bool isFilesystemNameValid(const QString &name);
0574     Q_INVOKABLE static bool isDBusServiceNameValid(const QString &name);
0575     Q_INVOKABLE static bool isEnvironmentVariableNameValid(const QString &name);
0576 
0577 public Q_SLOTS:
0578     void togglePermissionAtRow(int row);
0579     void setPermissionValueAtRow(int row, const QVariant &newValue);
0580     void addUserEnteredPermission(int /*FlatpakPermissionsSectionType::Type*/ rawSection, const QString &name, const QVariant &value);
0581 
0582 Q_SIGNALS:
0583     void referenceChanged();
0584     void showAdvancedChanged();
0585 
0586 private:
0587     int findIndexToInsertRowAndRemoveDummyRowIfNeeded(FlatpakPermissionsSectionType::Type section, bool emitModelSignals);
0588 
0589     void writeToFile() const;
0590     void writeToKConfig(KConfig &config) const;
0591 
0592     QVector<FlatpakPermission> m_permissions;
0593     QHash<QString, QStringList> m_unparsableEntriesByCategory;
0594     QPointer<FlatpakReference> m_reference;
0595     bool m_showAdvanced;
0596 
0597     friend class FlatpakPermissionModelTest;
0598 };