Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions interfaces/builtin/xdg_portal_permission_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2026 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package builtin

const xdgPortalPermissionStoreSummary = `allows access to the XDG Desktop Portal PermissionStore service`

const xdgPortalPermissionStoreBaseDeclarationPlugs = `
xdg-portal-permission-store:
allow-installation: false
deny-auto-connection: true
`

const xdgPortalPermissionStoreBaseDeclarationSlots = `
xdg-portal-permission-store:
allow-installation:
slot-snap-type:
- core
deny-auto-connection: true
`

const xdgPortalPermissionStoreConnectedPlugAppArmor = `
# Description: Allow access to xdg-desktop-portal's PermissionStore service.

#include <abstractions/dbus-session-strict>

dbus (receive, send)
bus=session
interface=org.freedesktop.impl.portal.PermissionStore
path=/org/freedesktop/impl/portal/PermissionStore
peer=(label=unconfined),
dbus (receive, send)
bus=session
interface=org.freedesktop.DBus.Properties
path=/org/freedesktop/impl/portal/PermissionStore
peer=(label=unconfined),
dbus (receive, send)
bus=session
interface=org.freedesktop.DBus.Peer
path=/org/freedesktop/impl/portal/PermissionStore
peer=(label=unconfined),
dbus (receive, send)
bus=session
interface=org.freedesktop.DBus.Introspectable
path=/org/freedesktop/impl/portal/PermissionStore
peer=(label=unconfined),
`

func init() {
registerIface(&commonInterface{
name: "xdg-portal-permission-store",
summary: xdgPortalPermissionStoreSummary,
implicitOnCore: true,
implicitOnClassic: true,
baseDeclarationPlugs: xdgPortalPermissionStoreBaseDeclarationPlugs,
baseDeclarationSlots: xdgPortalPermissionStoreBaseDeclarationSlots,
connectedPlugAppArmor: xdgPortalPermissionStoreConnectedPlugAppArmor,
})
}
115 changes: 115 additions & 0 deletions interfaces/builtin/xdg_portal_permission_store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2026 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package builtin_test

import (
. "gopkg.in/check.v1"

"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/interfaces/builtin"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
)

type XdgPortalPermissionStoreInterfaceSuite struct {
iface interfaces.Interface
slot *interfaces.ConnectedSlot
slotInfo *snap.SlotInfo
plug *interfaces.ConnectedPlug
plugInfo *snap.PlugInfo
}

var _ = Suite(&XdgPortalPermissionStoreInterfaceSuite{
iface: builtin.MustInterface("xdg-portal-permission-store"),
})

func (s *XdgPortalPermissionStoreInterfaceSuite) SetUpTest(c *C) {
const coreYaml = `name: core
version: 0
type: os
slots:
xdg-portal-permission-store:
interface: xdg-portal-permission-store
`
s.slot, s.slotInfo = MockConnectedSlot(c, coreYaml, nil, "xdg-portal-permission-store")

const consumerYaml = `name: consumer
version: 0
apps:
app:
plugs: [xdg-portal-permission-store]
`
s.plug, s.plugInfo = MockConnectedPlug(c, consumerYaml, nil, "xdg-portal-permission-store")
}

func (s *XdgPortalPermissionStoreInterfaceSuite) TestName(c *C) {
c.Assert(s.iface.Name(), Equals, "xdg-portal-permission-store")
}

func (s *XdgPortalPermissionStoreInterfaceSuite) TestSanitize(c *C) {
c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil)
}

func (s *XdgPortalPermissionStoreInterfaceSuite) TestAppArmorConnectedPlug(c *C) {
appSet, err := interfaces.NewSnapAppSet(s.plug.Snap(), nil)
c.Assert(err, IsNil)
spec := apparmor.NewSpecification(appSet)
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"})
c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "#include <abstractions/dbus-session-strict>")
c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "path=/org/freedesktop/impl/portal/PermissionStore")
c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "interface=org.freedesktop.impl.portal.PermissionStore")
c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "interface=org.freedesktop.DBus.Properties")
c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "interface=org.freedesktop.DBus.Peer")
c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "interface=org.freedesktop.DBus.Introspectable")
c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(label=unconfined)")
}

func (s *XdgPortalPermissionStoreInterfaceSuite) TestAppArmorConnectedSlot(c *C) {
appSet, err := interfaces.NewSnapAppSet(s.slot.Snap(), nil)
c.Assert(err, IsNil)
spec := apparmor.NewSpecification(appSet)
c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil)
c.Assert(spec.SecurityTags(), HasLen, 0)
}

