diff --git a/.gitignore b/.gitignore index b9f8c3fdbb3..daf9e513019 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,10 @@ cmd/*/*.[1-9] # auto-generated systemd units data/systemd/*.service + +# auto-generated dbus services +data/dbus/*.service + data/info # test-driver diff --git a/cmd/snap/cmd_userd.go b/cmd/snap/cmd_userd.go new file mode 100644 index 00000000000..17b75c5ab6f --- /dev/null +++ b/cmd/snap/cmd_userd.go @@ -0,0 +1,74 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/jessevdk/go-flags" + + "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/userd" +) + +type cmdUserd struct { + userd userd.Userd +} + +var shortUserdHelp = i18n.G("Start the userd service") +var longUserdHelp = i18n.G("The userd command starts the snap user session service.") + +func init() { + cmd := addCommand("userd", + shortAbortHelp, + longAbortHelp, + func() flags.Commander { + return &cmdUserd{} + }, + nil, + []argDesc{}, + ) + cmd.hidden = true +} + +func (x *cmdUserd) Execute(args []string) error { + if len(args) > 0 { + return ErrExtraArgs + } + + if err := x.userd.Init(); err != nil { + return err + } + x.userd.Start() + + ch := make(chan os.Signal) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1) + select { + case sig := <-ch: + fmt.Fprintf(Stdout, "Exiting on %s.\n", sig) + case <-x.userd.Dying(): + // something called Stop() + } + + return x.userd.Stop() +} diff --git a/cmd/snap/cmd_userd_test.go b/cmd/snap/cmd_userd_test.go new file mode 100644 index 00000000000..ec134a205e5 --- /dev/null +++ b/cmd/snap/cmd_userd_test.go @@ -0,0 +1,70 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2016 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 . + * + */ + +package main_test + +import ( + "os" + "syscall" + "time" + + . "gopkg.in/check.v1" + + snap "github.com/snapcore/snapd/cmd/snap" + "github.com/snapcore/snapd/testutil" +) + +type userdSuite struct { + BaseSnapSuite + testutil.DBusTest +} + +var _ = Suite(&userdSuite{}) + +func (s *userdSuite) TestUserdBadCommandline(c *C) { + _, err := snap.Parser().ParseArgs([]string{"userd", "extra-arg"}) + c.Assert(err, ErrorMatches, "too many arguments for command") +} + +func (s *userdSuite) TestUserd(c *C) { + go func() { + defer func() { + me, err := os.FindProcess(os.Getpid()) + c.Assert(err, IsNil) + me.Signal(syscall.SIGUSR1) + }() + + needle := "io.snapcraft.Launcher" + for i := 0; i < 10; i++ { + for _, objName := range s.SessionBus.Names() { + if objName == needle { + return + } + time.Sleep(1 * time.Second) + } + + } + c.Fatalf("%s does not appeared on the bus", needle) + }() + + rest, err := snap.Parser().ParseArgs([]string{"userd"}) + c.Assert(err, IsNil) + c.Check(rest, DeepEquals, []string{}) + c.Check(s.Stdout(), Equals, "Exiting on user defined signal 1.\n") +} diff --git a/data/Makefile b/data/Makefile new file mode 100644 index 00000000000..caece8c0c3e --- /dev/null +++ b/data/Makefile @@ -0,0 +1,3 @@ +all install clean: + $(MAKE) -C systemd $@ + $(MAKE) -C dbus $@ diff --git a/data/dbus/Makefile b/data/dbus/Makefile new file mode 100644 index 00000000000..66cc14e8648 --- /dev/null +++ b/data/dbus/Makefile @@ -0,0 +1,31 @@ +# +# Copyright (C) 2017 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 . + +BINDIR := /usr/bin +DBUSSERVICESDIR := /usr/share/dbus-1/services + +SERVICES_GENERATED := $(patsubst %.service.in,%.service,$(wildcard *.service.in)) +SERVICES := ${SERVICES_GENERATED} + +%.service: %.service.in + cat $< | sed 's:@bindir@:${BINDIR}:g' | cat > $@ + +all: ${SERVICES} + +install: ${SERVICES} + install -D -m 0644 -t ${DESTDIR}/${DBUSSERVICESDIR} $^ + +clean: + rm -f ${SERVICES_GENERATED} diff --git a/data/dbus/io.snapcraft.Launcher.service.in b/data/dbus/io.snapcraft.Launcher.service.in new file mode 100644 index 00000000000..cd65b0aa0e6 --- /dev/null +++ b/data/dbus/io.snapcraft.Launcher.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=io.snapcraft.Launcher +Exec=@bindir@/snap userd diff --git a/interfaces/builtin/unity7.go b/interfaces/builtin/unity7.go index 11408aec459..fc7cbe7de89 100644 --- a/interfaces/builtin/unity7.go +++ b/interfaces/builtin/unity7.go @@ -86,12 +86,23 @@ const unity7ConnectedPlugAppArmor = ` /usr/bin/xdg-open ixr, /usr/share/applications/{,*} r, /usr/bin/dbus-send ixr, + +# This allow access to the first version of the snapd-xdg-open +# version which was shipped outside of snapd dbus (send) bus=session path=/ interface=com.canonical.SafeLauncher member=OpenURL peer=(label=unconfined), +# ... and this allows access to the new xdg-open service which +# is now part of snapd itself. +dbus (send) + bus=session + path=/io/snapcraft/Launcher + interface=io.snapcraft.Launcher + member=OpenURL + peer=(label=unconfined), # input methods (ibus) # subset of ibus abstraction @@ -303,6 +314,11 @@ dbus (send) bus=session interface=com.canonical.SafeLauncher.OpenURL peer=(label=unconfined), +# new url helper (part of snap userd) +dbus (send) + bus=session + interface=io.snapcraft.Launcher.OpenURL + peer=(label=unconfined), # dbusmenu dbus (send) diff --git a/packaging/fedora/snapd.spec b/packaging/fedora/snapd.spec index 7fbd5416001..d520f3fc913 100644 --- a/packaging/fedora/snapd.spec +++ b/packaging/fedora/snapd.spec @@ -380,7 +380,7 @@ autoreconf --force --install --verbose popd # Build systemd units -pushd ./data/systemd +pushd ./data/ make BINDIR="%{_bindir}" LIBEXECDIR="%{_libexecdir}" \ SYSTEMDSYSTEMUNITDIR="%{_unitdir}" \ SNAP_MOUNT_DIR="%{_sharedstatedir}/snapd/snap" \ @@ -441,7 +441,7 @@ rm -fv %{buildroot}%{_bindir}/ubuntu-core-launcher popd # Install all systemd units -pushd ./data/systemd +pushd ./data/ %make_install SYSTEMDSYSTEMUNITDIR="%{_unitdir}" BINDIR="%{_bindir}" LIBEXECDIR="%{_libexecdir}" # Remove snappy core specific units rm -fv %{buildroot}%{_unitdir}/snapd.system-shutdown.service @@ -560,6 +560,7 @@ popd %ghost %dir %{_sharedstatedir}/snapd/snap/bin %dir %{_localstatedir}/snap %ghost %{_sharedstatedir}/snapd/state.json +%{_datadir}/dbus-1/services/io.snapcraft.Launcher.service %files -n snap-confine %doc cmd/snap-confine/PORTING diff --git a/packaging/opensuse-42.2/snapd.spec b/packaging/opensuse-42.2/snapd.spec index aef3bc923fc..a0d45a09832 100644 --- a/packaging/opensuse-42.2/snapd.spec +++ b/packaging/opensuse-42.2/snapd.spec @@ -206,7 +206,7 @@ install -d %buildroot/snap/bin install -m 644 -D packaging/opensuse-42.2/permissions %buildroot/%{_sysconfdir}/permissions.d/snapd install -m 644 -D packaging/opensuse-42.2/permissions.paranoid %buildroot/%{_sysconfdir}/permissions.d/snapd.paranoid # Install the systemd units -make -C data/systemd install DESTDIR=%{buildroot} SYSTEMDSYSTEMUNITDIR=%{_unitdir} +make -C data install DESTDIR=%{buildroot} SYSTEMDSYSTEMUNITDIR=%{_unitdir} for s in snapd.autoimport.service snapd.system-shutdown.service snap-repair.timer snap-repair.service snapd.core-fixup.service; do rm -f %buildroot/%{_unitdir}/$s done @@ -291,6 +291,7 @@ esac %{_libexecdir}/snapd/complete.sh %{_libexecdir}/snapd/etelpmoc.sh %{_mandir}/man1/snap.1.gz +/usr/share/dbus-1/services/io.snapcraft.Launcher.service %changelog diff --git a/packaging/ubuntu-14.04/control b/packaging/ubuntu-14.04/control index 59360aa68f4..a545367ca5c 100644 --- a/packaging/ubuntu-14.04/control +++ b/packaging/ubuntu-14.04/control @@ -7,6 +7,7 @@ Build-Depends: autoconf, autotools-dev, bash-completion, debhelper (>= 9), + dbus, dh-apparmor, dh-autoreconf, dh-golang (>=1.7), @@ -118,3 +119,11 @@ Section: oldlibs Pre-Depends: dpkg (>= 1.15.7.2) Description: Transitional package for snap-confine This is a transitional dummy package. It can safely be removed. + +Package: snapd-xdg-open +Architecture: any +Depends: snapd (= ${binary:Version}), ${misc:Depends} +Section: oldlibs +Pre-Depends: dpkg (>= 1.15.7.2) +Description: Transitional package for snapd-xdg-open + This is a transitional dummy package. It can safely be removed. diff --git a/packaging/ubuntu-14.04/rules b/packaging/ubuntu-14.04/rules index 58f0f34045c..d435da890f7 100755 --- a/packaging/ubuntu-14.04/rules +++ b/packaging/ubuntu-14.04/rules @@ -166,6 +166,7 @@ override_dh_install: # and now the normal install rules install --mode=0644 debian/snapd.system-shutdown.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR) $(MAKE) -C cmd install DESTDIR=$(CURDIR)/debian/tmp + dh_install override_dh_auto_install: snap.8 diff --git a/packaging/ubuntu-16.04/control b/packaging/ubuntu-16.04/control index 95a2984cb69..493ecfa77a2 100644 --- a/packaging/ubuntu-16.04/control +++ b/packaging/ubuntu-16.04/control @@ -7,6 +7,7 @@ Build-Depends: autoconf, autotools-dev, bash-completion, debhelper (>= 9), + dbus, dh-apparmor, dh-autoreconf, dh-golang (>=1.7), @@ -64,8 +65,8 @@ Depends: adduser, systemd, ${misc:Depends}, ${shlibs:Depends} -Replaces: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22) -Breaks: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22) +Replaces: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22), snapd-xdg-open (<< 0.0.0) +Breaks: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22), snapd-xdg-open (<< 0.0.0) Conflicts: snap (<< 2013-11-29-1ubuntu1) Built-Using: ${Built-Using} ${misc:Built-Using} Description: Tool to interact with Ubuntu Core Snappy. @@ -112,3 +113,11 @@ Section: oldlibs Pre-Depends: dpkg (>= 1.15.7.2) Description: Transitional package for snapd This is a transitional dummy package. It can safely be removed. + +Package: snapd-xdg-open +Architecture: any +Depends: snapd (= ${binary:Version}), ${misc:Depends} +Section: oldlibs +Pre-Depends: dpkg (>= 1.15.7.2) +Description: Transitional package for snapd-xdg-open + This is a transitional dummy package. It can safely be removed. diff --git a/packaging/ubuntu-16.04/rules b/packaging/ubuntu-16.04/rules index 56fc487b8b5..70387c6bb43 100755 --- a/packaging/ubuntu-16.04/rules +++ b/packaging/ubuntu-16.04/rules @@ -119,8 +119,8 @@ override_dh_auto_build: cd cmd && ( ./configure --prefix=/usr --libexecdir=/usr/lib/snapd $(VENDOR_ARGS)) $(MAKE) -C cmd all - # Generate the real systemd units out of the available templates - $(MAKE) -C data/systemd all + # Generate the real systemd/dbus config files + $(MAKE) -C data all override_dh_auto_test: dh_auto_test -- $(GCCGOFLAGS) @@ -210,10 +210,11 @@ override_dh_install: cp -R share/locale debian/snapd/usr/share; \ fi - # install snapd's systemd units, done here instead of - # debian/snapd.install because the ubuntu/14.04 release - # branch adds/changes bits here - $(MAKE) -C data/systemd install DESTDIR=$(CURDIR)/debian/snapd/ SYSTEMDSYSTEMUNITDIR=$(SYSTEMD_UNITS_DESTDIR) + # install snapd's systemd units / upstart jobs, done + # here instead of debian/snapd.install because the + # ubuntu/14.04 release branch adds/changes bits here + $(MAKE) -C data install DESTDIR=$(CURDIR)/debian/snapd/ \ + SYSTEMDSYSTEMUNITDIR=$(SYSTEMD_UNITS_DESTDIR) $(MAKE) -C cmd install DESTDIR=$(CURDIR)/debian/tmp diff --git a/spread.yaml b/spread.yaml index 1af9ae9a87c..aa6b6fd2c43 100644 --- a/spread.yaml +++ b/spread.yaml @@ -441,6 +441,7 @@ suites: fi . $TESTSLIB/pkgdb.sh distro_purge_package snapd + distro_purge_package snapd-xdg-open || true tests/unit/: summary: Suite to run unit tests (non-go and different go runtimes) diff --git a/tests/main/interfaces-dbus/task.yaml b/tests/main/interfaces-dbus/task.yaml index cd35e6c62a9..d8bff82b42c 100644 --- a/tests/main/interfaces-dbus/task.yaml +++ b/tests/main/interfaces-dbus/task.yaml @@ -17,6 +17,8 @@ prepare: | . "$TESTSLIB/dirs.sh" . "$TESTSLIB/pkgdb.sh" + distro_install_package dbus-x11 + echo "Give a snap declaring a dbus slot in installed" snap install --edge test-snapd-dbus-provider @@ -54,6 +56,7 @@ restore: | else systemctl stop dbus-provider fi + distro_purge_package dbus-x11 execute: | CONNECTED_PATTERN="test-snapd-dbus-provider:dbus-test +test-snapd-dbus-consumer" diff --git a/tests/main/snap-userd/task.yaml b/tests/main/snap-userd/task.yaml new file mode 100644 index 00000000000..9b126a96a02 --- /dev/null +++ b/tests/main/snap-userd/task.yaml @@ -0,0 +1,117 @@ +summary: Ensure snap userd allows opening a URL via xdg-open + +systems: + # Not supposed to work on Ubuntu Core systems as we don't have + # a user session environment there + - -ubuntu-core-* + +environment: + DISPLAY: :0 + +restore: | + . "$TESTSLIB/pkgdb.sh" + rm -f dbus.env + if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then + stop test-snap-userd || true + rm -f /etc/init/test-snap-userd.conf + else + systemctl stop --signal=KILL test-snap-userd.scope || true + fi + umount -f /usr/bin/xdg-open || true + umount -f /snap/core/current/usr/bin/xdg-open || true + distro_purge_package dbus-x11 xdg-utils + +execute: | + . "$TESTSLIB/pkgdb.sh" + . "$TESTSLIB/dirs.sh" + + # Install necessary pacakges to get dbus-launch helper + distro_install_package dbus-x11 xdg-utils + + dbus-launch > dbus.env + export $(cat dbus.env | xargs) + + # helper that returns true when io.snapcraft.Launcher.OpenURL + # responds + ping_launcher() { + dbus-send --session \ + --dest=io.snapcraft.Launcher \ + --type=method_call \ + --print-reply \ + / \ + org.freedesktop.DBus.Peer.Ping + } + + if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then + cat << EOF > /etc/init/test-snap-userd.conf + env DISPLAY="$DISPLAY" + env DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" + env DBUS_SESSION_BUS_PID="$DBUS_SESSION_BUS_PID" + kill timeout 5 + exec /usr/bin/snap userd + EOF + initctl reload-configuration + start test-snap-userd + while ! ping_launcher ; do + sleep .1 + done + else + systemd-run \ + --scope \ + --unit=test-snap-userd \ + --no-block \ + --setenv=DISPLAY="$DISPLAY" \ + --setenv=DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \ + --setenv=DBUS_SESSION_BUS_PID="$DBUS_SESSION_BUS_PID" \ + /bin/sh -c '/usr/bin/snap userd &' + while ! ping_launcher ; do + sleep .1 + done + fi + + # Create a small helper which will tell us if snap passes + # the URL correctly to the right handler + cat << 'EOF' > /tmp/xdg-open + #!/bin/sh + echo "$@" > /tmp/xdg-open-output + EOF + chmod +x /tmp/xdg-open + mount --bind /tmp/xdg-open /usr/bin/xdg-open + + # Until necessary changes landed in the core snap we need + # to create a custom one which points to the new service + # name io.snapcraft.Launcher where the current core + # snap still uses com.canonical.Launcher. + cat << 'EOF' > /tmp/xdg-open-core + #!/bin/sh + dbus-send --print-reply --session --dest=io.snapcraft.Launcher /io/snapcraft/Launcher io.snapcraft.Launcher.OpenURL string:"$1" + EOF + chmod +x /tmp/xdg-open-core + mount --bind /tmp/xdg-open-core /snap/core/current/usr/bin/xdg-open + + ensure_xdg_open_output() { + rm -f /tmp/xdg-open-output + export DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS + /snap/core/current/usr/bin/xdg-open $1 + test -e /tmp/xdg-open-output + test "$(cat /tmp/xdg-open-output)" = $1 + } + + # Ensure http, https and mailto work + ensure_xdg_open_output "https://snapcraft.io" + ensure_xdg_open_output "http://snapcraft.io" + ensure_xdg_open_output "mailto:talk@snapcraft.io" + + # Ensure other schemes are not passed through + rm /tmp/xdg-open-output + ! $SNAP_MOUNT_DIR/core/current/usr/bin/xdg-open ftp://snapcraft.io + test ! -e /tmp/xdg-open-output + ! $SNAP_MOUNT_DIR/core/current/usr/bin/xdg-open aabbcc + test ! -e /tmp/xdg-open-output + + if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then + stop test-snap-userd + else + systemctl list-units --type=scope # debug + systemctl stop --signal=KILL test-snap-userd.scope || true + fi diff --git a/tests/upgrade/snapd-xdg-open/task.yaml b/tests/upgrade/snapd-xdg-open/task.yaml new file mode 100644 index 00000000000..0f2d948de03 --- /dev/null +++ b/tests/upgrade/snapd-xdg-open/task.yaml @@ -0,0 +1,38 @@ +summary: Verify snapd-xdg-open package is properly replaced with the snapd one +description: | + snapd-xdg-open was formerly provided by the snapd-xdg-open package + and is now part of the snapd package. This test case verifies that + the snapd-xdg-open package from the archive is properly replaced. +# Test case only applies to Ubuntu as that is the only distribution where +# we had a snapd-xdg-open package ever. +systems: [-ubuntu-core-*, -debian-*, -ubuntu-14.04-*] +restore: | + . "$TESTSLIB/pkgdb.sh" + if [ "$REMOTE_STORE" = staging ]; then + echo "skip upgrade tests while talking to the staging store" + exit 0 + fi + distro_purge_package snapd-xdg-open || true +execute: | + . "$TESTSLIB/pkgdb.sh" + + # Original version of snapd-xdg-open in 16.04 which was not + # part of the snapd source package. + ver=0.0.0~16.04 + if ! distro_install_package snapd-xdg-open=$ver; then + # version of snapd-xdg-open in 17.04,17.10 + ver=0.0.0 + if ! distro_install_package snapd-xdg-open=$ver; then + echo "SKIP: cannot find snapd-xdg-open, skipping test" + exit 0 + fi + fi + + prevsnapdxdgver=$(dpkg-query --showformat='${Version}' --show snapd-xdg-open) + + # allow-downgrades prevents errors when new versions hit the archive, for instance, + # trying to install 2.11ubuntu1 over 2.11+0.16.04 + distro_install_local_package --allow-downgrades $GOHOME/snapd*.deb + + snapdxdgver=$(dpkg-query --showformat='${Version}' --show snapd-xdg-open) + [ "$snapdxdgver" != "$prevsnapdxdgver" ] diff --git a/testutil/dbustest.go b/testutil/dbustest.go new file mode 100644 index 00000000000..4f7b5f580d0 --- /dev/null +++ b/testutil/dbustest.go @@ -0,0 +1,69 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2015 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 . + * + */ + +package testutil + +import ( + "fmt" + "os" + "os/exec" + + "github.com/godbus/dbus" + + . "gopkg.in/check.v1" +) + +// DBusTest provides a separate dbus session bus for running tests +type DBusTest struct { + tmpdir string + dbusDaemon *exec.Cmd + oldSessionBusEnv string + + // the dbus.Conn to the session bus that tests can use + SessionBus *dbus.Conn +} + +func (s *DBusTest) SetUpSuite(c *C) { + if _, err := exec.LookPath("dbus-daemon"); err != nil { + c.Skip(fmt.Sprintf("cannot run test without dbus-daemon: %s", err)) + return + } + if _, err := exec.LookPath("dbus-launch"); err != nil { + c.Skip(fmt.Sprintf("cannot run test without dbus-launch: %s", err)) + return + } + + s.tmpdir = c.MkDir() + s.dbusDaemon = exec.Command("dbus-daemon", "--session", fmt.Sprintf("--address=unix:%s/user_bus_socket", s.tmpdir)) + err := s.dbusDaemon.Start() + c.Assert(err, IsNil) + s.oldSessionBusEnv = os.Getenv("DBUS_SESSION_BUS_ADDRESS") + + s.SessionBus, err = dbus.SessionBus() + c.Assert(err, IsNil) +} + +func (s *DBusTest) TearDownSuite(c *C) { + os.Setenv("DBUS_SESSION_BUS_ADDRESS", s.oldSessionBusEnv) + if s.dbusDaemon != nil && s.dbusDaemon.Process != nil { + err := s.dbusDaemon.Process.Kill() + c.Assert(err, IsNil) + } + +} diff --git a/userd/launcher.go b/userd/launcher.go new file mode 100644 index 00000000000..fc427e87bf9 --- /dev/null +++ b/userd/launcher.go @@ -0,0 +1,88 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package userd + +import ( + "fmt" + "net/url" + "os/exec" + + "github.com/godbus/dbus" + "github.com/snapcore/snapd/strutil" +) + +const launcherIntrospectionXML = ` + + + + + + + + + + + +` + +var ( + allowedURLSchemes = []string{"http", "https", "mailto"} +) + +// Launcher implements the 'io.snapcraft.Launcher' DBus interface. +type Launcher struct{} + +// Name returns the name of the interface this object implements +func (s *Launcher) Name() string { + return "io.snapcraft.Launcher" +} + +// IntrospectionData gives the XML formatted introspection description +// of the DBus service. +func (s *Launcher) IntrospectionData() string { + return launcherIntrospectionXML +} + +func makeAccessDeniedError(err error) *dbus.Error { + return &dbus.Error{ + Name: "org.freedesktop.DBus.Error.AccessDenied", + Body: []interface{}{err.Error()}, + } +} + +// OpenURL implements the 'OpenURL' method of the 'com.canonical.Launcher' +// DBus interface. Before the provided url is passed to xdg-open the scheme is +// validated against a list of allowed schemes. All other schemes are denied. +func (s *Launcher) OpenURL(addr string) *dbus.Error { + u, err := url.Parse(addr) + if err != nil { + return &dbus.ErrMsgInvalidArg + } + + if !strutil.ListContains(allowedURLSchemes, u.Scheme) { + return makeAccessDeniedError(fmt.Errorf("Supplied URL scheme %q is not allowed", u.Scheme)) + } + + if err = exec.Command("xdg-open", addr).Run(); err != nil { + return dbus.MakeFailedError(fmt.Errorf("cannot open supplied URL")) + } + + return nil +} diff --git a/userd/launcher_test.go b/userd/launcher_test.go new file mode 100644 index 00000000000..acc17424312 --- /dev/null +++ b/userd/launcher_test.go @@ -0,0 +1,81 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package userd_test + +import ( + "github.com/godbus/dbus" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/testutil" + "github.com/snapcore/snapd/userd" +) + +type launcherSuite struct { + launcher *userd.Launcher + + mockXdgOpen *testutil.MockCmd +} + +var _ = Suite(&launcherSuite{}) + +func (s *launcherSuite) SetUpTest(c *C) { + s.launcher = &userd.Launcher{} + s.mockXdgOpen = testutil.MockCommand(c, "xdg-open", "") +} + +func (s *launcherSuite) TearDownTest(c *C) { + s.mockXdgOpen.Restore() +} + +func (s *launcherSuite) TestOpenURLWithNotAllowedScheme(c *C) { + for _, t := range []struct { + url string + errMatcher string + }{ + {"tel://049112233445566", "Supplied URL scheme \"tel\" is not allowed"}, + {"aabbccdd0011", "Supplied URL scheme \"\" is not allowed"}, + {"invälid:%url", dbus.ErrMsgInvalidArg.Error()}, + } { + err := s.launcher.OpenURL(t.url) + c.Assert(err, ErrorMatches, t.errMatcher) + c.Assert(s.mockXdgOpen.Calls(), IsNil) + } +} + +func (s *launcherSuite) TestOpenURLWithAllowedSchemeHappy(c *C) { + for _, schema := range []string{"http", "https", "mailto"} { + err := s.launcher.OpenURL(schema + "://snapcraft.io") + c.Assert(err, IsNil) + c.Assert(s.mockXdgOpen.Calls(), DeepEquals, [][]string{ + {"xdg-open", schema + "://snapcraft.io"}, + }) + s.mockXdgOpen.ForgetCalls() + } +} + +func (s *launcherSuite) TestOpenURLWithFailingXdgOpen(c *C) { + cmd := testutil.MockCommand(c, "xdg-open", "false") + defer cmd.Restore() + + err := s.launcher.OpenURL("https://snapcraft.io") + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, "cannot open supplied URL") +} diff --git a/userd/userd.go b/userd/userd.go new file mode 100644 index 00000000000..d17018e6269 --- /dev/null +++ b/userd/userd.go @@ -0,0 +1,109 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package userd + +import ( + "bytes" + "fmt" + + "github.com/godbus/dbus" + "github.com/godbus/dbus/introspect" + "gopkg.in/tomb.v2" +) + +const ( + busName = "io.snapcraft.Launcher" + basePath = "/io/snapcraft/Launcher" +) + +type dbusInterface interface { + Name() string + IntrospectionData() string +} + +type Userd struct { + tomb tomb.Tomb + conn *dbus.Conn + dbusIfaces []dbusInterface +} + +func (ud *Userd) createAndExportInterfaces() { + ud.dbusIfaces = []dbusInterface{&Launcher{}} + + var buffer bytes.Buffer + buffer.WriteString("") + + for _, iface := range ud.dbusIfaces { + ud.conn.Export(iface, basePath, iface.Name()) + buffer.WriteString(iface.IntrospectionData()) + } + + buffer.WriteString(introspect.IntrospectDataString) + buffer.WriteString("") + + ud.conn.Export(introspect.Introspectable(buffer.String()), basePath, "org.freedesktop.DBus.Introspectable") +} + +func (ud *Userd) Init() error { + var err error + + ud.conn, err = dbus.SessionBus() + if err != nil { + return err + } + + reply, err := ud.conn.RequestName(busName, dbus.NameFlagDoNotQueue) + if err != nil { + return err + } + + if reply != dbus.RequestNameReplyPrimaryOwner { + err = fmt.Errorf("cannot obtain bus name '%s'", busName) + return err + } + + ud.createAndExportInterfaces() + return nil +} + +func (ud *Userd) Start() { + ud.tomb.Go(func() error { + // Listen to keep our thread up and running. All DBus bits + // are running in the background + select { + case <-ud.tomb.Dying(): + ud.conn.Close() + } + err := ud.tomb.Err() + if err != nil && err != tomb.ErrStillAlive { + return err + } + return nil + }) +} + +func (ud *Userd) Stop() error { + ud.tomb.Kill(nil) + return ud.tomb.Wait() +} + +func (ud *Userd) Dying() <-chan struct{} { + return ud.tomb.Dying() +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 66a46b98fa2..11ccfbc65e7 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -13,10 +13,16 @@ "revisionTime": "2016-11-14T12:22:54Z" }, { - "checksumSHA1": "LIpi0bDVAl3e0Xza8gohpKkH0+I=", + "checksumSHA1": "h77tT8kVh8x/J5ikkZReONPUjU0=", "path": "github.com/godbus/dbus", - "revision": "bd29ed602e2cf4207ebcabcd530259169e4289ba", - "revisionTime": "2017-07-07T17:46:28Z" + "revision": "97646858c46433e4afb3432ad28c12e968efa298", + "revisionTime": "2017-08-22T15:24:03Z" + }, + { + "checksumSHA1": "NrP46FPoALgKz3FY6puL3syMAAI=", + "path": "github.com/godbus/dbus/introspect", + "revision": "97646858c46433e4afb3432ad28c12e968efa298", + "revisionTime": "2017-08-22T15:24:03Z" }, { "checksumSHA1": "iIUYZyoanCQQTUaWsu8b+iOSPt4=",