Commit 575cb06e authored by Suhaas Joshi's avatar Suhaas Joshi Committed by Aleix Pol Gonzalez
Browse files

Show permissions a Flatpak has on application page

Flatpaks run inside an isolated and limiting sandbox. They usually
require additional resources on the system to work. Permissions to
access these resources outside the sandbox are granted to the
application by default, during installation. Discover does not list
which these permissions are, presently.

This commit lists these permissions out on the application's page. Each
permission is shown in a Kirigami.BasicListItem, which are laid out as a
vertical list below the Reviews' section by an instantiator. The model
to this instantiator resides in the new FlatpakPermission source/header
files along with the respective class.

Permissions are initially loaded from the metadata file of the
application, and then passed to the model constructor.
parent 207605b8
......@@ -666,6 +666,7 @@ DiscoverPage {
delegate: Loader {
property QtObject resource: appInfo.application
source: modelData
Layout.fillWidth: true
}
}
}
......
......@@ -8,6 +8,7 @@ set(flatpak-backend_SRCS
FlatpakJobTransaction.cpp
FlatpakTransactionThread.cpp
FlatpakRefreshAppstreamMetadataJob.cpp
FlatpakPermission.cpp
resources.qrc
)
......
/*
* SPDX-FileCopyrightText: 2022 Suhaas Joshi <joshiesuhaas0@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "FlatpakPermission.h"
#include <KLocalizedString>
FlatpakPermission::FlatpakPermission(QString name, QString category, QString value, QString brief, QString description, QString icon, QStringList list)
: m_name(name)
, m_category(category)
, m_value(value)
, m_brief(brief)
, m_description(description)
, m_icon(icon)
, m_list(list)
{
}
QString FlatpakPermission::name() const
{
return m_name;
}
QString FlatpakPermission::category() const
{
if (m_category == "filesystems") {
return i18n("Directories");
} else if (m_category == "System Bus Policy") {
return i18n("System Buses");
} else if (m_category == "Session Bus Policy") {
return i18n("Session Buses");
}
return m_category;
}
QString FlatpakPermission::value() const
{
return m_value;
}
QString FlatpakPermission::icon() const
{
return m_icon;
}
QString FlatpakPermission::brief() const
{
return m_brief;
}
QString FlatpakPermission::description() const
{
return m_description;
}
QStringList FlatpakPermission::list() const
{
return m_list;
}
FlatpakPermissionsModel::FlatpakPermissionsModel(QVector<FlatpakPermission> permissions)
: QAbstractListModel()
, m_permissions(permissions)
{
}
int FlatpakPermissionsModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_permissions.count();
}
QVariant FlatpakPermissionsModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
switch (role) {
case Roles::BriefRole:
return m_permissions.at(index.row()).brief();
case Roles::DescriptionRole:
return m_permissions.at(index.row()).description();
case Roles::ListRole:
return m_permissions.at(index.row()).list().join("\n- ").prepend("- ");
case Roles::NameRole:
return m_permissions.at(index.row()).name();
case Roles::ValueRole:
return m_permissions.at(index.row()).value();
case Roles::EmptyListRole:
return !m_permissions.at(index.row()).list().isEmpty();
case Roles::IconRole:
return m_permissions.at(index.row()).icon();
case Roles::CategoryRole:
return m_permissions.at(index.row()).category();
}
return QVariant();
}
QHash<int, QByteArray> FlatpakPermissionsModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[Roles::BriefRole] = "brief";
roles[Roles::DescriptionRole] = "description";
roles[Roles::ListRole] = "list";
roles[Roles::NameRole] = "name";
roles[Roles::ValueRole] = "value";
roles[Roles::EmptyListRole] = "emptylist";
roles[Roles::IconRole] = "icon";
roles[Roles::CategoryRole] = "category";
return roles;
}
/*
* SPDX-FileCopyrightText: 2022 Suhaas Joshi <joshiesuhaas0@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#ifndef FLATPAKPERMISSION_H
#define FLATPAKPERMISSION_H
#include <QAbstractListModel>
#include <QString>
#include <QVariant>
#include <QVector>
class FlatpakPermission
{
public:
FlatpakPermission(QString name, QString category, QString value, QString brief, QString description, QString icon, QStringList list = QStringList());
QString name() const;
QString value() const;
QString category() const;
QString icon() const;
QString brief() const;
QString description() const;
QStringList list() const;
private:
QString m_name;
QString m_category;
QString m_value; // on/off, talk/own, environment variables etc
QString m_brief;
QString m_description;
QString m_icon;
QStringList m_list; // to store all buses/directories
};
class FlatpakPermissionsModel : public QAbstractListModel
{
public:
FlatpakPermissionsModel(QVector<FlatpakPermission> permissions);
enum Roles {
BriefRole = Qt::UserRole + 1,
DescriptionRole,
ListRole,
NameRole,
ValueRole,
EmptyListRole,
IconRole,
CategoryRole
};
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
virtual QHash<int, QByteArray> roleNames() const override;
private:
QVector<FlatpakPermission> m_permissions;
};
#endif // FLATPAKPERMISSION_H
......@@ -7,6 +7,8 @@
#include "FlatpakResource.h"
#include "FlatpakBackend.h"
#include "FlatpakFetchDataJob.h"
#include "FlatpakPermission.h"
#include "FlatpakSourcesBackend.h"
#include "config-paths.h"
......@@ -39,7 +41,8 @@ static QString iconCachePath(const AppStream::Icon &icon)
return QStringLiteral("%1/icons/%2").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation), icon.url().fileName());
}
const QStringList FlatpakResource::m_objects{QStringLiteral("qrc:/qml/FlatpakAttention.qml")};
const QStringList FlatpakResource::m_objects({QStringLiteral("qrc:/qml/FlatpakAttention.qml")});
const QStringList FlatpakResource::m_bottomObjects({QStringLiteral("qrc:/qml/PermissionsList.qml")});
FlatpakResource::FlatpakResource(const AppStream::Component &component, FlatpakInstallation *installation, FlatpakBackend *parent)
: AbstractResource(parent)
......@@ -322,6 +325,22 @@ QString FlatpakResource::attentionText() const
return {};
}
Q_INVOKABLE QAbstractListModel *FlatpakResource::showPermissions()
{
if (m_permissions.empty()) {
loadPermissions();
}
return new FlatpakPermissionsModel(m_permissions);
}
Q_INVOKABLE int FlatpakResource::permissionCount()
{
if (m_permissions.empty()) {
loadPermissions();
}
return m_permissions.count();
}
QString FlatpakResource::name() const
{
QString name = m_appdata.name();
......@@ -652,3 +671,179 @@ QString FlatpakResource::versionString()
return AppStreamUtils::versionString(version, m_appdata);
}
void FlatpakResource::loadPermissions()
{
QByteArray metaDataBytes = FlatpakRunnables::fetchMetadata(this, NULL);
QString metaData(metaDataBytes);
QString name, category, brief, description, value = "on";
category = "shared";
if (metaData.contains("network")) {
name = i18n("network");
brief = i18n("Network Access");
description = i18n("Can access the internet");
m_permissions.push_back(FlatpakPermission(name, category, value, brief, description, "network-wireless"));
}
category = "socket";
if (metaData.contains("session-bus")) {
name = i18n("session-bus");
brief = i18n("Session Bus Access");
description = i18n("Access is granted to the entire Session Bus");
m_permissions.push_back(FlatpakPermission(name, category, value, brief, description, "system-save-session"));
}
if (metaData.contains("system-bus")) {
name = i18n("system-bus");
brief = i18n("System Bus Access");
description = i18n("Access is granted to the entire System Bus");
m_permissions.push_back(FlatpakPermission(name, category, value, brief, description, "system-save-session"));
}
if (metaData.contains("ssh-auth")) {
name = i18n("ssh-auth");
brief = i18n("Remote Login Access");
description = i18n("Can initiate remote login requests using the SSH protocol");
m_permissions.push_back(FlatpakPermission(name, category, value, brief, description, "x-shape-connection"));
}
if (metaData.contains("pcsc")) {
name = i18n("pspc");
brief = i18n("Smart Card Access");
description = i18n("Can integrate and communicate with smart cards");
m_permissions.push_back(FlatpakPermission(name, category, value, brief, description, "network-card"));
}
category = "devices";
if (metaData.contains("kvm")) {
name = i18n("kvm");
brief = i18n("Kernel-based Virtual Machine Access");
description = i18n("Allows running other operating systems as guests in virtual machines");
m_permissions.push_back(FlatpakPermission(name, category, value, brief, description, "virtualbox"));
}
if (metaData.contains("all")) {
name = i18n("all");
brief = i18n("Device Access");
description = i18n("Can communicate with and control built-in or connected hardware devices");
m_permissions.push_back(FlatpakPermission(name, category, value, brief, description, "preferences-devices-tree"));
}
category = "filesystems";
int fsStartIndex = metaData.indexOf(category);
int fsPermStartIndex = metaData.indexOf('=', fsStartIndex) + 1;
int fsPermEndIndex = metaData.indexOf('\n', fsPermStartIndex);
QString dirs;
for (int i = fsPermStartIndex; i < fsPermEndIndex; ++i) {
dirs.push_back(metaData[i]);
}
QStringList homeList, systemList;
bool home_ro = false, home_rw = false, home_cr = false, homeAccess = false;
bool system_ro = false, system_rw = false, system_cr = false, systemAccess = false;
for (int j = 0; j < dirs.count(';'); ++j) {
QString dir = dirs.section(';', j, j);
if ((dir.contains("home") || dir.contains("~"))) {
if (dir.contains(":ro")) {
homeList << dir.remove(":ro").append(i18n(" (read-only) "));
home_ro = true;
} else if (dir.contains(":create")) {
homeList << dir.remove(":create").append(i18n(" (can create files) "));
home_cr = true;
} else {
homeList << dir.remove(":rw").append(i18n(" (read & write) "));
home_rw = true;
}
homeAccess = true;
} else {
if (dir.contains(":ro")) {
systemList << dir.remove(":ro").append(i18n(" (read-only) "));
system_ro = true;
} else if (dir.contains(":create")) {
systemList << dir.remove(":create").append(i18n(" (can create files) "));
system_cr = true;
} else {
systemList << dir.remove(":rw").append(i18n(" (read & write) "));
system_rw = true;
}
systemAccess = true;
}
}
QString appendText = "\n- " + homeList.join("\n- ");
if (homeAccess) {
brief = i18n("Home Folder Access");
QString accessLevel;
if (home_rw && home_ro && home_cr) {
description = i18n("Can read, write, and create files in the following locations in your home folder without asking permission first:").append(appendText);
} else if (home_rw && !home_cr) {
description = i18n("Can read and write files in the following locations in your home folder without asking permission first:").append(appendText);
} else if (home_ro && !home_cr && !home_rw) {
description = i18n("Can read files in the following locations in your home folder without asking permission first:").append(appendText);
} else {
description = i18n("Can access files in the following locations in your home folder without asking permission first:").append(appendText);
}
m_permissions.push_back(FlatpakPermission(i18n("filesystems"), category, value, brief, description, "inode-directory", homeList));
}
appendText = "\n- " + systemList.join("\n- ");
if (systemAccess) {
brief = i18n("System Folder Access");
QString accessLevel;
if (system_rw && system_ro && system_cr) {
description = i18n("Can read, write, and create system files in the following locations without asking permission first:").append(appendText);
} else if (system_rw && !system_cr) {
description = i18n("Can read and write system files in the following locations without asking permission first:").append(appendText);
} else if (system_ro && !system_cr && !system_rw) {
description = i18n("Can read system files in the following locations without asking permission first:").append(appendText);
} else {
description = i18n("Can access system files in the following locations without asking permission first:").append(appendText);
}
m_permissions.push_back(FlatpakPermission(i18n("filesystems"), category, value, brief, description, "inode-directory", systemList));
}
category = "Session Bus Policy";
int index = metaData.indexOf(category);
if (index != -1) {
index = metaData.indexOf('\n', index);
QStringList busList;
while (true) {
QString policy;
int j;
for (j = index + 1; metaData[j] != '\n'; ++j) {
policy.push_back(metaData[j]);
}
if (policy.isEmpty()) {
break;
}
QStringList l = policy.split('=');
busList << l[0];
index = j;
}
name = i18n("Session Bus Policy");
brief = i18n("Session Bus Access");
description = i18n("Can communicate with other applications and processes in the same desktop session using the following communication protocols:").append("\n- " + busList.join("\n- "));
m_permissions.push_back(FlatpakPermission(name, category, value, brief, description, "system-save-session", busList));
}
category = "System Bus Policy";
index = metaData.indexOf(category);
if (index != -1) {
index = metaData.indexOf('\n', index);
QStringList busList;
while (true) {
QString policy;
int j;
for (j = index + 1; metaData[j] != '\n'; ++j) {
policy.push_back(metaData[j]);
}
if (policy.isEmpty()) {
break;
}
QStringList l = policy.split('=');
busList << l[0];
index = j;
}
name = i18n("System Bus Policy");
brief = i18n("System Bus Access");
description = i18n("Can communicate with all applications and system services using the following communication protocols:").append("\n- " + busList.join("\n- "));
m_permissions.push_back(FlatpakPermission(name, category, value, brief, description, "system-save-session", busList));
}
}
......@@ -10,10 +10,12 @@
#include <resources/AbstractResource.h>
#include "FlatpakPermission.h"
#include "flatpak-helper.h"
#include <AppStreamQt/component.h>
#include <QAbstractItemModel>
#include <QPixmap>
class AddonList;
......@@ -24,7 +26,9 @@ class FlatpakResource : public AbstractResource
{
Q_OBJECT
Q_PROPERTY(QStringList topObjects MEMBER m_objects CONSTANT)
Q_PROPERTY(QStringList objects MEMBER m_bottomObjects CONSTANT)
Q_PROPERTY(QString attentionText READ attentionText CONSTANT)
public:
explicit FlatpakResource(const AppStream::Component &component, FlatpakInstallation *installation, FlatpakBackend *parent);
......@@ -169,6 +173,8 @@ public:
{
m_availableVersion = version;
}
Q_INVOKABLE QAbstractListModel *showPermissions();
Q_INVOKABLE int permissionCount();
void setTemporarySource(const QSharedPointer<FlatpakSource> &temp)
{
......@@ -184,6 +190,7 @@ Q_SIGNALS:
private:
void setCommit(const QString &commit);
void loadPermissions();
const AppStream::Component m_appdata;
FlatpakResource::Id m_id;
......@@ -205,7 +212,9 @@ private:
QString m_availableVersion;
FlatpakResource::ResourceType m_type = DesktopApp;
QSharedPointer<FlatpakSource> m_temp;
QVector<FlatpakPermission> m_permissions;
static const QStringList m_objects;
static const QStringList m_bottomObjects;
};
inline uint qHash(const FlatpakResource::Id &key)
......
/*
* SPDX-FileCopyrightText: 2022 Suhaas Joshi <joshiesuhaas0@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
import QtQuick 2.0
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.12
import QtQml.Models 2.15
import org.kde.kirigami 2.14 as Kirigami
Item {
id: root
implicitWidth: Kirigami.Units.gridUnit * 35
implicitHeight: permissionsColumn.height + Kirigami.Units.gridUnit * 2
visible: list.model.rowCount() > 0
ColumnLayout {
id: permissionsColumn
width: root.width
Kirigami.Heading {
text: i18nc("%1 is the name of the application", "Permissions for %1", resource.name)
font.weight: Font.DemiBold
level: 2
Layout.fillWidth: true
wrapMode: Text.Wrap
}
Instantiator {
id: list
model: resource.showPermissions()
Kirigami.BasicListItem {
parent: permissionsColumn
text: model.brief
subtitle: model.description
icon: model.icon
hoverEnabled: false
width: permissionsColumn.width
}
}
}
}
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource>
<file>qml/FlatpakAttention.qml</file>
</qresource>
<qresource prefix="/">
<file>qml/FlatpakAttention.qml</file>
<file>qml/PermissionsList.qml</file>
</qresource>
</RCC>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment