Skip to content

Commit

Permalink
Introduce USB portal
Browse files Browse the repository at this point in the history
The USB portal is the middleman between sandboxed apps, and the
devices connected and available to the host system. This is the
first version of the portal.

Device filtering
================

Sandboxed apps must declare which USB devices they support ahead
of time. This information is read by the XDG Desktop Portal and
used to determine which USB devices will be exposed to requesting
apps. On Flatpak, these enumerable and hidden devices are set by the
"--usb" and "--nousb" arguments against "flatpak build-finish"
and "flatpak run". Neither "--devices=all" nor "--device=usb" do
influence the portal.

Hidding a device always take precedence over making them enumerable,
even when a blanket permission ("--usb=all") is set.

Individual devices are assigned a unique identifier by the portal,
which is used for all further interactions. This unique identifier
is completely random and independent of the device. Permission
checks are in place to not allow apps to try and guess device ids
without having permission to access then.

Permissions
===========

There are 2 dynamic permissions managed by the USB portal in the
permission store:

 1. Blanket USB permission: per-app permission to use any methods
    of the USB portal. Without this permission, apps must not be
    able to do anything - enumerate, monitor, or acquire - with
    the USB portal. [1]

 2. Specific device permission: per-app permission to acquire a
    specific USB device, down to the serial number.

Enumerating devices
===================

There are 2 ways for apps to learn about devices:

 - Apps can call the EnumerateDevices() method, which gives a
   snapshot of the current devices to the app.

 - Apps can create a device monitoring session with CreateSession()
   which sends the list of available devices on creation, and also
   notifies the app about connected and disconnected devices.

Only devices that the app is allowed to see are reported in both
cases.

The udev properties exposed by device enumeration is limited to a
well known subset of properties. [2]

Device acquisition & release
============================

Once an app has determined which devices it wants to access, the
app can call the AcquireDevices() method. This method may prompt
a dialog for the user to allow or deny the app from accessing
specific devices.

If permission is granted, XDG Desktop Portal tries to open the
device file on the behalf of the requesting app, and pass down
the file descriptor to that file. [3]

---

[1] Exceptionally, apps can release previously acquired devices,
even when this permission is disabled. This is so because we
don't yet have kernel-sided USB revoking. With USB revoking in
place, it would be possible to hard-cut app access right when
the app permission changes.

[2] This patch uses a hardcoded list. There is no mechanism for
apps to influence which other udev properties are fetched. This
approach is open to suggestions - it may be necessary to expose
more information more liberally through the portal.

[3] This is clearly not ideal. The ideal approach is to go through
logind's TakeDevice() method. However, that will add significant
complexity to the portal, since this logind method can only be
called by the session controller (i.e. the only executable capable
of calling TakeControl() in the session - usually the compositor).
This can and probably should be implemented in a subsequent round
of improvements to the USB portal.

Co-Authored By: Georges Basile Stavracas Neto <[email protected]>
Co-Authored-By: Ryan Gonzalez <[email protected]>

Signed-off-by: Hubert Figuière <[email protected]>
  • Loading branch information
hfiguiere committed Sep 22, 2024
1 parent 994df18 commit 2661e96
Show file tree
Hide file tree
Showing 19 changed files with 2,348 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ jobs:
librsvg2-common \
libgeoclue-2-dev \
libglib2.0-dev \
libgudev-1.0-dev \
libjson-glib-dev \
libpipewire-0.3-dev \
libsystemd-dev \
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
json-glib-devel \
libcap \
libcap-devel \
libgudev-devel \
libportal-devel \
llvm \
meson \
Expand Down
2 changes: 2 additions & 0 deletions data/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ portal_sources = files(
'org.freedesktop.portal.Session.xml',
'org.freedesktop.portal.Settings.xml',
'org.freedesktop.portal.Trash.xml',
'org.freedesktop.portal.Usb.xml',
'org.freedesktop.portal.Wallpaper.xml',
)

Expand All @@ -61,6 +62,7 @@ portal_impl_sources = files(
'org.freedesktop.impl.portal.Secret.xml',
'org.freedesktop.impl.portal.Session.xml',
'org.freedesktop.impl.portal.Settings.xml',
'org.freedesktop.impl.portal.Usb.xml',
'org.freedesktop.impl.portal.Wallpaper.xml',
)

Expand Down
75 changes: 75 additions & 0 deletions data/org.freedesktop.impl.portal.Usb.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?xml version="1.0"?>
<!--
Copyright (C) 2023 GNOME Foundation Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
Author: Georges Basile Stavracas Neto <[email protected]>
-->

