// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2016-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 policy_test

import (
	"fmt"
	"strings"

	. "gopkg.in/check.v1"

	"github.com/snapcore/snapd/asserts"
	"github.com/snapcore/snapd/interfaces"
	"github.com/snapcore/snapd/interfaces/builtin"
	"github.com/snapcore/snapd/interfaces/ifacetest"
	"github.com/snapcore/snapd/interfaces/policy"
	"github.com/snapcore/snapd/release"
	"github.com/snapcore/snapd/snap"
	"github.com/snapcore/snapd/snap/snaptest"
	"github.com/snapcore/snapd/strutil"
	"github.com/snapcore/snapd/testutil"
)

type baseDeclSuite struct {
	baseDecl        *asserts.BaseDeclaration
	restoreSanitize func()
}

var _ = Suite(&baseDeclSuite{})

func (s *baseDeclSuite) SetUpSuite(c *C) {
	s.restoreSanitize = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
	s.baseDecl = asserts.BuiltinBaseDeclaration()
}

func (s *baseDeclSuite) TearDownSuite(c *C) {
	s.restoreSanitize()
}

func (s *baseDeclSuite) connectCand(c *C, iface, slotYaml, plugYaml string) *policy.ConnectCandidate {
	if slotYaml == "" {
		slotYaml = fmt.Sprintf(`name: slot-snap
version: 0
slots:
  %s:
`, iface)
	}
	if plugYaml == "" {
		plugYaml = fmt.Sprintf(`name: plug-snap
version: 0
plugs:
  %s:
`, iface)
	}

	slot, _ := ifacetest.MockConnectedSlot(c, slotYaml, nil, iface)
	plug, _ := ifacetest.MockConnectedPlug(c, plugYaml, nil, iface)

	return &policy.ConnectCandidate{
		Plug:            plug,
		Slot:            slot,
		BaseDeclaration: s.baseDecl,
	}
}

func (s *baseDeclSuite) installSlotCand(c *C, iface string, snapType snap.Type, yaml string) *policy.InstallCandidate {
	if yaml == "" {
		yaml = fmt.Sprintf(`name: install-slot-snap
version: 0
type: %s
slots:
  %s:
`, snapType, iface)
	}
	snap := snaptest.MockInfo(c, yaml, nil)
	return &policy.InstallCandidate{
		Snap:            snap,
		BaseDeclaration: s.baseDecl,
	}
}

func (s *baseDeclSuite) installPlugCand(c *C, iface string, snapType snap.Type, yaml string) *policy.InstallCandidate {
	if yaml == "" {
		yaml = fmt.Sprintf(`name: install-plug-snap
version: 0
type: %s
plugs:
  %s:
`, snapType, iface)
	}
	snap := snaptest.MockInfo(c, yaml, nil)
	return &policy.InstallCandidate{
		Snap:            snap,
		BaseDeclaration: s.baseDecl,
	}
}

const declTempl = `type: snap-declaration
authority-id: canonical
series: 16
snap-name: @name@
snap-id: @snapid@
publisher-id: @publisher@
@plugsSlots@
timestamp: 2016-09-30T12:00:00Z
sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij

AXNpZw==`

func (s *baseDeclSuite) mockSnapDecl(c *C, name, snapID, publisher string, plugsSlots string) *asserts.SnapDeclaration {
	encoded := strings.Replace(declTempl, "@name@", name, 1)
	encoded = strings.Replace(encoded, "@snapid@", snapID, 1)
	encoded = strings.Replace(encoded, "@publisher@", publisher, 1)
	if plugsSlots != "" {
		encoded = strings.Replace(encoded, "@plugsSlots@", strings.TrimSpace(plugsSlots), 1)
	} else {
		encoded = strings.Replace(encoded, "@plugsSlots@\n", "", 1)
	}
	a, err := asserts.Decode([]byte(encoded))
	c.Assert(err, IsNil)
	return a.(*asserts.SnapDeclaration)
}

func (s *baseDeclSuite) TestAutoConnection(c *C) {
	all := builtin.Interfaces()

	// these have more complex or in flux policies and have their
	// own separate tests
	snowflakes := map[string]bool{
		"content":                  true,
		"core-support":             true,
		"cuda-driver-libs":         true,
		"desktop":                  true,
		"egl-driver-libs":          true,
		"gbm-driver-libs":          true,
		"vulkan-driver-libs":       true,
		"home":                     true,
		"lxd-support":              true,
		"microstack-support":       true,
		"multipass-support":        true,
		"nvidia-video-driver-libs": true,
		"opengl-driver-libs":       true,
		"opengles-driver-libs":     true,
		"packagekit-control":       true,
		"pkcs11":                   true,
		"remoteproc":               true,
		"screen-inhibit-control":   true,
		"snapd-control":            true,
		"upower-observe":           true,
		"empty":                    true,
	}

	// these simply auto-connect, anything else doesn't
	autoconnect := map[string]bool{
		"accel":                   true,
		"audio-playback":          true,
		"browser-support":         true,
		"desktop-legacy":          true,
		"gsettings":               true,
		"media-hub":               true,
		"mir":                     true,
		"network":                 true,
		"network-bind":            true,
		"network-status":          true,
		"online-accounts-service": true,
		"opengl":                  true,
		"optical-drive":           true,
		"ros-opt-data":            true,
		"ubuntu-download-manager": true,
		"unity7":                  true,
		"unity8":                  true,
		"wayland":                 true,
		"x11":                     true,
	}

	for _, iface := range all {
		if snowflakes[iface.Name()] {
			continue
		}
		expected := autoconnect[iface.Name()]
		comm := Commentf(iface.Name())

		// check base declaration
		cand := s.connectCand(c, iface.Name(), "", "")
		arity, err := cand.CheckAutoConnect()
		if expected {
			c.Check(err, IsNil, comm)
			c.Check(arity.SlotsPerPlugAny(), Equals, false)
		} else {
			c.Check(err, NotNil, comm)
		}
	}
}

func (s *baseDeclSuite) TestAutoConnectionImplicitSlotOnly(c *C) {
	all := builtin.Interfaces()

	// these auto-connect only with an implicit slot
	autoconnect := map[string]bool{
		"desktop":                true,
		"screen-inhibit-control": true,
		"upower-observe":         true,
	}

	for _, iface := range all {
		if !autoconnect[iface.Name()] {
			continue
		}
		comm := Commentf(iface.Name())

		// check base declaration
		cand := s.connectCand(c, iface.Name(), fmt.Sprintf(`name: snapd
type: snapd
version: 0
slots:
  %s:
`, iface.Name()), "")
		arity, err := cand.CheckAutoConnect()
		c.Check(err, IsNil, comm)
		c.Check(arity.SlotsPerPlugAny(), Equals, false)
	}
}