func (s *XdgPortalPermissionStoreInterfaceSuite) TestAppArmorPermanentSlot(c *C) {
spec := &apparmor.Specification{}
c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil)
c.Assert(spec.SecurityTags(), HasLen, 0)
}

func (s *XdgPortalPermissionStoreInterfaceSuite) TestStaticInfo(c *C) {
si := interfaces.StaticInfoOf(s.iface)
c.Check(si.ImplicitOnCore, Equals, true)
c.Check(si.ImplicitOnClassic, Equals, true)
c.Check(si.Summary, Equals, "allows access to the XDG Desktop Portal PermissionStore service")
c.Check(si.BaseDeclarationPlugs, testutil.Contains, "xdg-portal-permission-store")
c.Check(si.BaseDeclarationPlugs, testutil.Contains, "allow-installation: false")
c.Check(si.BaseDeclarationPlugs, testutil.Contains, "deny-auto-connection: true")
c.Check(si.BaseDeclarationSlots, testutil.Contains, "xdg-portal-permission-store")
c.Check(si.BaseDeclarationSlots, testutil.Contains, "deny-auto-connection: true")
}

func (s *XdgPortalPermissionStoreInterfaceSuite) TestInterfaces(c *C) {
c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
}
2 changes: 2 additions & 0 deletions interfaces/policy/basedeclaration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,7 @@ func (s *baseDeclSuite) TestPlugInstallation(c *C) {
"uinput": true,
"unity8": true,
"ubuntu-pro-control": true,
"xdg-portal-permission-store": true,
"userns": true,
"xilinx-dma": true,
"snap-fde-control": true,
Expand Down Expand Up @@ -1452,6 +1453,7 @@ func (s *baseDeclSuite) TestValidity(c *C) {
"system-files": true,
"tee": true,
"ubuntu-pro-control": true,
"xdg-portal-permission-store": true,
"udisks2": true,
"uinput": true,
"unity8": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/bin/sh
set -eu

command="${1:-all}"
bus_name="${DBUS_BUS_NAME}"
object_path="${DBUS_OBJECT_PATH}"
iface_name="${DBUS_IFACE_NAME}"

cmd_ping() {
# Use --print-reply so dbus-send performs a method call and waits for the
# reply. We discard the reply body because for Ping we only care about the
# success or failure of the round trip.
dbus-send --session --print-reply \
--dest="$bus_name" \
"$object_path" \
org.freedesktop.DBus.Peer.Ping >/dev/null
echo ok
}

cmd_introspect() {
dbus-send --session --print-reply \
--dest="$bus_name" \
"$object_path" \
org.freedesktop.DBus.Introspectable.Introspect | grep -q "$iface_name"
echo ok
}

cmd_get_all() {
dbus-send --session --print-reply \
--dest="$bus_name" \
"$object_path" \
org.freedesktop.DBus.Properties.GetAll \
string:"$iface_name" | grep -q 'uint32 1'
echo ok
}

cmd_test() {
dbus-send --session --print-reply \
--dest="$bus_name" \
"$object_path" \
"$iface_name".Test | grep -q 'string "ok"'
echo ok
}

case "$command" in
ping)
cmd_ping
;;
introspect)
cmd_introspect
;;
get-all)
cmd_get_all
;;
test)
cmd_test
;;
all)
cmd_ping
cmd_introspect
cmd_get_all
cmd_test
;;
*)
echo "unknown command: $command" >&2
exit 1
;;
esac
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: test-snapd-xdg-portal-permission-store
version: 1.0
summary: test client for xdg-portal-permission-store
description: ...
apps:
client:
command: bin/client.sh all
plugs: &permission-store-plugs
- xdg-portal-permission-store
environment: &permission-store-env
DBUS_BUS_NAME: org.freedesktop.impl.portal.PermissionStore
DBUS_OBJECT_PATH: /org/freedesktop/impl/portal/PermissionStore
DBUS_IFACE_NAME: org.freedesktop.impl.portal.PermissionStore
ping:
command: bin/client.sh ping
plugs: *permission-store-plugs
environment: *permission-store-env
introspect:
command: bin/client.sh introspect
plugs: *permission-store-plugs
environment: *permission-store-env
get-all:
command: bin/client.sh get-all
plugs: *permission-store-plugs
environment: *permission-store-env
test:
command: bin/client.sh test
plugs: *permission-store-plugs
environment: *permission-store-env
Loading
Loading