<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<!--
org.freedesktop.impl.portal.Usb:
@short_description: USB portal backend interface
This portal lets applications register global shortcuts so they can
act regardless of the system state upon an input event.
This documentation describes version 1 of this interface.
-->
<interface name="org.freedesktop.impl.portal.Usb">

<!--
AcquireDevices:
@handle: Object path for the :ref:`org.freedesktop.impl.portal.Request` object representing this call
@parent_window: Identifier for the application window, see :ref:`Common Conventions <window-identifiers>`.
@app_id: App id of the application
@devices: Array of device identifiers, and options for each device
@options: Vardict with optional further information
@response: Numeric Request response
@results: Vardict with the results of the call
Opens the given device node.
Each element of the @devices array contains the device ID, and the
following keys:
* ``writable`` (``b``)
Whether the device will be opened in read-write or read-only mode.
Default: False
There are no supported keys in the @options vardict.
The following results get returned via the @results vardict:
* ``devices`` (``a(sa{sv})``)
Which devices to open
-->
<method name="AcquireDevices">
<arg type="o" name="handle" direction="in"/>
<arg type="s" name="parent_window" direction="in"/>
<arg type="s" name="app_id" direction="in"/>
<arg type="a(sa{sv}a{sv})" name="devices" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In4" value="QVariantMap"/>
<arg type="a{sv}" name="options" direction="in"/>
<arg type="u" name="response" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
<arg type="a{sv}" name="results" direction="out"/>
</method>

<property name="version" type="u" access="read"/>
</interface>
</node>
222 changes: 222 additions & 0 deletions data/org.freedesktop.portal.Usb.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
<?xml version="1.0"?>
<!--
Copyright (C) 2020 Endless OS Foundation LLC
2023 GNOME Foundation Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
Author: Georges Basile Stavracas Neto <[email protected]>
Ryan Gonzalez <[email protected]>
-->

<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<!--
org.freedesktop.portal.Usb:
@short_description: Portal for USB device access
This interface lets sandboxed applications monitor and request
access to connected USB devices.
Applications should prefer specialized portals for specific
device types, such as the Camera portal for cameras.
This documentation describes version 1 of this interface.
-->
<interface name="org.freedesktop.portal.Usb">

<!--
CreateSession:
@options: Vardict with optional further information
@handle: Object path for the :ref:`org.freedesktop.portal.Request` object representing this call
Creates an USB monitoring session.
Supported keys in the @options vardict include:
* ``session_handle_token`` (``s``)
A string that will be used as the last element of the session handle. Must be a valid
object path element. See the #org.freedesktop.portal.Session documentation for
more information about the session handle.
-->
<method name="CreateSession">
<arg type="a{sv}" name="options" direction="in"/>
<arg type="o" name="session_handle" direction="out"/>
</method>

<!--
EnumerateDevices:
@options: Vardict with optional further information
Enumerates all connected USB devices that this application has permission
to see.
The following results are returned in the @device vardict:
* ``id`` (``s``)
The portal-specific unique identifier of the device.
* ``parent`` (``s``)
Device ID of the parent device.
* ``readable`` (``b``)
Whether the device can be opened for reading with
org.freedesktop.portal.Usb.AcquireDevices(). If not present, then
it should be assumed to be false.
* ``writable`` (``b``)
Whether the device can be opened for writing with
org.freedesktop.portal.Usb.AcquireDevices(). If not present, then
it should be assumed to be false.
* ``device-file`` (``s``)
A string path to the device node inside the /dev filesystem.
* ``properties`` (``a{sv}``)
A list of udev properties that this device has. These properties
are not parsed in any way by the portal, it is up to apps to parse
them.
-->
<method name="EnumerateDevices">
<arg type="a{sv}" name="options" direction="in"/>
<arg type="a(sa{sv})" name="devices" direction="out"/>
</method>

<!--
AcquireDevices:
@parent_window: Identifier for the application window, see :ref:`Common Conventions <window-identifiers>`.
@devices: Array of device identifiers, device information, and access options
@options: Vardict with optional further information
@handle: Object path for the :ref:`org.freedesktop.portal.Request` object representing this call
Acquires the given device nodes.
Each element of the @devices array contains the device ID, and the
following keys:
* ``writable`` (``b``)
Whether the device will be opened in read-write or read-only mode.
Default: False
Supported keys in the @options vardict include:
* ``handle_token`` (``s``)
A string that will be used as the last element of the @handle. Must be a valid
object path element. See the :ref:`org.freedesktop.portal.Request` documentation
for more information about the @handle.
The #org.freedesktop.portal.Request::Response signal is emitted without
any extra information.
-->
<method name="AcquireDevices">
<arg type="s" name="parent_window" direction="in"/>
<arg type="a(sa{sv})" name="devices" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<arg type="o" name="handle" direction="out"/>
</method>