func (s *baseDeclSuite) TestAutoConnectPlugSlot(c *C) {
	all := builtin.Interfaces()

	// these have more complex or in flux policies and have their
	// own separate tests
	snowflakes := map[string]bool{
		"classic-support": true,
		"content":         true,
		"cups-control":    true,
		"home":            true,
		"lxd-support":     true,
		// netlink-driver needs the family-name attributes to match
		"netlink-driver": true,
	}

	for _, iface := range all {
		if snowflakes[iface.Name()] {
			continue
		}
		c.Check(iface.AutoConnect(nil, nil), Equals, true)
	}
}

func (s *baseDeclSuite) TestInterimAutoConnectionHome(c *C) {
	restore := release.MockOnClassic(true)
	defer restore()
	cand := s.connectCand(c, "home", "", "")
	arity, err := cand.CheckAutoConnect()
	c.Check(err, IsNil)
	c.Check(arity.SlotsPerPlugAny(), Equals, false)

	release.OnClassic = false
	_, err = cand.CheckAutoConnect()
	c.Check(err, ErrorMatches, `auto-connection denied by slot rule of interface \"home\"`)
}

func (s *baseDeclSuite) TestHomeReadAll(c *C) {
	const plugYaml = `name: plug-snap
version: 0
plugs:
  home:
    read: all
`
	restore := release.MockOnClassic(true)
	defer restore()
	cand := s.connectCand(c, "home", "", plugYaml)
	err := cand.Check()
	c.Check(err, NotNil)

	_, err = cand.CheckAutoConnect()
	c.Check(err, NotNil)

	release.OnClassic = false
	err = cand.Check()
	c.Check(err, NotNil)

	_, err = cand.CheckAutoConnect()
	c.Check(err, NotNil)
}

func (s *baseDeclSuite) TestHomeReadDefault(c *C) {
	const plugYaml = `name: plug-snap
version: 0
plugs:
  home: null
`
	restore := release.MockOnClassic(true)
	defer restore()
	cand := s.connectCand(c, "home", "", plugYaml)
	err := cand.Check()
	c.Check(err, IsNil)

	// Same as TestInterimAutoConnectionHome()
	arity, err := cand.CheckAutoConnect()
	c.Check(err, IsNil)
	c.Check(arity.SlotsPerPlugAny(), Equals, false)

	release.OnClassic = false
	err = cand.Check()
	c.Check(err, IsNil)

	// Same as TestInterimAutoConnectionHome()
	_, err = cand.CheckAutoConnect()
	c.Check(err, NotNil)
}

func (s *baseDeclSuite) TestAutoConnectionSnapdControl(c *C) {
	cand := s.connectCand(c, "snapd-control", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"snapd-control\"")

	plugsSlots := `
plugs:
  snapd-control:
    allow-auto-connection: true
`

	lxdDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = lxdDecl
	arity, err := cand.CheckAutoConnect()
	c.Check(err, IsNil)
	c.Check(arity.SlotsPerPlugAny(), Equals, false)
}

func (s *baseDeclSuite) TestAutoConnectionContent(c *C) {
	// random snaps cannot connect with content
	// (Sanitize* will now also block this)
	cand := s.connectCand(c, "content", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)

	slotDecl1 := s.mockSnapDecl(c, "slot-snap", "slot-snap-id", "pub1", "")
	plugDecl1 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub1", "")
	plugDecl2 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub2", "")

	// same publisher, same content
	cand = s.connectCand(c, "stuff", `
name: slot-snap
version: 0
slots:
  stuff:
    interface: content
    content: mk1
`, `
name: plug-snap
version: 0
plugs:
  stuff:
    interface: content
    content: mk1
`)
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl1
	arity, err := cand.CheckAutoConnect()
	c.Check(err, IsNil)
	c.Check(arity.SlotsPerPlugAny(), Equals, false)

	// different publisher, same content
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl2
	_, err = cand.CheckAutoConnect()
	c.Check(err, NotNil)

	// same publisher, different content
	cand = s.connectCand(c, "stuff", `name: slot-snap
version: 0
slots:
  stuff:
    interface: content
    content: mk1
`, `
name: plug-snap
version: 0
plugs:
  stuff:
    interface: content
    content: mk2
`)
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl1
	_, err = cand.CheckAutoConnect()
	c.Check(err, NotNil)
}

func (s *baseDeclSuite) TestAutoConnectionSharedMemory(c *C) {
	// random snaps cannot connect with shared-memory
	// (Sanitize* will now also block this)
	cand := s.connectCand(c, "shared-memory", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)

	slotDecl1 := s.mockSnapDecl(c, "slot-snap", "slot-snap-id", "pub1", "")
	plugDecl1 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub1", "")
	plugDecl2 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub2", "")

	// same publisher, same shared-memory
	cand = s.connectCand(c, "stuff", `
name: slot-snap
version: 0
slots:
  stuff:
    interface: shared-memory
    shared-memory: mk1
`, `
name: plug-snap
version: 0
plugs:
  stuff:
    interface: shared-memory
    private: false
    shared-memory: mk1
`)
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl1
	arity, err := cand.CheckAutoConnect()
	c.Check(err, IsNil)
	c.Check(arity.SlotsPerPlugAny(), Equals, false)

	// different publisher, same shared-memory
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl2
	_, err = cand.CheckAutoConnect()
	c.Check(err, NotNil)

	// same publisher, different shared-memory
	cand = s.connectCand(c, "stuff", `name: slot-snap
version: 0
slots:
  stuff:
    interface: shared-memory
    shared-memory: mk1
`, `
name: plug-snap
version: 0
plugs:
  stuff:
    interface: shared-memory
    private: false
    shared-memory: mk2
`)
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl1
	_, err = cand.CheckAutoConnect()
	c.Check(err, NotNil)
}

func (s *baseDeclSuite) TestAutoConnectionSharedMemoryPrivate(c *C) {
	slotDecl := s.mockSnapDecl(c, "snapd", "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4", "canonical", "")
	appSlotDecl := s.mockSnapDecl(c, "slot-snap", "slot-snap-id", "pub1", "")
	plugDecl := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub1", "")

	// private shm plug, implicit slot
	cand := s.connectCand(c, "shared-memory", `
name: snapd
type: snapd
version: 0
slots:
  shared-memory:
`, `
name: plug-snap
version: 0
plugs:
  shared-memory:
    private: true
`)
	cand.SlotSnapDeclaration = slotDecl
	cand.PlugSnapDeclaration = plugDecl
	arity, err := cand.CheckAutoConnect()
	c.Check(err, IsNil)
	c.Check(arity.SlotsPerPlugAny(), Equals, false)

	// private shm plug, regular app slot
	cand = s.connectCand(c, "shared-memory", `
name: slot-snap
version: 0
slots:
  shared-memory:
`, `
name: plug-snap
version: 0
plugs:
  shared-memory:
    private: true
`)
	cand.SlotSnapDeclaration = appSlotDecl
	cand.PlugSnapDeclaration = plugDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, NotNil)

	// regular shm plug, implicit slot
	cand = s.connectCand(c, "shared-memory", `
name: snapd
type: snapd
version: 0
slots:
  shared-memory:
`, `
name: plug-snap
version: 0
plugs:
  shared-memory:
    private: false
`)
	cand.SlotSnapDeclaration = slotDecl
	cand.PlugSnapDeclaration = plugDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, NotNil)
}

