Files
protocol/auth/grants.go

330 lines
8.5 KiB
Go

// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package auth
import (
"maps"
"strings"
"golang.org/x/exp/slices"
"github.com/livekit/protocol/livekit"
)
type VideoGrant struct {
// actions on rooms
RoomCreate bool `json:"roomCreate,omitempty"`
RoomList bool `json:"roomList,omitempty"`
RoomRecord bool `json:"roomRecord,omitempty"`
// actions on a particular room
RoomAdmin bool `json:"roomAdmin,omitempty"`
RoomJoin bool `json:"roomJoin,omitempty"`
Room string `json:"room,omitempty"`
// Name of the room configuration to apply to the room if created
RoomConfiguration string `json:"roomConfiguration,omitempty"`
// permissions within a room, if none of the permissions are set explicitly
// it will be granted with all publish and subscribe permissions
CanPublish *bool `json:"canPublish,omitempty"`
CanSubscribe *bool `json:"canSubscribe,omitempty"`
CanPublishData *bool `json:"canPublishData,omitempty"`
// TrackSource types that a participant may publish.
// When set, it supersedes CanPublish. Only sources explicitly set here can be published
CanPublishSources []string `json:"canPublishSources,omitempty"` // keys keep track of each source
// by default, a participant is not allowed to update its own metadata
CanUpdateOwnMetadata *bool `json:"canUpdateOwnMetadata,omitempty"`
// actions on ingresses
IngressAdmin bool `json:"ingressAdmin,omitempty"` // applies to all ingress
// participant is not visible to other participants
Hidden bool `json:"hidden,omitempty"`
// indicates to the room that current participant is a recorder
Recorder bool `json:"recorder,omitempty"`
// indicates that the holder can register as an Agent framework worker
// it is also set on all participants that are joining as Agent
Agent bool `json:"agent,omitempty"`
}
type SIPGrant struct {
// Admin grants access to all SIP features.
Admin bool `json:"admin,omitempty"`
// Call allows making outbound SIP calls.
Call bool `json:"call,omitempty"`
}
type ClaimGrants struct {
Identity string `json:"-"`
Name string `json:"name,omitempty"`
Kind string `json:"kind,omitempty"`
Video *VideoGrant `json:"video,omitempty"`
SIP *SIPGrant `json:"sip,omitempty"`
// for verifying integrity of the message body
Sha256 string `json:"sha256,omitempty"`
Metadata string `json:"metadata,omitempty"`
// Key/value attributes to attach to the participant
Attributes map[string]string `json:"attributes,omitempty"`
}
func (c *ClaimGrants) SetParticipantKind(kind livekit.ParticipantInfo_Kind) {
c.Kind = kindFromProto(kind)
}
func (c *ClaimGrants) GetParticipantKind() livekit.ParticipantInfo_Kind {
return kindToProto(c.Kind)
}
func (c *ClaimGrants) Clone() *ClaimGrants {
if c == nil {
return nil
}
clone := *c
clone.Video = c.Video.Clone()
clone.SIP = c.SIP.Clone()
clone.Attributes = maps.Clone(c.Attributes)
return &clone
}
func (v *VideoGrant) SetCanPublish(val bool) {
v.CanPublish = &val
}
func (v *VideoGrant) SetCanPublishData(val bool) {
v.CanPublishData = &val
}
func (v *VideoGrant) SetCanSubscribe(val bool) {
v.CanSubscribe = &val
}
func (v *VideoGrant) SetCanPublishSources(sources []livekit.TrackSource) {
v.CanPublishSources = make([]string, 0, len(sources))
for _, s := range sources {
v.CanPublishSources = append(v.CanPublishSources, sourceToString(s))
}
}
func (v *VideoGrant) SetCanUpdateOwnMetadata(val bool) {
v.CanUpdateOwnMetadata = &val
}
func (v *VideoGrant) GetCanPublish() bool {
if v.CanPublish == nil {
return true
}
return *v.CanPublish
}
func (v *VideoGrant) GetCanPublishSource(source livekit.TrackSource) bool {
if !v.GetCanPublish() {
return false
}
// don't differentiate between nil and unset, since that distinction doesn't survive serialization
if len(v.CanPublishSources) == 0 {
return true
}
sourceStr := sourceToString(source)
for _, s := range v.CanPublishSources {
if s == sourceStr {
return true
}
}
return false
}
func (v *VideoGrant) GetCanPublishSources() []livekit.TrackSource {
if len(v.CanPublishSources) == 0 {
return nil
}
sources := make([]livekit.TrackSource, 0, len(v.CanPublishSources))
for _, s := range v.CanPublishSources {
sources = append(sources, sourceToProto(s))
}
return sources
}
func (v *VideoGrant) GetCanPublishData() bool {
if v.CanPublishData == nil {
return v.GetCanPublish()
}
return *v.CanPublishData
}
func (v *VideoGrant) GetCanSubscribe() bool {
if v.CanSubscribe == nil {
return true
}
return *v.CanSubscribe
}
func (v *VideoGrant) GetCanUpdateOwnMetadata() bool {
if v.CanUpdateOwnMetadata == nil {
return false
}
return *v.CanUpdateOwnMetadata
}
func (v *VideoGrant) MatchesPermission(permission *livekit.ParticipantPermission) bool {
if permission == nil {
return false
}
if v.GetCanPublish() != permission.CanPublish {
return false
}
if v.GetCanPublishData() != permission.CanPublishData {
return false
}
if v.GetCanSubscribe() != permission.CanSubscribe {
return false
}
if v.GetCanUpdateOwnMetadata() != permission.CanUpdateMetadata {
return false
}
if v.Hidden != permission.Hidden {
return false
}
if v.Recorder != permission.Recorder {
return false
}
if v.Agent != permission.Agent {
return false
}
if !slices.Equal(v.GetCanPublishSources(), permission.CanPublishSources) {
return false
}
return true
}
func (v *VideoGrant) UpdateFromPermission(permission *livekit.ParticipantPermission) {
if permission == nil {
return
}
v.SetCanPublish(permission.CanPublish)
v.SetCanPublishData(permission.CanPublishData)
v.SetCanPublishSources(permission.CanPublishSources)
v.SetCanSubscribe(permission.CanSubscribe)
v.SetCanUpdateOwnMetadata(permission.CanUpdateMetadata)
v.Hidden = permission.Hidden
v.Recorder = permission.Recorder
v.Agent = permission.Agent
}
func (v *VideoGrant) ToPermission() *livekit.ParticipantPermission {
pp := &livekit.ParticipantPermission{
CanPublish: v.GetCanPublish(),
CanPublishData: v.GetCanPublishData(),
CanSubscribe: v.GetCanSubscribe(),
CanPublishSources: v.GetCanPublishSources(),
CanUpdateMetadata: v.GetCanUpdateOwnMetadata(),
Hidden: v.Hidden,
Recorder: v.Recorder,
Agent: v.Agent,
}
return pp
}
func (v *VideoGrant) Clone() *VideoGrant {
if v == nil {
return nil
}
clone := *v
if v.CanPublish != nil {
canPublish := *v.CanPublish
clone.CanPublish = &canPublish
}
if v.CanSubscribe != nil {
canSubscribe := *v.CanSubscribe
clone.CanSubscribe = &canSubscribe
}
if v.CanPublishData != nil {
canPublishData := *v.CanPublishData
clone.CanPublishData = &canPublishData
}
if v.CanPublishSources != nil {
clone.CanPublishSources = make([]string, len(v.CanPublishSources))
copy(clone.CanPublishSources, v.CanPublishSources)
}
if v.CanUpdateOwnMetadata != nil {
canUpdateOwnMetadata := *v.CanUpdateOwnMetadata
clone.CanUpdateOwnMetadata = &canUpdateOwnMetadata
}
return &clone
}
func (s *SIPGrant) Clone() *SIPGrant {
if s == nil {
return nil
}
clone := *s
return &clone
}
func sourceToString(source livekit.TrackSource) string {
return strings.ToLower(source.String())
}
func sourceToProto(sourceStr string) livekit.TrackSource {
switch sourceStr {
case "camera":
return livekit.TrackSource_CAMERA
case "microphone":
return livekit.TrackSource_MICROPHONE
case "screen_share":
return livekit.TrackSource_SCREEN_SHARE
case "screen_share_audio":
return livekit.TrackSource_SCREEN_SHARE_AUDIO
default:
return livekit.TrackSource_UNKNOWN
}
}
func kindFromProto(source livekit.ParticipantInfo_Kind) string {
return strings.ToLower(source.String())
}
func kindToProto(sourceStr string) livekit.ParticipantInfo_Kind {
switch sourceStr {
case "", "standard":
return livekit.ParticipantInfo_STANDARD
case "ingress":
return livekit.ParticipantInfo_INGRESS
case "egress":
return livekit.ParticipantInfo_EGRESS
case "sip":
return livekit.ParticipantInfo_SIP
case "agent":
return livekit.ParticipantInfo_AGENT
default:
return livekit.ParticipantInfo_STANDARD
}
}