Various features on Apertis require a way to discover the applications and/or agents that implement a particular set of functionality. We refer to the “API contract” for this set of functionality as an interface.

Use cases

  • A global search user interface requires a list of services that can act as “Auxiliary Sources” (see §6.2 in the Global Search design document). For example, a Spotify client might register itself as a search provider so that searching for a term in a global search will find artists or songs matching that term.
  • An application that will display a Sharing menu similar to the one in Android requires a list of applications with which files or data can be shared.
  • A navigation app, potentially from an app-store, obtains points of interest from a number of providers, again potentially from an app-store. In a “pull” model, the navigation app would consume the interface “points-of-interest provider” by sending queries to the implementors and getting results back, and the points-of-interest providers would implement that interface. Conversely, in a “push” model, the navigation app could implement the interface “points-of-interest sink”, and the points-of-interest providers could consume that interface by sending points of interest to each sink.
  • If more than one navigation app is installed (for example because an Apertis system includes the OEM’s own simple navigation solution, but it is possible to install premium navigation software from the app-store), a settings user interface to select the preferred navigation app might need to list all the possible navigation apps.
  • Interface discovery could potentially be used with the interface “is the preferred navigation app” to start the navigation app on-demand. If it is, it must be possible to mark one as preferred.
  • A navigation app could have a preferences dialog in which points of interest providers can be selected or deselected. It should not display points of interest from deselected providers, and should not waste system resources on receiving points of interest from those providers. However, if another application also consumes points of interest, disabling a points of interest provider in the navigation app should not prevent it from being used by the other application.
  • The platform could have a preferences dialog in which points of interest providers can be selected or deselected. If a POI provider is deselected here, POI consumers such as the navigation app should behave as though the deselected provider had not been installed at all.

In other systems

GNOME Shell’s search provider API relies on applications registering their support for the search provider “interface” by installing files in /usr/share/gnome-shell/search-providers. This is not ideally suited to a platform like Apertis with a strong division between the “platform” and “app bundle” layers, and does not generalize trivially (each interface would have to define its own location in which to place metadata files).

The freedesktop.org Desktop Entry specification shared by GNOME, KDE and other open source desktop environments uses .desktop metadata files to store metadata about applications. It defines an Interfaces key whose value is a list of syntactically valid D-Bus interface names. Each interface name may represent either a D-Bus interface, or any other “API contract”; there is no requirement that D-Bus is actually used.

Security considerations

Restricting who can advertise a given interface

If arbitrary ISVs can publish app-bundles that advertise arbitrary interfaces, there is a risk that consumers of those interfaces would have an inappropriate level of trust in those app-bundles by assuming that only their own app-bundles can advertise “their” interfaces, for example “leaking” private information to them.

Communication between consumers and implementors

If a particular interface involves direct communication between a consumer and an implementor, then discovery is not sufficient: it is also necessary to ensure that the security model allows the consumer and the implementor to communicate. Conversely, if a particular interface forces all communication between a consumer and an implementor through a trusted intermediary, then it is necessary to ensure that the security model allows both the consumer and the implementor to communicate with the trusted intermediary, and that the trusted intermediary is able to determine that forwarding data between consumer and implementor will not violate the security model.

The desired security model for this interface is that some subset of interfaces are considered to be public interfaces. Trusted platform components may list the implementors of any interface, public or not, and may initiate communication with those implementors. Store applications may list the implementors of public interfaces, and may initiate communication with the implementors of public interfaces, but cannot do the same for non-public interfaces.

Visibility of applications to other applications

Our security model does not consider it to be acceptable for app-bundles to be able to enumerate other app-bundles’ entry points (with the exception that public interfaces may be enumerated). This implies that the implementation of get_implementations() (and the objects that it returns) must be done via IPC (most likely D-Bus) to a trusted service, which can read the .desktop files in XDG_DATA_DIRS/applications and apply appropriate filtering for the caller’s limited view of the system.

Recommendation

For each entry point in a Flatpak application bundle, we recommend that a freedesktop.org .desktop file is provided in the standard location /app/share/applications, containing the standardized Interfaces key as described above.

This information should be made available to API users via a C API resembling GLib’s GAppInfo and GDesktopAppInfo APIs, in particular g_desktop_app_info_get_implementations(). However, we recommend an asynchronous version of that API in order to support the implementation being via D-Bus. Specifically, it should look something like this, with Namespace replaced by some suitable API namespace:

1
2
3
4
5
6
7
8
9
void namespace_app_registry_get_implementations_async (NamespaceAppRegistry *self,
    const gchar *interface_name,
    GCancellable *cancellable,
    GAsyncReadyCallback *callback,
    gpointer user_data);
/* Returns: (element-type GAppInfo) (transfer full): */
GList *namespace_app_registry_get_implementations_finish (NamespaceAppRegistry *self,
    GAsyncResult *result,
    GError **error);