func (s *baseDeclSuite) TestAutoConnectionLxdSupportOverride(c *C) {
	// by default, don't auto-connect
	cand := s.connectCand(c, "lxd-support", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)

	plugsSlots := `
plugs:
  lxd-support:
    allow-auto-connection: true
`

	lxdDecl := s.mockSnapDecl(c, "lxd", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = lxdDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionLxdSupportOverrideRevoke(c *C) {
	cand := s.connectCand(c, "lxd-support", "", "")
	plugsSlots := `
plugs:
  lxd-support:
    allow-auto-connection: false
`

	lxdDecl := s.mockSnapDecl(c, "notlxd", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = lxdDecl
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection not allowed by plug rule of interface \"lxd-support\" for \"notlxd\" snap")
}

func (s *baseDeclSuite) TestAutoConnectionKernelModuleControlOverride(c *C) {
	cand := s.connectCand(c, "kernel-module-control", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"kernel-module-control\"")

	plugsSlots := `
plugs:
  kernel-module-control:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionDockerSupportOverride(c *C) {
	cand := s.connectCand(c, "docker-support", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"docker-support\"")

	plugsSlots := `
plugs:
  docker-support:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionClassicSupportOverride(c *C) {
	cand := s.connectCand(c, "classic-support", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"classic-support\"")

	plugsSlots := `
plugs:
  classic-support:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "classic", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionKubernetesSupportOverride(c *C) {
	cand := s.connectCand(c, "kubernetes-support", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"kubernetes-support\"")

	plugsSlots := `
plugs:
  kubernetes-support:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionMicroStackSupportOverride(c *C) {
	cand := s.connectCand(c, "microstack-support", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"microstack-support\"")

	plugsSlots := `
plugs:
  microstack-support:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}
func (s *baseDeclSuite) TestAutoConnectionGreengrassSupportOverride(c *C) {
	cand := s.connectCand(c, "greengrass-support", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"greengrass-support\"")

	plugsSlots := `
plugs:
  greengrass-support:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionMultipassSupportOverride(c *C) {
	cand := s.connectCand(c, "multipass-support", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"multipass-support\"")

	plugsSlots := `
plugs:
  multipass-support:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "multipass-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionBlockDevicesOverride(c *C) {
	cand := s.connectCand(c, "block-devices", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"block-devices\"")

	plugsSlots := `
plugs:
  block-devices:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionPackagekitControlOverride(c *C) {
	cand := s.connectCand(c, "packagekit-control", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"packagekit-control\"")

	plugsSlots := `
plugs:
  packagekit-control:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionPosixMQOverride(c *C) {
	cand := s.connectCand(c, "posix-mq", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection not allowed by plug rule of interface \"posix-mq\"")

	plugsSlots := `
plugs:
  posix-mq:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionSteamSupportOverride(c *C) {
	cand := s.connectCand(c, "steam-support", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"steam-support\"")

	plugsSlots := `
plugs:
  steam-support:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionOverrideMultiple(c *C) {
	plugsSlots := `
plugs:
  network-bind:
    allow-auto-connection: true
  network-control:
    allow-auto-connection: true
  kernel-module-control:
    allow-auto-connection: true
  system-observe:
    allow-auto-connection: true
  hardware-observe:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots)

	all := builtin.Interfaces()
	// these are a mixture interfaces that the snap plugs
	plugged := map[string]bool{
		"network-bind":          true,
		"network-control":       true,
		"kernel-module-control": true,
		"system-observe":        true,
		"hardware-observe":      true,
	}
	for _, iface := range all {
		if !plugged[iface.Name()] {
			continue
		}

		cand := s.connectCand(c, iface.Name(), "", "")
		cand.PlugSnapDeclaration = snapDecl
		arity, err := cand.CheckAutoConnect()
		c.Check(err, IsNil)
		c.Check(arity.SlotsPerPlugAny(), Equals, false)
	}
}

// describe installation rules for slots succinctly for cross-checking,
// if an interface is not mentioned here a slot of its type can only
// be installed by a core snap (and this was taken care by
// BeforePrepareSlot),
// otherwise the entry for the interface is the list of snap types it
// can be installed by (using the declaration naming);
// ATM a nil entry means even stricter rules that would need be tested
// separately and whose implementation is in flux for now
var (
	slotInstallation = map[string][]string{
		// other
		"adb-support":               {"core"},
		"audio-playback":            {"app", "core"},
		"audio-record":              {"app", "core"},
		"autopilot-introspection":   {"core"},
		"avahi-control":             {"app", "core"},
		"avahi-observe":             {"app", "core"},
		"bluez":                     {"app", "core"},
		"bool-file":                 {"core", "gadget"},
		"browser-support":           {"core"},
		"checkbox-support":          {"core"},
		"content":                   {"app", "gadget", "kernel"},
		"core-support":              {"core"},
		"cups":                      {"app"},
		"cups-control":              {"app", "core"},
		"dbus":                      {"app"},
		"docker-support":            {"core"},
		"desktop-launch":            {"core"},
		"dsp":                       {"core", "gadget"},
		"empty":                     {"app"},
		"fwupd":                     {"app", "core"},
		"gpio":                      {"core", "gadget"},
		"gpio-control":              {"core"},
		"greengrass-support":        {"core"},
		"hidraw":                    {"core", "gadget"},
		"i2c":                       {"core", "gadget"},
		"iio":                       {"core", "gadget"},
		"iscsi-initiator":           {"core"},
		"kernel-module-load":        {"core"},
		"kubernetes-support":        {"core"},
		"location-control":          {"app"},
		"location-observe":          {"app"},
		"lxd-support":               {"core"},
		"maliit":                    {"app"},
		"media-hub":                 {"app", "core"},
		"mir":                       {"app"},
		"microstack-support":        {"core"},
		"modem-manager":             {"app", "core"},
		"mount-control":             {"core"},
		"mpris":                     {"app"},
		"dm-multipath":              {"core"},
		"netlink-driver":            {"core", "gadget"},
		"network-manager":           {"app", "core"},
		"network-manager-observe":   {"app", "core"},
		"network-status":            {"core"},
		"nvme-control":              {"core"},
		"ofono":                     {"app", "core"},
		"online-accounts-service":   {"app"},
		"power-control":             {"core"},
		"ppp":                       {"core"},
		"polkit-agent":              {"core"},
		"pipewire":                  {"app", "core"},
		"pulseaudio":                {"app", "core"},
		"pwm":                       {"core", "gadget"},
		"qualcomm-ipc-router":       {"core", "app"},
		"raw-volume":                {"core", "gadget"},
		"scsi-generic":              {"core"},
		"sd-control":                {"core"},
		"serial-port":               {"core", "gadget"},
		"spi":                       {"core", "gadget"},
		"screen-inhibit-control":    {"core", "app"},
		"steam-support":             {"core"},
		"storage-framework-service": {"app"},
		"thumbnailer-service":       {"app"},
		"ubuntu-download-manager":   {"app"},
		"udisks2":                   {"app", "core"},
		"uhid":                      {"core"},
		"uio":                       {"core", "gadget"},
		"unity8":                    {"app"},
		"unity8-calendar":           {"app"},
		"unity8-contacts":           {"app"},
		"upower-observe":            {"app", "core"},
		"usb-gadget":                {"core"},
		"userns":                    {"core"},
		"wayland":                   {"app", "core"},
		"x11":                       {"app", "core"},
		// snowflakes
		"classic-support":          nil,
		"cuda-driver-libs":         nil,
		"custom-device":            nil,
		"docker":                   nil,
		"egl-driver-libs":          nil,
		"gbm-driver-libs":          nil,
		"gpio-chardev":             nil,
		"lxd":                      nil,
		"microceph":                nil,
		"microceph-support":        nil,
		"microovn":                 nil,
		"nvidia-video-driver-libs": nil,
		"opengl-driver-libs":       nil,
		"opengles-driver-libs":     nil,
		"pkcs11":                   nil,
		"podman":                   nil,
		"posix-mq":                 nil,
		"shared-memory":            nil,
		"vulkan-driver-libs":       nil,
	}

	restrictedPlugInstallation = map[string][]string{
		"core-support": {"core"},
	}

	snapTypeMap = map[string]snap.Type{
		"core":   snap.TypeOS,
		"app":    snap.TypeApp,
		"kernel": snap.TypeKernel,
		"gadget": snap.TypeGadget,
	}
)

func (s *baseDeclSuite) TestSlotInstallation(c *C) {
	all := builtin.Interfaces()

	for _, iface := range all {
		types, ok := slotInstallation[iface.Name()]
		if !ok { // common ones, only core can install them,
			types = []string{"core"}
		}

		// only restricted slots can use the AppArmor
		// unconfined profile mode so check that this
		// slot is not using it
		c.Assert(interfaces.StaticInfoOf(iface).AppArmorUnconfinedSlots, Equals, false)
		if types == nil {
			// snowflake needs to be tested specially
			continue
		}
		for name, snapType := range snapTypeMap {
			ok := strutil.ListContains(types, name)
			ic := s.installSlotCand(c, iface.Name(), snapType, ``)
			err := ic.Check()
			comm := Commentf("%s by %s snap", iface.Name(), name)
			if ok {
				c.Check(err, IsNil, comm)
			} else {
				c.Check(err, NotNil, comm)
			}
		}
	}

	// test desktop specifically
	ic := s.installSlotCand(c, "desktop", snap.TypeApp, ``)
	err := ic.Check()
	c.Check(err, Not(IsNil))
	c.Check(err, ErrorMatches, "installation denied by \"desktop\" slot rule of interface \"desktop\"")
	// ... but the minimal check (used by --dangerous) allows installation
	icMin := &policy.InstallCandidateMinimalCheck{
		Snap:            ic.Snap,
		BaseDeclaration: s.baseDecl,
	}
	err = icMin.Check()
	c.Check(err, IsNil)

	// test docker specially - allow-installation for app and core
	ic = s.installSlotCand(c, "docker", snap.TypeApp, ``)
	err = ic.Check()
	c.Assert(err, ErrorMatches, "installation denied by \"docker\" slot rule of interface \"docker\"")

	// The core snap may provide a docker slot (implicit on classic)
	ic = s.installSlotCand(c, "docker", snap.TypeOS, `name: core
version: 0
type: os
slots:
  docker:
`)
	ic.SnapDeclaration = s.mockSnapDecl(c, "core", "99T7MUlRhtI3U0QFgl5mXXESAiSwt776", "canonical", "")
	c.Assert(ic.Check(), IsNil)

	// or the snapd snap may provide a docker slot (implicit on classic)
	ic = s.installSlotCand(c, "docker", snap.TypeOS, `name: snapd
version: 0
type: snapd
slots:
  docker:
`)
	ic.SnapDeclaration = s.mockSnapDecl(c, "snapd", "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4", "canonical", "")
	c.Assert(ic.Check(), IsNil)

	// test lxd specially
	ic = s.installSlotCand(c, "lxd", snap.TypeApp, ``)
	err = ic.Check()
	c.Assert(err, Not(IsNil))
	c.Assert(err, ErrorMatches, "installation not allowed by \"lxd\" slot rule of interface \"lxd\"")

	// test podman specially
	ic = s.installSlotCand(c, "podman", snap.TypeApp, ``)
	err = ic.Check()
	c.Assert(err, Not(IsNil))
	c.Assert(err, ErrorMatches, "installation not allowed by \"podman\" slot rule of interface \"podman\"")

	// test microceph specially
	ic = s.installSlotCand(c, "microceph", snap.TypeApp, ``)
	err = ic.Check()
	c.Assert(err, Not(IsNil))
	c.Assert(err, ErrorMatches, "installation not allowed by \"microceph\" slot rule of interface \"microceph\"")

	// test microovn specially
	ic = s.installSlotCand(c, "microovn", snap.TypeApp, ``)
	err = ic.Check()
	c.Assert(err, Not(IsNil))
	c.Assert(err, ErrorMatches, "installation not allowed by \"microovn\" slot rule of interface \"microovn\"")

	// test shared-memory specially
	ic = s.installSlotCand(c, "shared-memory", snap.TypeApp, ``)
	err = ic.Check()
	c.Assert(err, Not(IsNil))
	c.Assert(err, ErrorMatches, "installation denied by \"shared-memory\" slot rule of interface \"shared-memory\"")

	// test gpio-chardev specifically as it needs correct attributes in the snap yaml
	for name, snapType := range snapTypeMap {
		ic := s.installSlotCand(c, "gpio-chardev", snapType, fmt.Sprintf(`name: my-device
version: 0
type: %s
slots:
  gpio-chardev:
    source-chip: [chip0]
    lines: 0,1
`, snapType))
		err := ic.Check()
		comm := Commentf("%s by %s snap", "gpio-chardev", name)
		if snapType == snap.TypeGadget {
			c.Check(err, IsNil, comm)
		} else {
			c.Check(err, NotNil, comm)
		}
	}

	// The core and snapd snaps may provide a shared-memory slot
	ic = s.installSlotCand(c, "shared-memory", snap.TypeOS, `name: core
version: 0
type: os
slots:
  shared-memory:
`)
	ic.SnapDeclaration = s.mockSnapDecl(c, "core", "99T7MUlRhtI3U0QFgl5mXXESAiSwt776", "canonical", "")
	c.Assert(ic.Check(), IsNil)

	ic = s.installSlotCand(c, "shared-memory", snap.TypeSnapd, `name: snapd
version: 0
type: snapd
slots:
  shared-memory:
`)
	ic.SnapDeclaration = s.mockSnapDecl(c, "snapd", "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4", "canonical", "")
	c.Assert(ic.Check(), IsNil)

	ic = s.installSlotCand(c, "udisks2", snap.TypeApp, `name: udisks2
version: 0
type: app
slots:
  udisks2:
`)
	err = ic.Check()
	c.Assert(err, IsNil)

	ic = s.installSlotCand(c, "udisks2", snap.TypeApp, `name: udisks2
version: 0
type: app
slots:
  udisks2:
    udev-file: some/file
`)
	err = ic.Check()
	c.Assert(err, Not(IsNil))
	c.Assert(err, ErrorMatches, "installation not allowed by \"udisks2\" slot rule of interface \"udisks2\"")
}

func (s *baseDeclSuite) TestPlugInstallation(c *C) {
	all := builtin.Interfaces()

	restricted := map[string]bool{
		"auditd-support":                   true,
		"block-devices":                    true,
		"checkbox-support":                 true,
		"classic-support":                  true,
		"cuda-driver-libs":                 true,
		"desktop-launch":                   true,
		"dm-crypt":                         true,
		"docker-support":                   true,
		"egl-driver-libs":                  true,
		"gbm-driver-libs":                  true,
		"vulkan-driver-libs":               true,
		"greengrass-support":               true,
		"gpio-control":                     true,
		"ion-memory-control":               true,
		"iscsi-initiator":                  true,
		"kernel-firmware-control":          true,
		"kernel-module-control":            true,
		"kernel-module-load":               true,
		"kubernetes-support":               true,
		"lxd-support":                      true,
		"microceph-support":                true,
		"microstack-support":               true,
		"mount-control":                    true,
		"multipass-support":                true,
		"dm-multipath":                     true,
		"nomad-support":                    true,
		"nvidia-drivers-support":           true,
		"nvme-control":                     true,
		"nvidia-video-driver-libs":         true,
		"opengl-driver-libs":               true,
		"opengles-driver-libs":             true,
		"packagekit-control":               true,
		"personal-files":                   true,
		"polkit":                           true,
		"polkit-agent":                     true,
		"remoteproc":                       true,
		"ros-snapd-support":                true,
		"scsi-generic":                     true,
		"sd-control":                       true,
		"shutdown":                         true,
		"snap-interfaces-requests-control": true,
		"snap-refresh-control":             true,
		"snap-refresh-observe":             true,
		"snap-themes-control":              true,
		"snapd-control":                    true,
		"steam-support":                    true,
		"system-files":                     true,
		"tee":                              true,
		"uinput":                           true,
		"unity8":                           true,
		"ubuntu-pro-control":               true,
		"userns":                           true,
		"xilinx-dma":                       true,
		"snap-fde-control":                 true,
		"firmware-updater-support":         true,
		"devlxd":                           true,
	}

	for _, iface := range all {
		types, ok := restrictedPlugInstallation[iface.Name()]
		// If plug installation is restricted to specific snap types we
		// need to make sure this is really the case here. If that is not
		// the case we continue as normal.
		if ok {
			// only restricted plugs can use the AppArmor
			// unconfined profile mode so check that this
			// plug is not using it
			c.Assert(interfaces.StaticInfoOf(iface).AppArmorUnconfinedPlugs, Equals, false)
			for name, snapType := range snapTypeMap {
				ok := strutil.ListContains(types, name)
				ic := s.installPlugCand(c, iface.Name(), snapType, ``)
				err := ic.Check()
				comm := Commentf("%s by %s snap", iface.Name(), name)
				if ok {
					c.Check(err, IsNil, comm)
				} else {
					c.Check(err, NotNil, comm)
				}
			}
		} else {
			ic := s.installPlugCand(c, iface.Name(), snap.TypeApp, ``)
			err := ic.Check()
			comm := Commentf("%s", iface.Name())
			if restricted[iface.Name()] {
				c.Check(err, NotNil, comm)
			} else {
				c.Check(err, IsNil, comm)
				// only restricted plugs can use the AppArmor
				// unconfined profile mode so check that this
				// plug is not using it
				c.Assert(interfaces.StaticInfoOf(iface).AppArmorUnconfinedPlugs, Equals, false)
			}
		}
	}
}

func (s *baseDeclSuite) TestConnection(c *C) {
	all := builtin.Interfaces()

	// connecting with these interfaces needs to be allowed on
	// case-by-case basis
	noconnect := map[string]bool{
		"content":                   true,
		"cups":                      true,
		"custom-device":             true,
		"desktop":                   true,
		"docker":                    true,
		"fwupd":                     true,
		"location-control":          true,
		"location-observe":          true,
		"lxd":                       true,
		"maliit":                    true,
		"mediatek-accel":            true,
		"microceph":                 true,
		"microovn":                  true,
		"mir":                       true,
		"online-accounts-service":   true,
		"podman":                    true,
		"posix-mq":                  true,
		"qualcomm-ipc-router":       true,
		"raw-volume":                true,
		"screen-inhibit-control":    true,
		"shared-memory":             true,
		"storage-framework-service": true,
		"thumbnailer-service":       true,
		"ubuntu-download-manager":   true,
		"unity8-calendar":           true,
		"unity8-contacts":           true,
		"upower-observe":            true,
	}

	for _, iface := range all {
		expected := !noconnect[iface.Name()]
		comm := Commentf(iface.Name())

		// check base declaration
		cand := s.connectCand(c, iface.Name(), "", "")
		err := cand.Check()

		if expected {
			c.Check(err, IsNil, comm)
		} else {
			c.Check(err, NotNil, comm)
		}
	}
}

func (s *baseDeclSuite) TestConnectionImplicitSlotOnly(c *C) {
	all := builtin.Interfaces()

	// these allow connect only with an implicit slot
	autoconnect := map[string]bool{
		"desktop":                true,
		"qualcomm-ipc-router":    true,
		"screen-inhibit-control": true,
		"upower-observe":         true,
	}

	for _, iface := range all {
		if !autoconnect[iface.Name()] {
			continue
		}
		comm := Commentf(iface.Name())

		// check base declaration
		cand := s.connectCand(c, iface.Name(), fmt.Sprintf(`name: snapd
type: snapd
version: 0
slots:
  %s:
`, iface.Name()), "")
		err := cand.Check()
		c.Check(err, IsNil, comm)
	}
}

func (s *baseDeclSuite) TestConnectionOnClassic(c *C) {
	restore := release.MockOnClassic(false)
	defer restore()

	all := builtin.Interfaces()

	// connecting with these interfaces needs to be allowed on
	// case-by-case basis when not on classic
	noconnect := map[string]bool{
		"audio-record":    true,
		"modem-manager":   true,
		"network-manager": true,
		"ofono":           true,
		"pulseaudio":      true,
	}

	for _, onClassic := range []bool{true, false} {
		release.OnClassic = onClassic
		for _, iface := range all {
			if !noconnect[iface.Name()] {
				continue
			}
			expected := onClassic
			comm := Commentf(iface.Name())

			// check base declaration
			cand := s.connectCand(c, iface.Name(), "", "")
			err := cand.Check()

			if expected {
				c.Check(err, IsNil, comm)
			} else {
				c.Check(err, NotNil, comm)
			}
		}
	}
}

func (s *baseDeclSuite) TestConnectionImplicitOnClassicOrAppSnap(c *C) {
	restore := release.MockOnClassic(false)
	defer restore()

	all := builtin.Interfaces()

	// These interfaces represent when the interface might be implicit on
	// classic or when the interface is provided via an app snap. As such,
	// they all share the following:
	//
	// - implicitOnCore: false
	// - implicitOnClassis: true
	// - base declaration uses:
	//     allow-installation:
	//       slot-snap-type:
	//         - app
	//         - core
	//     deny-connection:
	//       on-classic: false
	//     deny-auto-connection: true|false|unspecified
	//
	// connecting with these interfaces needs to be allowed on
	// case-by-case basis when not on classic
	ifaces := map[string]bool{
		"audio-playback":  true,
		"audio-record":    true,
		"cups-control":    true,
		"modem-manager":   true,
		"network-manager": true,
		"ofono":           true,
		"pulseaudio":      true,
	}

	for _, iface := range all {
		if !ifaces[iface.Name()] {
			continue
		}
		comm := Commentf(iface.Name())

		// verify the interface is setup as expected wrt
		// implicitOnCore, implicitOnClassic, no plugs and has
		// expected slots (ignoring AutoConnection)
		si := interfaces.StaticInfoOf(iface)
		c.Assert(si.ImplicitOnCore, Equals, false)
		c.Assert(si.ImplicitOnClassic, Equals, true)

		c.Assert(s.baseDecl.PlugRule(iface.Name()), IsNil)

		sr := s.baseDecl.SlotRule(iface.Name())
		c.Assert(sr, Not(IsNil))
		c.Assert(sr.AllowInstallation, HasLen, 1)
		c.Check(sr.AllowInstallation[0].SlotSnapTypes, DeepEquals, []string{"app", "core"}, comm)
		c.Assert(sr.DenyConnection, HasLen, 1)
		c.Check(sr.DenyConnection[0].OnClassic, DeepEquals, &asserts.OnClassicConstraint{Classic: false}, comm)

		for _, onClassic := range []bool{true, false} {
			release.OnClassic = onClassic

			for _, implicit := range []bool{true, false} {
				// When implicitOnCore is false, there is
				// nothing to test on Core
				if implicit && !onClassic {
					continue
				}

				snapType := "app"
				if implicit {
					snapType = "os"
				}
				slotYaml := fmt.Sprintf(`name: slot-snap
version: 0
type: %s
slots:
  %s:
`, snapType, iface.Name())

				// XXX: eventually 'onClassic && !implicit' but
				// the current declaration allows connection on
				// Core when 'type: app'. See:
				// https://github.com/snapcore/snapd/pull/8920/files#r471678529
				expected := onClassic

				// check base declaration
				cand := s.connectCand(c, iface.Name(), slotYaml, "")
				err := cand.Check()

				if expected {
					c.Check(err, IsNil, comm)
				} else {
					c.Check(err, NotNil, comm)
				}
			}
		}
	}
}

func (s *baseDeclSuite) TestValidity(c *C) {
	all := builtin.Interfaces()

	// these interfaces have rules both for the slots and plugs side
	// given how the rules work this can be delicate,
	// listed here to make sure that was a conscious decision
	bothSides := map[string]bool{
		"auditd-support":                   true,
		"block-devices":                    true,
		"audio-playback":                   true,
		"classic-support":                  true,
		"checkbox-support":                 true,
		"core-support":                     true,
		"cuda-driver-libs":                 true,
		"custom-device":                    true,
		"desktop":                          true,
		"desktop-launch":                   true,
		"dm-crypt":                         true,
		"docker-support":                   true,
		"egl-driver-libs":                  true,
		"vulkan-driver-libs":               true,
		"gbm-driver-libs":                  true,
		"greengrass-support":               true,
		"gpio-control":                     true,
		"ion-memory-control":               true,
		"iscsi-initiator":                  true,
		"kernel-firmware-control":          true,
		"kernel-module-control":            true,
		"kernel-module-load":               true,
		"kubernetes-support":               true,
		"lxd-support":                      true,
		"microceph-support":                true,
		"microstack-support":               true,
		"mount-control":                    true,
		"multipass-support":                true,
		"dm-multipath":                     true,
		"nomad-support":                    true,
		"nvidia-drivers-support":           true,
		"nvme-control":                     true,
		"nvidia-video-driver-libs":         true,
		"opengl-driver-libs":               true,
		"opengles-driver-libs":             true,
		"packagekit-control":               true,
		"personal-files":                   true,
		"pkcs11":                           true,
		"posix-mq":                         true,
		"polkit":                           true,
		"polkit-agent":                     true,
		"remoteproc":                       true,
		"ros-snapd-support":                true,
		"qualcomm-ipc-router":              true,
		"scsi-generic":                     true,
		"screen-inhibit-control":           true,
		"sd-control":                       true,
		"shutdown":                         true,
		"shared-memory":                    true,
		"snap-interfaces-requests-control": true,
		"snap-refresh-control":             true,
		"snap-refresh-observe":             true,
		"snap-themes-control":              true,
		"snapd-control":                    true,
		"steam-support":                    true,
		"system-files":                     true,
		"tee":                              true,
		"ubuntu-pro-control":               true,
		"udisks2":                          true,
		"uinput":                           true,
		"unity8":                           true,
		"usb-gadget":                       true,
		"userns":                           true,
		"wayland":                          true,
		"xilinx-dma":                       true,
		"confdb":                           true,
		"snap-fde-control":                 true,
		"firmware-updater-support":         true,
		"devlxd":                           true,
		"mediatek-accel":                   true,
	}

	for _, iface := range all {
		plugRule := s.baseDecl.PlugRule(iface.Name())
		slotRule := s.baseDecl.SlotRule(iface.Name())
		if plugRule == nil && slotRule == nil {
			c.Logf("%s is not considered in the base declaration", iface.Name())
			c.Fail()
		}
		if plugRule != nil && slotRule != nil {
			if !bothSides[iface.Name()] {
				c.Logf("%s have both a base declaration slot rule and plug rule, make sure that's intended and correct", iface.Name())
				c.Fail()
			}
		}
	}
}

func (s *baseDeclSuite) TestConnectionContent(c *C) {
	// we let connect explicitly as long as content matches (or is absent on both sides)

	// random (Sanitize* will now also block this)
	cand := s.connectCand(c, "content", "", "")
	err := cand.Check()
	c.Check(err, NotNil)

	slotDecl1 := s.mockSnapDecl(c, "slot-snap", "slot-snap-id", "pub1", "")
	plugDecl1 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub1", "")
	plugDecl2 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub2", "")

	// same publisher, same content
	cand = s.connectCand(c, "stuff", `name: slot-snap
version: 0
slots:
  stuff:
    interface: content
    content: mk1
`, `
name: plug-snap
version: 0
plugs:
  stuff:
    interface: content
    content: mk1
`)
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl1
	err = cand.Check()
	c.Check(err, IsNil)

	// different publisher, same content
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl2
	err = cand.Check()
	c.Check(err, IsNil)

	// same publisher, different content
	cand = s.connectCand(c, "stuff", `
name: slot-snap
version: 0
slots:
  stuff:
    interface: content
    content: mk1
`, `
name: plug-snap
version: 0
plugs:
  stuff:
    interface: content
    content: mk2
`)
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl1
	err = cand.Check()
	c.Check(err, NotNil)
}

func (s *baseDeclSuite) TestConnectionQualcommIpcRouter(c *C) {
	// we let connect explicitly as long as qcipc matches

	slotDecl1 := s.mockSnapDecl(c, "slot-snap", "slot-snap-id", "pub1", "")
	plugDecl1 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub1", "")

	// Same qcipc label
	cand := s.connectCand(c, "qc-router", `name: slot-snap
version: 0
slots:
  qc-router:
    interface: qualcomm-ipc-router
    qcipc: monitor
    address: abcd
`, `
name: plug-snap
version: 0
plugs:
  qc-router:
    interface: qualcomm-ipc-router
    qcipc: monitor
`)
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl1
	err := cand.Check()
	c.Check(err, IsNil)

	// Different qcipc label
	cand = s.connectCand(c, "qc-router", `name: slot-snap
version: 0
slots:
  qc-router:
    interface: qualcomm-ipc-router
    qcipc: monitor
    address: abcd
`, `
name: plug-snap
version: 0
plugs:
  qc-router:
    interface: qualcomm-ipc-router
    qcipc: other
`)
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl1
	err = cand.Check()
	c.Check(err.Error(), Equals, `connection not allowed by slot rule of interface "qualcomm-ipc-router"`)

	// Legacy case with slot provided by system
	cand = s.connectCand(c, "qualcomm-ipc-router", `name: snapd
version: 0
type: snapd
slots:
  qualcomm-ipc-router:
`, `
name: plug-snap
version: 0
plugs:
  qualcomm-ipc-router:
`)
	cand.SlotSnapDeclaration = s.mockSnapDecl(c, "snapd", "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4", "canonical", "")
	cand.PlugSnapDeclaration = plugDecl1
	err = cand.Check()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestConnectionSharedMemory(c *C) {
	// we let connect explicitly as long as shared-memory matches

	// random (Sanitize* will now also block this)
	cand := s.connectCand(c, "shared-memory", "", "")
	err := cand.Check()
	c.Check(err, NotNil)

	slotDecl1 := s.mockSnapDecl(c, "slot-snap", "slot-snap-id", "pub1", "")
	plugDecl1 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub1", "")
	plugDecl2 := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub2", "")

	// same publisher, same shared-memory
	cand = s.connectCand(c, "stuff", `name: slot-snap
version: 0
slots:
  stuff:
    interface: shared-memory
    shared-memory: mk1
`, `
name: plug-snap
version: 0
plugs:
  stuff:
    interface: shared-memory
    private: false
    shared-memory: mk1
`)
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl1
	err = cand.Check()
	c.Check(err, IsNil)

	// different publisher, same shared-memory
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl2
	err = cand.Check()
	c.Check(err, IsNil)

	// same publisher, different shared-memory
	cand = s.connectCand(c, "stuff", `
name: slot-snap
version: 0
slots:
  stuff:
    interface: shared-memory
    shared-memory: mk1
`, `
name: plug-snap
version: 0
plugs:
  stuff:
    interface: shared-memory
    private: false
    shared-memory: mk2
`)
	cand.SlotSnapDeclaration = slotDecl1
	cand.PlugSnapDeclaration = plugDecl1
	err = cand.Check()
	c.Check(err, NotNil)
}

func (s *baseDeclSuite) TestConnectionSharedMemoryPrivate(c *C) {
	slotDecl := s.mockSnapDecl(c, "snapd", "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4", "canonical", "")
	appSlotDecl := s.mockSnapDecl(c, "slot-snap", "slot-snap-id", "pub1", "")
	plugDecl := s.mockSnapDecl(c, "plug-snap", "plug-snap-id", "pub1", "")

	// private shm plug, implicit slot
	cand := s.connectCand(c, "shared-memory", `name: snapd
type: snapd
version: 0
slots:
  shared-memory:
`, `
name: plug-snap
version: 0
plugs:
  shared-memory:
    private: true
`)
	cand.SlotSnapDeclaration = slotDecl
	cand.PlugSnapDeclaration = plugDecl
	err := cand.Check()
	c.Check(err, IsNil)

	// private shm plug, regular app slot
	cand = s.connectCand(c, "shared-memory", `name: slot-snap
version: 0
slots:
  shared-memory:
    shared-memory: mk1
`, `
name: plug-snap
version: 0
plugs:
  shared-memory:
    private: true
`)
	cand.SlotSnapDeclaration = appSlotDecl
	cand.PlugSnapDeclaration = plugDecl
	err = cand.Check()
	c.Check(err, NotNil)

	// regular shm plug, implicit slot
	cand = s.connectCand(c, "shared-memory", `name: snapd
type: snapd
version: 0
slots:
  shared-memory:
`, `
name: plug-snap
version: 0
plugs:
  shared-memory:
    shared-memory: mk1
    private: false
`)
	cand.SlotSnapDeclaration = slotDecl
	cand.PlugSnapDeclaration = plugDecl
	err = cand.Check()
	c.Check(err, NotNil)
}

func (s *baseDeclSuite) TestComposeBaseDeclaration(c *C) {
	decl, err := policy.ComposeBaseDeclaration(nil)
	c.Assert(err, IsNil)
	c.Assert(string(decl), testutil.Contains, `
type: base-declaration
authority-id: canonical
series: 16
revision: 0
`)
}

func (s *baseDeclSuite) TestDoesNotPanic(c *C) {
	// In case there are any issues in the actual interfaces we'd get a panic
	// on snapd startup. This test prevents this from happing unnoticed.
	_, err := policy.ComposeBaseDeclaration(builtin.Interfaces())
	c.Assert(err, IsNil)
}

func (s *baseDeclSuite) TestBrowserSupportAllowSandbox(c *C) {
	const plugYaml = `name: plug-snap
version: 0
plugs:
  browser-support:
   allow-sandbox: true
`
	cand := s.connectCand(c, "browser-support", "", plugYaml)
	err := cand.Check()
	c.Check(err, NotNil)

	_, err = cand.CheckAutoConnect()
	c.Check(err, NotNil)
}

func (s *baseDeclSuite) TestOpticalDriveWrite(c *C) {
	type options struct {
		readonlyYamls []string
		writableYamls []string
	}

	opts := &options{
		readonlyYamls: []string{
			// Non-specified "write" attribute
			`name: plug-snap
version: 0
plugs:
  optical-drive: null
`,
			// Undefined "write" attribute
			`name: plug-snap
version: 0
plugs:
  optical-drive: {}
`,
			// False "write" attribute
			`name: plug-snap
version: 0
plugs:
  optical-drive:
    write: false
`,
		},
		writableYamls: []string{
			// True "write" attribute
			`name: plug-snap
version: 0
plugs:
  optical-drive:
    write: true
`,
		},
	}

	checkOpticalDriveAutoConnect := func(plugYaml string, checker Checker) {
		cand := s.connectCand(c, "optical-drive", "", plugYaml)
		err := cand.Check()
		c.Check(err, checker)
		_, err = cand.CheckAutoConnect()
		c.Check(err, checker)
	}

	for _, plugYaml := range opts.readonlyYamls {
		checkOpticalDriveAutoConnect(plugYaml, IsNil)
	}
	for _, plugYaml := range opts.writableYamls {
		checkOpticalDriveAutoConnect(plugYaml, NotNil)
	}
}

func (s *baseDeclSuite) TestRawVolumeOverride(c *C) {
	const slotYaml = `name: slot-snap
type: gadget
version: 0
slots:
  raw-volume:
    path: /dev/mmcblk0p1
`
	slot, _ := ifacetest.MockConnectedSlot(c, slotYaml, nil, "raw-volume")
	// mock a well-formed slot snap decl with SnapID
	slotSnapDecl := s.mockSnapDecl(c, "slot-snap", "slotsnapidididididididididididid", "canonical", "")

	const plugYaml = `name: plug-snap
version: 0
plugs:
  raw-volume:
`
	plug, _ := ifacetest.MockConnectedPlug(c, plugYaml, nil, "raw-volume")

	// no plug-side declaration
	cand := &policy.ConnectCandidate{
		Plug:                plug,
		Slot:                slot,
		SlotSnapDeclaration: slotSnapDecl,
		BaseDeclaration:     s.baseDecl,
	}

	err := cand.Check()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "connection denied by slot rule of interface \"raw-volume\"")
	_, err = cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by slot rule of interface \"raw-volume\"")

	// specific plug-side declaration for connection only
	plugsOverride := `
plugs:
  raw-volume:
    allow-connection:
      slot-snap-id:
        - slotsnapidididididididididididid
    allow-auto-connection: false
`
	plugSnapDecl := s.mockSnapDecl(c, "plug-snap", "plugsnapidididididididididididid", "canonical", plugsOverride)
	cand.PlugSnapDeclaration = plugSnapDecl
	err = cand.Check()
	c.Check(err, IsNil)
	_, err = cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection not allowed by plug rule of interface \"raw-volume\" for \"plug-snap\" snap")

	// specific plug-side declaration for connection and auto-connection
	plugsOverride = `
plugs:
  raw-volume:
    allow-connection:
      slot-snap-id:
        - slotsnapidididididididididididid
    allow-auto-connection:
      slot-snap-id:
        - slotsnapidididididididididididid
`
	plugSnapDecl = s.mockSnapDecl(c, "plug-snap", "plugsnapidididididididididididid", "canonical", plugsOverride)
	cand.PlugSnapDeclaration = plugSnapDecl
	err = cand.Check()
	c.Check(err, IsNil)
	arity, err := cand.CheckAutoConnect()
	c.Check(err, IsNil)
	c.Check(arity.SlotsPerPlugAny(), Equals, false)

	// blanket allow for connection and auto-connection to any slotting snap
	plugsOverride = `
plugs:
  raw-volume:
    allow-connection: true
    allow-auto-connection: true
`
	plugSnapDecl = s.mockSnapDecl(c, "some-snap", "plugsnapidididididididididididid", "canonical", plugsOverride)
	cand.PlugSnapDeclaration = plugSnapDecl
	err = cand.Check()
	c.Check(err, IsNil)
	arity, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
	c.Check(arity.SlotsPerPlugAny(), Equals, false)
}

func (s *baseDeclSuite) TestAutoConnectionDesktopLaunchOverride(c *C) {
	cand := s.connectCand(c, "desktop-launch", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"desktop-launch\"")

	plugsSlots := `
plugs:
  desktop-launch:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "some-snap", "some-snap-with-desktop-launch-id", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionPolkitAgentOverride(c *C) {
	cand := s.connectCand(c, "polkit-agent", "", "")
	_, err := cand.CheckAutoConnect()
	c.Check(err, NotNil)
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"polkit-agent\"")

	plugsSlots := `
plugs:
  polkit-agent:
    allow-auto-connection: true
`

	snapDecl := s.mockSnapDecl(c, "some-snap", "some-snap-with-polkit-agent-id", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestConnectionDesktop(c *C) {
	cand := s.connectCand(c, "desktop", "", "")
	err := cand.Check()
	c.Assert(err, ErrorMatches, `connection denied by plug rule of interface "desktop"`)

	plugsSlots := `
plugs:
  desktop:
    allow-connection: true
`
	snapDecl := s.mockSnapDecl(c, "some-snap", "some-snap-with-desktop-id", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	err = cand.Check()
	c.Assert(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionDesktop(c *C) {
	cand := s.connectCand(c, "desktop", "", "")
	_, err := cand.CheckAutoConnect()
	c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"desktop\"")

	plugsSlots := `
plugs:
  desktop:
    allow-auto-connection: true
`
	snapDecl := s.mockSnapDecl(c, "some-snap", "some-snap-with-desktop-id", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}

func (s *baseDeclSuite) TestDesktopFileIDsOverride(c *C) {
	ic := s.installPlugCand(c, "desktop", snap.TypeApp, `name: some-snap
version: 0
type: app
plugs:
  desktop:
    desktop-file-ids: [org.example]
`)
	err := ic.Check()
	c.Assert(err, ErrorMatches, `installation not allowed by "desktop" plug rule of interface "desktop"`)

	const plugsOverride = `
plugs:
  desktop:
    allow-installation:
      plug-attributes:
        desktop-file-ids:
          - org.example
`
	ic.SnapDeclaration = s.mockSnapDecl(c, "some-snap", "some-snap-id", "canonical", plugsOverride)
	err = ic.Check()
	c.Assert(err, IsNil)
}

func (s *baseDeclSuite) TestConnectionScreenInhibitControl(c *C) {
	cand := s.connectCand(c, "screen-inhibit-control", "", "")
	err := cand.Check()
	c.Assert(err, ErrorMatches, `connection denied by slot rule of interface "screen-inhibit-control"`)

	plugsSlots := `
plugs:
  screen-inhibit-control:
    allow-connection: true
`
	snapDecl := s.mockSnapDecl(c, "some-snap", "some-snap", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	err = cand.Check()
	c.Assert(err, IsNil)
}

func (s *baseDeclSuite) TestAutoConnectionScreenInhibitControl(c *C) {
	cand := s.connectCand(c, "screen-inhibit-control", "", "")
	_, err := cand.CheckAutoConnect()
	c.Assert(err, ErrorMatches, "auto-connection denied by slot rule of interface \"screen-inhibit-control\"")

	plugsSlots := `
plugs:
  screen-inhibit-control:
    allow-auto-connection: true
`
	snapDecl := s.mockSnapDecl(c, "some-snap", "some-snap", "canonical", plugsSlots)
	cand.PlugSnapDeclaration = snapDecl
	_, err = cand.CheckAutoConnect()
	c.Check(err, IsNil)
}
