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 }