where the result is a list of objects that implement the GAppInfo GInterface. If there is an order of preference, the most-preferred should come first. If there is no particular preference order, the implementation should use a predictable order, such as ordering by most-recently-used, most-recently-installed or alphabetically.

Either this could be implemented in terms of a D-Bus API, or it could have a D-Bus API based on it for access by non-C applications, for example:

/* returns a list of pairs (desktop file ID, text of .desktop file) */
org.apertis.Namespace1.GetImplementations(s interface_name) → a(ss)

For interfaces (API contracts) that already have a system-wide registration mechanism, such as Telepathy connection managers, D-Bus session services and systemd user services, manual integration may be needed to ensure the registration system is Flatpak-compatible.

Selecting a preferred implementation

Some of the possible use-cases for interfaces benefit from the concept of a preferred implementation: for example, a navigation button should launch the preferred (default) navigation application, and if points-of-interest providers have a “push” model, they should not start non-preferred navigation applications in order to push points of interest into those implementations.

For other use-cases, having a preferred implementation is unnecessary: for example, for a Sharing menu, global search, or points-of-interest providers with a “pull” model, the natural design is to query all known implementations in parallel, possibly excluding some that have been disabled.

We recommend addressing the question of a default/preferred implementation on a case-by-case basis (for example by introducing a platform setting for each interface that needs a preferred choice), and only developing a more general solution if experience demonstrates that it is needed in practice.

For example, a preferred navigation application could be selected with an API like

1
2
3
4
5
6
7
void namespace_app_registry_get_default_navigation_implementation_async (NamespaceAppRegistry *self,
    GCancellable *cancellable,
    GAsyncReadyCallback *callback,
    gpointer user_data);
GAppInfo *namespace_app_registry_get_default_navigation_implementation_finish (NamespaceAppRegistry *self,
    GAsyncResult *result,
    GError **error);

if required.

The storage of preferred implementations should be considered to be an implementation detail of the platform component that implements this platform API. For example, it could have a GSetting for each well-known interface, whose value is the string app-ID (D-Bus well-known name) of the preferred entry-point, or an ordered list of preferred entry-points with the most-preferred first.

Enabling/disabling providers

If a provider is disabled system-wide, the platform component that implements interface discovery must behave as though it was not installed at all when answering queries from other components. The storage of enabled/disabled implementations can be considered to be an implementation detail of that component: for example it could store a string-list of disabled app IDs in GSettings. Uninstalling a provider should probably remove it from that list, so that reinstalling the provider automatically enables it.

If a provider is disabled for a particular consumer, we recommend that the consumer stores its own string-list of disabled app IDs, and filters the results of queries on the client-side, encapsulated in a library. This probably only makes sense for interfaces where the consumer will use all non-disabled implementations.

Restricting who can advertise a given interface

We recommend that interfaces advertised by a provider should be restricted by app-store curators, as follows:

  • each ISV that will publish apps on the app-store registers one or more reversed-DNS prefixes with the app-store curator as part of their app-developer account (for example, Collabora Ltd. might register com.collabora and/or uk.co.collabora)
  • the app-store curator verifies the ISV’s ownership of the relevant domain names before accepting uploads from that ISV
  • app-bundles published by the ISV may implement interface names in the namespace of those reversed-DNS prefixes without necessarily triggering extensive checking by the app-store curator (for example, Collabora Ltd. could publish an app-bundle implementing com.collabora.MyInterface)
  • a whitelist of known-“safe” interface names in shared namespaces such as org.apertis, org.freedesktop and org.gnome could also be implemented without necessarily triggering extra checks by the app-store curator (for example, an org.apertis.SharingProvider interface which adds the app to the Sharing menu might be considered to be “safe” for anyone to implement)
  • all other interface names would be “red flags” leading to rejection or additional checking by the app-store curator

This implies that cooperating ISVs cannot invent their own interfaces without app-store curators’ involvement.

It is important to note that if the platform initially has this policy, it cannot be relaxed to “anyone may implement any interface” later. If it was, ISVs writing previously-correct code would potentially become susceptible to cross-app resource access attacks (for example, if the ISV owning example.net had assumed that every implementation of net.example.MyInterface was necessarily trusted code).

Communication between consumers and implementors

App-bundles that implement public interfaces should ensure that their Flatpak permissions allow them to receive D-Bus messages from anywhere on the system.

App-bundles that do not implement public interfaces should ensure that their Flatpak permissions do not expose any services that would allow D-Bus messages from anywhere outside the application, other than any needed platform services.

Visibility of applications to other applications

App-bundles’ Flatpak permissions should not include unrestricted read access to any part of the filesystem that could list other app bundles or their information, such as ~/.var or the Flatpak repository.

The implementation of the interface discovery should be done via D-Bus. The service providing this D-Bus API should be a platform component. It is considered to be a trusted component for the purposes of security between app-bundles: it must reveal public interface implementations to other app-bundles, but must only reveal non-public interface implementations to trusted platform components.