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

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-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
    21  
    22  import (
    23  	"fmt"
    24  	"regexp"
    25  	"strings"
    26  
    27  	"github.com/snapcore/snapd/interfaces"
    28  	"github.com/snapcore/snapd/interfaces/apparmor"
    29  	"github.com/snapcore/snapd/release"
    30  	"github.com/snapcore/snapd/snap"
    31  )
    32  
    33  const mprisSummary = `allows operating as an MPRIS player`
    34  
    35  const mprisBaseDeclarationSlots = `
    36    mpris:
    37      allow-installation:
    38        slot-snap-type:
    39          - app
    40      deny-connection:
    41        slot-attributes:
    42          name: .+
    43      deny-auto-connection: true
    44  `
    45  
    46  const mprisPermanentSlotAppArmor = `
    47  # Description: Allow operating as an MPRIS player.
    48  
    49  # DBus accesses
    50  #include <abstractions/dbus-session-strict>
    51  
    52  # https://specifications.freedesktop.org/mpris-spec/latest/
    53  # allow binding to the well-known DBus mpris interface based on the snap's name
    54  dbus (bind)
    55      bus=session
    56      name="org.mpris.MediaPlayer2.###MPRIS_NAME###{,.*}",
    57  
    58  # register as a player
    59  dbus (send)
    60      bus=system
    61      path=/org/freedesktop/DBus
    62      interface=org.freedesktop.DBus
    63      member="{Request,Release}Name"
    64      peer=(name=org.freedesktop.DBus, label=unconfined),
    65  
    66  dbus (send)
    67      bus=system
    68      path=/org/freedesktop/DBus
    69      interface=org.freedesktop.DBus
    70      member="GetConnectionUnix{ProcessID,User}"
    71      peer=(name=org.freedesktop.DBus, label=unconfined),
    72  
    73  dbus (send)
    74      bus=session
    75      path=/org/mpris/MediaPlayer2
    76      interface=org.freedesktop.DBus.Properties
    77      member="{GetAll,PropertiesChanged}"
    78      peer=(name=org.freedesktop.DBus, label=unconfined),
    79  
    80  dbus (send)
    81      bus=session
    82      path=/org/mpris/MediaPlayer2
    83      interface="org.mpris.MediaPlayer2{,.Player}"
    84      peer=(name=org.freedesktop.DBus, label=unconfined),
    85  
    86  # we can always connect to ourselves
    87  dbus (receive)
    88      bus=session
    89      path=/org/mpris/MediaPlayer2
    90      peer=(label=@{profile_name}),
    91  `
    92  
    93  const mprisConnectedSlotAppArmor = `
    94  # Allow connected clients to interact with the player
    95  dbus (receive)
    96      bus=session
    97      interface=org.freedesktop.DBus.Properties
    98      path=/org/mpris/MediaPlayer2
    99      peer=(label=###PLUG_SECURITY_TAGS###),
   100  
   101  dbus (receive)
   102      bus=session
   103      interface=org.freedesktop.DBus.Introspectable
   104      peer=(label=###PLUG_SECURITY_TAGS###),
   105  
   106  dbus (receive)
   107      bus=session
   108      interface="org.mpris.MediaPlayer2{,.*}"
   109      path=/org/mpris/MediaPlayer2
   110      peer=(label=###PLUG_SECURITY_TAGS###),
   111  
   112  dbus (send)
   113      bus=session
   114      interface=org.freedesktop.DBus.Properties
   115      path=/org/mpris/MediaPlayer2
   116      member=PropertiesChanged
   117      peer=(label=###PLUG_SECURITY_TAGS###),
   118  `
   119  
   120  const mprisConnectedSlotAppArmorClassic = `
   121  # Allow unconfined clients to interact with the player on classic
   122  dbus (receive)
   123      bus=session
   124      path=/org/mpris/MediaPlayer2
   125      peer=(label=unconfined),
   126  dbus (receive)
   127      bus=session
   128      interface=org.freedesktop.DBus.Introspectable
   129      peer=(label=unconfined),
   130  `
   131  
   132  const mprisConnectedPlugAppArmor = `
   133  # Description: Allow connecting to an MPRIS player.
   134  
   135  #include <abstractions/dbus-session-strict>
   136  
   137  # Find the mpris player
   138  dbus (send)
   139      bus=session
   140      path=/org/freedesktop/DBus
   141      interface=org.freedesktop.DBus.Introspectable
   142      peer=(name="org.freedesktop.DBus", label="unconfined"),
   143  dbus (send)
   144      bus=session
   145      path=/{,org,org/mpris,org/mpris/MediaPlayer2}
   146      interface=org.freedesktop.DBus.Introspectable
   147      peer=(name="org.freedesktop.DBus", label="unconfined"),
   148  # This reveals all names on the session bus
   149  dbus (send)
   150      bus=session
   151      path=/
   152      interface=org.freedesktop.DBus
   153      member=ListNames
   154      peer=(name="org.freedesktop.DBus", label="unconfined"),
   155  
   156  # Communicate with the mpris player
   157  dbus (send)
   158      bus=session
   159      path=/org/mpris/MediaPlayer2
   160      peer=(label=###SLOT_SECURITY_TAGS###),
   161  `
   162  
   163  type mprisInterface struct{}
   164  
   165  func (iface *mprisInterface) Name() string {
   166  	return "mpris"
   167  }
   168  
   169  func (iface *mprisInterface) StaticInfo() interfaces.StaticInfo {
   170  	return interfaces.StaticInfo{
   171  		Summary:              mprisSummary,
   172  		BaseDeclarationSlots: mprisBaseDeclarationSlots,
   173  	}
   174  }
   175  
   176  func (iface *mprisInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   177  	old := "###SLOT_SECURITY_TAGS###"
   178  	new := slotAppLabelExpr(slot)
   179  	spec.AddSnippet(strings.Replace(mprisConnectedPlugAppArmor, old, new, -1))
   180  	return nil
   181  }
   182  
   183  func (iface *mprisInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error {
   184  	name, err := iface.getName(slot.Attrs)
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	old := "###MPRIS_NAME###"
   190  	new := name
   191  	spec.AddSnippet(strings.Replace(mprisPermanentSlotAppArmor, old, new, -1))
   192  	// on classic, allow unconfined remotes to control the player
   193  	// (eg, indicator-sound)
   194  	if release.OnClassic {
   195  		spec.AddSnippet(mprisConnectedSlotAppArmorClassic)
   196  	}
   197  	return nil
   198  }
   199  
   200  func (iface *mprisInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   201  	old := "###PLUG_SECURITY_TAGS###"
   202  	new := plugAppLabelExpr(plug)
   203  	spec.AddSnippet(strings.Replace(mprisConnectedSlotAppArmor, old, new, -1))
   204  	return nil
   205  }
   206  
   207  var isValidDBusElement = regexp.MustCompile("^[a-zA-Z0-9_-]*$").MatchString
   208  
   209  func (iface *mprisInterface) getName(attribs map[string]interface{}) (string, error) {
   210  	// default to snap instance name if 'name' attribute not set
   211  	// parallel-installs: snaps utilizing the mpris interface must adjust
   212  	// themselves accordingly for parallel installs and use
   213  	// SNAP_INSTANCE_NAME as part of their well-known name.
   214  	mprisName := "@{SNAP_INSTANCE_NAME}"
   215  	for attr := range attribs {
   216  		if attr != "name" {
   217  			return "", fmt.Errorf("unknown attribute '%s'", attr)
   218  		}
   219  		raw, ok := attribs[attr]
   220  		if !ok {
   221  			return "", fmt.Errorf("cannot find attribute %q", attr)
   222  		}
   223  		name, ok := raw.(string)
   224  		if !ok {
   225  			return "", fmt.Errorf("name element %v is not a string", raw)
   226  		}
   227  
   228  		if !isValidDBusElement(name) {
   229  			return "", fmt.Errorf("invalid name element: %q", name)
   230  		}
   231  		mprisName = name
   232  	}
   233  	return mprisName, nil
   234  }
   235  
   236  func (iface *mprisInterface) BeforePrepareSlot(slot *snap.SlotInfo) error {
   237  	_, err := iface.getName(slot.Attrs)
   238  	return err
   239  }
   240  
   241  func (iface *mprisInterface) AutoConnect(*snap.PlugInfo, *snap.SlotInfo) bool {
   242  	// allow what declarations allowed
   243  	return true
   244  }
   245  
   246  func init() {
   247  	registerIface(&mprisInterface{})
   248  }