mirror of
https://gitlab.freedesktop.org/pipewire/media-session.git
synced 2026-05-06 04:48:10 +02:00
modules: access-default: handle arbitrary clients
Extend the access-default module to handle arbitrary clients based on its configuration file. Add a default configuration file which gives all permissions to clients having pipewire.access = "flatpak" and media.category = "Manager" and gives "rx" permissions to clients having pipewire.access = "flatpak" or pipewire.access = "restricted" See pipewire#1960, pipewire#1863, etc. and wireplumber@703c35cfd44265e81595db29eed081c8785cda87.
This commit is contained in:
parent
9d074e3ce7
commit
a02208c04f
6 changed files with 218 additions and 94 deletions
|
|
@ -19,19 +19,8 @@ specify an alternative config directory.
|
|||
|
||||
## Access management
|
||||
|
||||
The \ref page_media_session_module_access_default module handles clients
|
||||
that have \ref PW_KEY_ACCESS set to "flatpak". Other clients are
|
||||
ignored.
|
||||
|
||||
The module sets the permissions of all objects to `RX`. This limits the
|
||||
flatpaks from doing modifications to other objects.
|
||||
|
||||
Because this will also set the core object permission `R`, the client will
|
||||
resume with the new permissions.
|
||||
|
||||
`pipewire-media-session` implements \ref PW_KEY_MEDIA_CATEGORY type
|
||||
"Manager" applications by simply setting the client permissions to ALL. No
|
||||
additional checks are performed yet.
|
||||
The \ref page_media_session_module_access_default module monitors
|
||||
clients and sets the initial permissions based on its configuration file.
|
||||
|
||||
## Modules
|
||||
|
||||
|
|
|
|||
25
media-session.d/access-default.conf
Normal file
25
media-session.d/access-default.conf
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# client access config file for PipeWire version @VERSION@ #
|
||||
#
|
||||
# Copy and edit this file in @MEDIA_SESSION_CONFIG_DIR@/media-session.d/
|
||||
# for system-wide changes or in
|
||||
# ~/.config/pipewire/media-session.d/ for local changes.
|
||||
|
||||
rules = [
|
||||
{
|
||||
matches = [
|
||||
{ pipewire.access = "flatpak", media.category = "Manager" }
|
||||
]
|
||||
actions = {
|
||||
default-permissions = "all"
|
||||
}
|
||||
}
|
||||
{
|
||||
matches = [
|
||||
{ pipewire.access = "flatpak" }
|
||||
{ pipewire.access = "restricted" }
|
||||
]
|
||||
actions = {
|
||||
default-permissions = "rx"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -73,7 +73,7 @@ session.modules = {
|
|||
# the default bundle is always enabled.
|
||||
|
||||
default = [
|
||||
access-default # manages flatpak access
|
||||
access-default # manage default client permissions
|
||||
portal # manage portal permissions
|
||||
v4l2 # video for linux udev detection
|
||||
#libcamera # libcamera udev detection
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ conf_config.set('VERSION', '@0@'.format(media_session_version))
|
|||
conf_config.set('MEDIA_SESSION_CONFIG_DIR', media_session_configdir)
|
||||
|
||||
conf_files = [
|
||||
[ 'access-default.conf', 'access-default.conf' ],
|
||||
[ 'bluez-monitor.conf', 'bluez-monitor.conf' ],
|
||||
[ 'v4l2-monitor.conf', 'v4l2-monitor.conf' ],
|
||||
[ 'media-session.conf', 'media-session.conf' ],
|
||||
|
|
|
|||
|
|
@ -22,40 +22,71 @@
|
|||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <strings.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <spa/utils/defs.h>
|
||||
#include <spa/utils/json.h>
|
||||
#include <spa/utils/string.h>
|
||||
|
||||
#include "pipewire/pipewire.h"
|
||||
#include <pipewire/pipewire.h>
|
||||
|
||||
#include "media-session.h"
|
||||
|
||||
/** \page page_media_session_module_access_default Media Session Module: Access Default
|
||||
*
|
||||
* The Access Flatpak module manages permissions for flatpak clients. It
|
||||
* monitors clients with a \ref PW_KEY_ACCESS value of `"flatpak"` and
|
||||
* restricts those clients to the \ref PW_PERM_R and \ref PW_PERM_X
|
||||
* permissions.
|
||||
* The Access Default module manages permissions for clients. It monitors clients and
|
||||
* applies permissions based on the rules specified in the module's configuration file.
|
||||
* The permissions are only set once and for all objects.
|
||||
*
|
||||
* Clients of \ref PW_KEY_MEDIA_CATEGORY type "Manager" are permitted full
|
||||
* access (\ref PW_PERM_ALL).
|
||||
* ## Configuration
|
||||
*
|
||||
* The value "flatpak" is typically assigned by the \ref page_module_access.
|
||||
* This module loads the `alsa-monitor.conf` configuration file. The main
|
||||
* component in that file is the `rules = []` array that consists of multiple
|
||||
* dictionaries that `matches` a client and specifying `actions` to take.
|
||||
*
|
||||
* ## Module Properties
|
||||
* The following `actions` are supported:
|
||||
* - `default-permissions`: set the permissions of the matched client
|
||||
*
|
||||
* This module requires the following properties on the client object:
|
||||
* - \ref PW_KEY_ACCESS
|
||||
* - \ref PW_KEY_MEDIA_CATEGORY (optional, only matters for "Manager" objects)
|
||||
* The value of `default-permissions` may either be the string "all"
|
||||
* for granting all permissions or a combination of `r` (read), `w` (write),
|
||||
* and `x` (execute) characters.
|
||||
*
|
||||
* Only the first rule that matches will be applied.
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* rules = [
|
||||
* {
|
||||
* matches = [
|
||||
* { pipewire.access = "flatpak", media.category = "Manager" }
|
||||
* ]
|
||||
* actions = {
|
||||
* default-permissions = "all"
|
||||
* }
|
||||
* }
|
||||
* {
|
||||
* matches = [
|
||||
* { pipewire.access = "flatpak" }
|
||||
* { pipewire.access = "restricted" }
|
||||
* ]
|
||||
* actions = {
|
||||
* default-permissions = "rx"
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
|
||||
#define NAME "access-default"
|
||||
#define SESSION_KEY "access-default"
|
||||
#define SESSION_KEY NAME
|
||||
#define SESSION_CONF NAME ".conf"
|
||||
|
||||
PW_LOG_TOPIC_STATIC(mod_topic, "ms.mod." NAME);
|
||||
#define PW_LOG_TOPIC_DEFAULT mod_topic
|
||||
|
|
@ -65,59 +96,105 @@ struct impl {
|
|||
struct spa_hook listener;
|
||||
|
||||
struct spa_list client_list;
|
||||
|
||||
char *rules;
|
||||
size_t rules_length;
|
||||
};
|
||||
|
||||
struct client {
|
||||
struct sm_client *obj;
|
||||
|
||||
uint32_t id;
|
||||
struct impl *impl;
|
||||
|
||||
struct spa_list link; /**< link in impl client_list */
|
||||
|
||||
struct sm_client *sm_client;
|
||||
struct spa_hook listener;
|
||||
unsigned int active:1;
|
||||
|
||||
struct impl *impl;
|
||||
|
||||
unsigned int handled:1;
|
||||
};
|
||||
|
||||
static uint32_t parse_permissions(const char *perms)
|
||||
{
|
||||
if (strcasecmp(perms, "all") == 0)
|
||||
return PW_PERM_ALL;
|
||||
|
||||
uint32_t p = 0;
|
||||
|
||||
for (; *perms; perms++) {
|
||||
switch (tolower(*perms)) {
|
||||
case 'r': p |= PW_PERM_R; break;
|
||||
case 'w': p |= PW_PERM_W; break;
|
||||
case 'x': p |= PW_PERM_X; break;
|
||||
case 'm': p |= PW_PERM_M; break;
|
||||
case '-': /* do nothing */ break;
|
||||
default: return PW_PERM_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static int handle_default_permission(void *data, const char *location, const char *action, const char *value, size_t length)
|
||||
{
|
||||
if (spa_streq(action, "default-permissions")) {
|
||||
uint32_t *perms = data;
|
||||
if (*perms != PW_PERM_INVALID)
|
||||
return 0;
|
||||
|
||||
if (!spa_json_is_string(value, length))
|
||||
return 0;
|
||||
|
||||
char p[16];
|
||||
if (spa_json_parse_stringn(value, length, p, sizeof(p)) <= 0)
|
||||
return 0;
|
||||
|
||||
*perms = parse_permissions(p);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t get_permissions(struct impl *impl, const struct spa_dict *client_props)
|
||||
{
|
||||
uint32_t perm = PW_PERM_INVALID;
|
||||
|
||||
pw_conf_match_rules(impl->rules, impl->rules_length, NAME, client_props,
|
||||
handle_default_permission, &perm);
|
||||
|
||||
return perm;
|
||||
}
|
||||
|
||||
static void object_update(void *data)
|
||||
{
|
||||
struct client *client = data;
|
||||
struct sm_client *sm_client = client->sm_client;
|
||||
struct impl *impl = client->impl;
|
||||
const char *str;
|
||||
|
||||
pw_log_debug("%p: client %p %08x", impl, client, client->obj->obj.changed);
|
||||
pw_log_debug("%p: client %p: changed:0x%08x avail:0x%08x handled:%d",
|
||||
impl, client,
|
||||
sm_client->obj.changed, sm_client->obj.avail,
|
||||
client->handled);
|
||||
|
||||
if (client->obj->obj.avail & SM_CLIENT_CHANGE_MASK_INFO &&
|
||||
!client->active) {
|
||||
struct pw_permission permissions[1];
|
||||
uint32_t perms;
|
||||
if (client->handled || !SPA_FLAG_IS_SET(sm_client->obj.avail, SM_CLIENT_CHANGE_MASK_INFO))
|
||||
return;
|
||||
|
||||
if (client->obj->info == NULL || client->obj->info->props == NULL ||
|
||||
(str = spa_dict_lookup(client->obj->info->props, PW_KEY_ACCESS)) == NULL)
|
||||
return;
|
||||
struct pw_client_info *info = sm_client->info;
|
||||
if (info == NULL || info->props == NULL)
|
||||
return;
|
||||
|
||||
if (spa_streq(str, "flatpak")) {
|
||||
if ((str = spa_dict_lookup(client->obj->info->props, PW_KEY_MEDIA_CATEGORY)) != NULL &&
|
||||
(spa_streq(str, "Manager"))) {
|
||||
/* FIXME, use permission store to check if this app is allowed to
|
||||
* be a manager app */
|
||||
perms = PW_PERM_ALL;
|
||||
} else {
|
||||
/* limited access for everything else */
|
||||
perms = PW_PERM_R | PW_PERM_X;
|
||||
}
|
||||
} else if (spa_streq(str, "restricted")) {
|
||||
perms = PW_PERM_R | PW_PERM_X;
|
||||
} else
|
||||
return;
|
||||
client->handled = true;
|
||||
|
||||
pw_log_info("%p: flatpak client %d granted 0x%08x permissions"
|
||||
, impl, client->id, perms);
|
||||
permissions[0] = PW_PERMISSION_INIT(PW_ID_ANY, perms);
|
||||
pw_client_update_permissions(client->obj->obj.proxy,
|
||||
1, permissions);
|
||||
client->active = true;
|
||||
}
|
||||
uint32_t perms = get_permissions(impl, info->props);
|
||||
if (perms == PW_PERM_INVALID)
|
||||
return;
|
||||
|
||||
pw_log_info("%p: client %p: id:%u granting:" PW_PERMISSION_FORMAT,
|
||||
impl, sm_client, sm_client->obj.id, PW_PERMISSION_ARGS(perms));
|
||||
|
||||
const struct pw_permission permissions[] = {
|
||||
PW_PERMISSION_INIT(PW_ID_ANY, perms),
|
||||
};
|
||||
pw_client_update_permissions(client->sm_client->obj.proxy,
|
||||
SPA_N_ELEMENTS(permissions), permissions);
|
||||
}
|
||||
|
||||
static const struct sm_object_events object_events = {
|
||||
|
|
@ -125,58 +202,46 @@ static const struct sm_object_events object_events = {
|
|||
.update = object_update
|
||||
};
|
||||
|
||||
static int
|
||||
handle_client(struct impl *impl, struct sm_object *object)
|
||||
static void handle_client(struct impl *impl, struct sm_client *sm_client)
|
||||
{
|
||||
struct client *client;
|
||||
|
||||
pw_log_debug("%p: new client '%u'", impl, object->id);
|
||||
pw_log_debug("%p: new client %p: id:%u", impl, sm_client, sm_client->obj.id);
|
||||
|
||||
client = sm_object_add_data(&sm_client->obj, SESSION_KEY, sizeof(*client));
|
||||
|
||||
client = sm_object_add_data(object, SESSION_KEY, sizeof(struct client));
|
||||
client->obj = (struct sm_client*)object;
|
||||
client->id = object->id;
|
||||
client->impl = impl;
|
||||
spa_list_append(&impl->client_list, &client->link);
|
||||
|
||||
client->obj->obj.mask |= SM_CLIENT_CHANGE_MASK_INFO;
|
||||
sm_object_add_listener(&client->obj->obj, &client->listener, &object_events, client);
|
||||
|
||||
return 1;
|
||||
client->sm_client = sm_client;
|
||||
client->sm_client->obj.mask |= SM_CLIENT_CHANGE_MASK_INFO;
|
||||
sm_object_add_listener(&client->sm_client->obj, &client->listener, &object_events, client);
|
||||
}
|
||||
|
||||
static void destroy_client(struct impl *impl, struct client *client)
|
||||
static void destroy_client(struct client *client)
|
||||
{
|
||||
spa_list_remove(&client->link);
|
||||
spa_hook_remove(&client->listener);
|
||||
sm_object_remove_data((struct sm_object*)client->obj, SESSION_KEY);
|
||||
sm_object_remove_data(&client->sm_client->obj, SESSION_KEY);
|
||||
}
|
||||
|
||||
static void session_create(void *data, struct sm_object *object)
|
||||
{
|
||||
struct impl *impl = data;
|
||||
int res;
|
||||
|
||||
pw_log_debug("%p: create global '%d'", impl, object->id);
|
||||
pw_log_debug("%p: create global %u", data, object->id);
|
||||
|
||||
if (spa_streq(object->type, PW_TYPE_INTERFACE_Client))
|
||||
res = handle_client(impl, object);
|
||||
else
|
||||
res = 0;
|
||||
|
||||
if (res < 0)
|
||||
pw_log_warn("%p: can't handle global %d", impl, object->id);
|
||||
handle_client(data, (struct sm_client *) object);
|
||||
}
|
||||
|
||||
static void session_remove(void *data, struct sm_object *object)
|
||||
{
|
||||
struct impl *impl = data;
|
||||
pw_log_debug("%p: remove global '%d'", impl, object->id);
|
||||
pw_log_debug("%p: remove global %u", data, object->id);
|
||||
|
||||
if (spa_streq(object->type, PW_TYPE_INTERFACE_Client)) {
|
||||
struct client *client;
|
||||
|
||||
if ((client = sm_object_get_data(object, SESSION_KEY)) != NULL)
|
||||
destroy_client(impl, client);
|
||||
destroy_client(client);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +251,9 @@ static void session_destroy(void *data)
|
|||
struct client *client;
|
||||
|
||||
spa_list_consume(client, &impl->client_list, link)
|
||||
destroy_client(impl, client);
|
||||
destroy_client(client);
|
||||
|
||||
free(impl->rules);
|
||||
|
||||
spa_hook_remove(&impl->listener);
|
||||
free(impl);
|
||||
|
|
@ -199,22 +266,64 @@ static const struct sm_media_session_events session_events = {
|
|||
.destroy = session_destroy,
|
||||
};
|
||||
|
||||
static int load_conf(struct impl *impl)
|
||||
{
|
||||
struct pw_properties *conf = pw_properties_new(NULL, NULL);
|
||||
if (conf == NULL)
|
||||
return -errno;
|
||||
|
||||
int res = sm_media_session_load_conf(impl->session, SESSION_CONF, conf);
|
||||
if (res < 0)
|
||||
goto out;
|
||||
|
||||
const char *rules = pw_properties_get(conf, "rules");
|
||||
if (rules == NULL) {
|
||||
pw_log_warn("no 'rules' section in " SESSION_CONF);
|
||||
res = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
impl->rules = strdup(rules);
|
||||
if (impl->rules == NULL) {
|
||||
res = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
impl->rules_length = strlen(impl->rules);
|
||||
|
||||
out:
|
||||
pw_properties_free(conf);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int sm_access_default_start(struct sm_media_session *session)
|
||||
{
|
||||
struct impl *impl;
|
||||
int res;
|
||||
|
||||
PW_LOG_TOPIC_INIT(mod_topic);
|
||||
|
||||
impl = calloc(1, sizeof(struct impl));
|
||||
impl = calloc(1, sizeof(*impl));
|
||||
if (impl == NULL)
|
||||
return -errno;
|
||||
|
||||
impl->session = session;
|
||||
|
||||
res = load_conf(impl);
|
||||
if (res < 0)
|
||||
goto error;
|
||||
|
||||
spa_list_init(&impl->client_list);
|
||||
|
||||
sm_media_session_add_listener(impl->session,
|
||||
&impl->listener,
|
||||
&session_events, impl);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
free(impl);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2440,7 +2440,7 @@ static const struct {
|
|||
const char *props;
|
||||
|
||||
} modules[] = {
|
||||
{ "access-default", "manage flatpak access", sm_access_default_start, NULL },
|
||||
{ "access-default", "manage default client permissions", sm_access_default_start, NULL },
|
||||
{ "portal", "manage portal permissions", sm_access_portal_start, NULL },
|
||||
{ "metadata", "export metadata API", sm_metadata_start, NULL },
|
||||
{ "default-nodes", "restore default nodes", sm_default_nodes_start, NULL },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue