github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/interfaces/builtin/desktop_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package builtin_test
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/interfaces"
    32  	"github.com/snapcore/snapd/interfaces/apparmor"
    33  	"github.com/snapcore/snapd/interfaces/builtin"
    34  	"github.com/snapcore/snapd/interfaces/mount"
    35  	"github.com/snapcore/snapd/release"
    36  	"github.com/snapcore/snapd/snap"
    37  	"github.com/snapcore/snapd/testutil"
    38  )
    39  
    40  type DesktopInterfaceSuite struct {
    41  	iface        interfaces.Interface
    42  	coreSlotInfo *snap.SlotInfo
    43  	coreSlot     *interfaces.ConnectedSlot
    44  	plugInfo     *snap.PlugInfo
    45  	plug         *interfaces.ConnectedPlug
    46  }
    47  
    48  var _ = Suite(&DesktopInterfaceSuite{
    49  	iface: builtin.MustInterface("desktop"),
    50  })
    51  
    52  const desktopConsumerYaml = `name: consumer
    53  version: 0
    54  apps:
    55   app:
    56    plugs: [desktop]
    57  `
    58  
    59  const desktopCoreYaml = `name: core
    60  version: 0
    61  type: os
    62  slots:
    63    desktop:
    64  `
    65  
    66  func (s *DesktopInterfaceSuite) SetUpTest(c *C) {
    67  	s.plug, s.plugInfo = MockConnectedPlug(c, desktopConsumerYaml, nil, "desktop")
    68  	s.coreSlot, s.coreSlotInfo = MockConnectedSlot(c, desktopCoreYaml, nil, "desktop")
    69  }
    70  
    71  func (s *DesktopInterfaceSuite) TearDownTest(c *C) {
    72  	dirs.SetRootDir("/")
    73  }
    74  
    75  func (s *DesktopInterfaceSuite) TestName(c *C) {
    76  	c.Assert(s.iface.Name(), Equals, "desktop")
    77  }
    78  
    79  func (s *DesktopInterfaceSuite) TestSanitizeSlot(c *C) {
    80  	c.Assert(interfaces.BeforePrepareSlot(s.iface, s.coreSlotInfo), IsNil)
    81  }
    82  
    83  func (s *DesktopInterfaceSuite) TestSanitizePlug(c *C) {
    84  	c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil)
    85  }
    86  
    87  func (s *DesktopInterfaceSuite) TestAppArmorSpec(c *C) {
    88  	tmpdir := c.MkDir()
    89  	dirs.SetRootDir(tmpdir)
    90  	c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/usr/share/fonts"), 0777), IsNil)
    91  	c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/usr/local/share/fonts"), 0777), IsNil)
    92  	c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/var/cache/fontconfig"), 0777), IsNil)
    93  	restore := release.MockOnClassic(false)
    94  	defer restore()
    95  
    96  	// connected plug to core slot
    97  	spec := &apparmor.Specification{}
    98  	c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil)
    99  	c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"})
   100  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Description: Can access basic graphical desktop resources")
   101  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "#include <abstractions/fonts>")
   102  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/etc/gtk-3.0/settings.ini r,")
   103  	c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Allow access to xdg-desktop-portal and xdg-document-portal")
   104  
   105  	// On an all-snaps system, the only UpdateNS rule is for the
   106  	// document portal.
   107  	updateNS := spec.UpdateNS()
   108  	profile0 := `  # Mount the document portal
   109    mount options=(bind) /run/user/[0-9]*/doc/by-app/snap.consumer/ -> /run/user/[0-9]*/doc/,
   110    umount /run/user/[0-9]*/doc/,
   111  
   112  `
   113  	c.Assert(strings.Join(updateNS, ""), Equals, profile0)
   114  
   115  	// On a classic system, there are UpdateNS rules for the host
   116  	// system font mounts
   117  	restore = release.MockOnClassic(true)
   118  	defer restore()
   119  	spec = &apparmor.Specification{}
   120  	c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil)
   121  	updateNS = spec.UpdateNS()
   122  	c.Check(updateNS, testutil.Contains, "  # Mount the document portal\n")
   123  	c.Check(updateNS, testutil.Contains, "  # Read-only access to /usr/share/fonts\n")
   124  	c.Check(updateNS, testutil.Contains, "  # Read-only access to /usr/local/share/fonts\n")
   125  	c.Check(updateNS, testutil.Contains, "  # Read-only access to /var/cache/fontconfig\n")
   126  
   127  	// connected plug to core slot
   128  	spec = &apparmor.Specification{}
   129  	c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.coreSlot), IsNil)
   130  	c.Assert(spec.SecurityTags(), HasLen, 0)
   131  }
   132  
   133  func (s *DesktopInterfaceSuite) TestMountSpec(c *C) {
   134  	tmpdir := c.MkDir()
   135  	dirs.SetRootDir(tmpdir)
   136  	c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/usr/share/fonts"), 0777), IsNil)
   137  	c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/usr/local/share/fonts"), 0777), IsNil)
   138  	c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/var/cache/fontconfig"), 0777), IsNil)
   139  
   140  	restore := release.MockOnClassic(false)
   141  	defer restore()
   142  
   143  	// On all-snaps systems, the font related mount entries are missing
   144  	spec := &mount.Specification{}
   145  	c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil)
   146  	c.Check(spec.MountEntries(), HasLen, 0)
   147  
   148  	entries := spec.UserMountEntries()
   149  	c.Check(entries, HasLen, 1)
   150  	c.Check(entries[0].Name, Equals, "$XDG_RUNTIME_DIR/doc/by-app/snap.consumer")
   151  	c.Check(entries[0].Dir, Equals, "$XDG_RUNTIME_DIR/doc")
   152  	c.Check(entries[0].Options, DeepEquals, []string{"bind", "rw", "x-snapd.ignore-missing"})
   153  
   154  	// On classic systems, a number of font related directories
   155  	// are bind mounted from the host system if they exist.
   156  	restore = release.MockOnClassic(true)
   157  	defer restore()
   158  	restore = release.MockReleaseInfo(&release.OS{ID: "ubuntu"})
   159  	defer restore()
   160  	spec = &mount.Specification{}
   161  	c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil)
   162  
   163  	entries = spec.MountEntries()
   164  	c.Assert(entries, HasLen, 3)
   165  
   166  	const hostfs = "/var/lib/snapd/hostfs"
   167  	c.Check(entries[0].Name, Equals, hostfs+dirs.SystemFontsDir)
   168  	c.Check(entries[0].Dir, Equals, "/usr/share/fonts")
   169  	c.Check(entries[0].Options, DeepEquals, []string{"bind", "ro"})
   170  
   171  	c.Check(entries[1].Name, Equals, hostfs+dirs.SystemLocalFontsDir)
   172  	c.Check(entries[1].Dir, Equals, "/usr/local/share/fonts")
   173  	c.Check(entries[1].Options, DeepEquals, []string{"bind", "ro"})
   174  
   175  	c.Check(entries[2].Name, Equals, hostfs+dirs.SystemFontconfigCacheDirs[0])
   176  	c.Check(entries[2].Dir, Equals, "/var/cache/fontconfig")
   177  	c.Check(entries[2].Options, DeepEquals, []string{"bind", "ro"})
   178  
   179  	entries = spec.UserMountEntries()
   180  	c.Assert(entries, HasLen, 1)
   181  	c.Check(entries[0].Dir, Equals, "$XDG_RUNTIME_DIR/doc")
   182  
   183  	for _, distroWithQuirks := range []string{"fedora", "arch"} {
   184  		restore = release.MockReleaseInfo(&release.OS{ID: distroWithQuirks})
   185  		defer restore()
   186  
   187  		tmpdir = c.MkDir()
   188  		dirs.SetRootDir(tmpdir)
   189  		if distroWithQuirks == "fedora" {
   190  			// Fedora is a little special with their fontconfig cache location(s) and how we handle them
   191  			c.Assert(dirs.SystemFontconfigCacheDirs, DeepEquals, []string{filepath.Join(tmpdir, "/var/cache/fontconfig"), filepath.Join(tmpdir, "/usr/lib/fontconfig/cache")})
   192  		} else {
   193  			c.Assert(dirs.SystemFontconfigCacheDirs, DeepEquals, []string{filepath.Join(tmpdir, "/var/cache/fontconfig")})
   194  		}
   195  		c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/usr/share/fonts"), 0777), IsNil)
   196  		c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/usr/local/share/fonts"), 0777), IsNil)
   197  		c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/usr/lib/fontconfig/cache"), 0777), IsNil)
   198  		c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/var/cache/fontconfig"), 0777), IsNil)
   199  		spec = &mount.Specification{}
   200  		c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil)
   201  		entries = spec.MountEntries()
   202  		c.Assert(entries, HasLen, 2)
   203  
   204  		for _, en := range entries {
   205  			if en.Dir == "/var/cache/fontconfig" || en.Dir == "/usr/lib/fontconfig/cache" {
   206  				c.Fatalf("unpexected cache mount entry: %q", en.Dir)
   207  			}
   208  		}
   209  	}
   210  }
   211  
   212  func (s *DesktopInterfaceSuite) TestStaticInfo(c *C) {
   213  	si := interfaces.StaticInfoOf(s.iface)
   214  	c.Assert(si.ImplicitOnCore, Equals, false)
   215  	c.Assert(si.ImplicitOnClassic, Equals, true)
   216  	c.Assert(si.Summary, Equals, `allows access to basic graphical desktop resources`)
   217  	c.Assert(si.BaseDeclarationSlots, testutil.Contains, "desktop")
   218  }
   219  
   220  func (s *DesktopInterfaceSuite) TestInterfaces(c *C) {
   221  	c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
   222  }
   223  
   224  func (s *DesktopInterfaceSuite) TestDisableMountHostFontCache(c *C) {
   225  	const mockSnapYaml = `name: desktop-snap
   226  version: 1.0
   227  plugs:
   228    desktop:
   229      mount-host-font-cache: false
   230  `
   231  	plug, plugInfo := MockConnectedPlug(c, mockSnapYaml, nil, "desktop")
   232  	c.Check(interfaces.BeforePreparePlug(s.iface, plugInfo), IsNil)
   233  
   234  	// The fontconfig cache is not mounted.
   235  	tmpdir := c.MkDir()
   236  	dirs.SetRootDir(tmpdir)
   237  	c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/usr/share/fonts"), 0777), IsNil)
   238  	c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/usr/local/share/fonts"), 0777), IsNil)
   239  	c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/var/cache/fontconfig"), 0777), IsNil)
   240  	restore := release.MockOnClassic(true)
   241  	defer restore()
   242  	// mock a distribution where the fontconfig cache would always be
   243  	// mounted
   244  	restore = release.MockReleaseInfo(&release.OS{ID: "ubuntu"})
   245  	defer restore()
   246  
   247  	spec := &mount.Specification{}
   248  	c.Assert(spec.AddConnectedPlug(s.iface, plug, s.coreSlot), IsNil)
   249  	var mounts []string
   250  	for _, ent := range spec.MountEntries() {
   251  		mounts = append(mounts, ent.Dir)
   252  	}
   253  	c.Check(mounts, Not(testutil.Contains), "/var/cache/fontconfig")
   254  }
   255  
   256  func (s *DesktopInterfaceSuite) TestMountFontCacheTrue(c *C) {
   257  	const mockSnapYaml = `name: desktop-snap
   258  version: 1.0
   259  plugs:
   260    desktop:
   261      mount-font-cache: true
   262  `
   263  	plug, plugInfo := MockConnectedPlug(c, mockSnapYaml, nil, "desktop")
   264  	c.Check(interfaces.BeforePreparePlug(s.iface, plugInfo), IsNil)
   265  
   266  	tmpdir := c.MkDir()
   267  	dirs.SetRootDir(tmpdir)
   268  	c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/var/cache/fontconfig"), 0777), IsNil)
   269  	restore := release.MockOnClassic(true)
   270  	defer restore()
   271  	// mock a distribution where the fontconfig cache would always be
   272  	// mounted
   273  	restore = release.MockReleaseInfo(&release.OS{ID: "ubuntu"})
   274  	defer restore()
   275  
   276  	spec := &mount.Specification{}
   277  	c.Assert(spec.AddConnectedPlug(s.iface, plug, s.coreSlot), IsNil)
   278  	var mounts []string
   279  	for _, ent := range spec.MountEntries() {
   280  		mounts = append(mounts, ent.Dir)
   281  	}
   282  	c.Check(mounts, testutil.Contains, "/var/cache/fontconfig")
   283  }
   284  
   285  func (s *DesktopInterfaceSuite) TestMountHostFontCacheNotBool(c *C) {
   286  	const mockSnapYamlTemplate = `name: desktop-snap
   287  version: 1.0
   288  plugs:
   289    desktop:
   290      mount-host-font-cache: %s
   291  `
   292  	for _, value := range []string{
   293  		`"hello world"`,
   294  		`""`,
   295  		"42",
   296  		"[1,2,3,4]",
   297  		`{"foo":"bar"}`,
   298  	} {
   299  		_, plugInfo := MockConnectedPlug(c, fmt.Sprintf(mockSnapYamlTemplate, value), nil, "desktop")
   300  		c.Check(interfaces.BeforePreparePlug(s.iface, plugInfo), ErrorMatches, "desktop plug requires bool with 'mount-host-font-cache'", Commentf(value))
   301  	}
   302  }