<!--
FinishAcquireDevices:
@options: Vardict with optional further information
@results: Array of device ids, and the result of the access
@finished: Whether all device results were reported
Retrieves the file descriptors of the devices requested during
org.freedesktop.portal.Usb.AcquireDevices().
This method can only be called once, and only after calling
org.freedesktop.portal.Usb.AcquireDevices().
Each element of the @devices_fds array contains the device ID, and the
following keys:
* ``success`` (``b``)
Whether the device access was successful or not.
* ``fd`` (``h``)
The file descriptor representing the device. Only present if this
was a successful device access.
* ``error`` (``s``)
Error message describing why accessing the device was not
successful. Only present if this was an failed device access.
There are no supported keys in the @options vardict.
-->
<method name="FinishAcquireDevices">
<arg type="a{sv}" name="options" direction="in"/>
<arg type="a(sa{sv})" name="results" direction="out"/>
<arg type="b" name="finished" direction="out"/>
</method>

<!--
ReleaseDevices:
@devices: Array of device identifiers
@options: Vardict with optional further information
Releases previously acquired devices.
Each element of the @devices array contains the device ID of the device.
There are no supported keys in the @options vardict.
-->
<method name="ReleaseDevices">
<arg type="as" name="devices" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
</method>

<!--
DeviceEvents:
@session_handle: Object path for the :ref:`org.freedesktop.portal.Session` object
@events: A list of events. See org.freedesktop.portal.Usb.EnumerateDevices() for a list of all the properties that may be present in the vardict.
The DeviceEvents signal is emitted when one or more USB devices have
been added, changed, or removed. This signal is only emitted for active
sessions created with org.freedesktop.portal.Usb.CreateSession().
Each element of the @events array is composed of the following fields:
* ``action`` (``s``)
Type of event that occurred. One of "add", "change", or "remove".
* ``id`` (``s``)
Device ID that the event occurred on.
* ``device`` (``s``)
Device properties attached to the ID. See
org.freedesktop.portal.Usb.EnumerateDevices() for a list of all
the properties that may be present in the vardict.
-->
<signal name="DeviceEvents">
<arg type="o" name="session_handle" direction="out"/>
<arg type="a(ssa{sv})" name="events" direction="out"/>
</signal>

<property name="version" type="u" access="read"/>
</interface>
</node>
1 change: 1 addition & 0 deletions doc/api-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ and the object path ``/org/freedesktop/portal/desktop`` on the session bus.
doc-org.freedesktop.portal.Session.rst
doc-org.freedesktop.portal.Settings.rst
doc-org.freedesktop.portal.Trash.rst
doc-org.freedesktop.portal.Usb.rst
doc-org.freedesktop.portal.Wallpaper.rst
1 change: 1 addition & 0 deletions doc/impl-dbus-interfaces.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ accessible to sandboxed applications.
doc-org.freedesktop.impl.portal.Secret.rst
doc-org.freedesktop.impl.portal.Session.rst
doc-org.freedesktop.impl.portal.Settings.rst
doc-org.freedesktop.impl.portal.Usb.rst
doc-org.freedesktop.impl.portal.Wallpaper.rst
7 changes: 7 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ libportal_dep = dependency('libportal',
required: get_option('libportal'))
pipewire_dep = dependency('libpipewire-0.3', version: '>= 0.2.90')
libsystemd_dep = dependency('libsystemd', required: get_option('systemd'))
gudev_dep = dependency('gudev-1.0', required: get_option('gudev'))


bwrap = find_program('bwrap', required: get_option('sandboxed-image-validation'))
Expand Down Expand Up @@ -144,6 +145,11 @@ if have_libsystemd
config_h.set('HAVE_LIBSYSTEMD', 1)
endif

have_gudev = gudev_dep.found()
if have_gudev
config_h.set('HAVE_GUDEV', 1)
endif

add_project_arguments(['-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_66'], language: 'c')

build_documentation = false
Expand Down Expand Up @@ -202,6 +208,7 @@ summary({
'Enable libsystemd support': have_libsystemd,
'Enable geoclue support': have_geoclue,
'Enable libportal support': have_libportal,
'Enable gudev support': have_gudev,
'Enable installed tests:': enable_installed_tests,
'Enable python test suite': enable_pytest,
'Build man pages': rst2man.found(),
Expand Down
4 changes: 4 additions & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ option('geoclue',
type: 'feature',
value: 'auto',
description: 'Enable Geoclue support. Needed for location portal')
option('gudev',
type: 'feature',
value: 'auto',
description: 'Enable udev support. Needed for the USB portal.')
option('systemd',
type: 'feature',
value: 'auto',
Expand Down
Loading

0 comments on commit 2661e96

Please sign in to comment.