github.com/livekit/protocol@v1.39.3/auth/grants.go (about)

     1  // Copyright 2023 LiveKit, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package auth
    16  
    17  import (
    18  	"errors"
    19  	"maps"
    20  	"strings"
    21  
    22  	"go.uber.org/zap/zapcore"
    23  	"golang.org/x/exp/slices"
    24  	"google.golang.org/protobuf/encoding/protojson"
    25  
    26  	"github.com/livekit/protocol/livekit"
    27  	"github.com/livekit/protocol/logger"
    28  	"github.com/livekit/protocol/utils"
    29  )
    30  
    31  type RoomConfiguration livekit.RoomConfiguration
    32  
    33  var tokenMarshaler = protojson.MarshalOptions{
    34  	EmitDefaultValues: false,
    35  }
    36  
    37  var ErrSensitiveCredentials = errors.New("room configuration should not contain sensitive credentials")
    38  
    39  func (c *RoomConfiguration) Clone() *RoomConfiguration {
    40  	if c == nil {
    41  		return nil
    42  	}
    43  	return (*RoomConfiguration)(utils.CloneProto((*livekit.RoomConfiguration)(c)))
    44  }
    45  
    46  func (c *RoomConfiguration) MarshalJSON() ([]byte, error) {
    47  	return tokenMarshaler.Marshal((*livekit.RoomConfiguration)(c))
    48  }
    49  
    50  func (c *RoomConfiguration) UnmarshalJSON(data []byte) error {
    51  	return protojson.Unmarshal(data, (*livekit.RoomConfiguration)(c))
    52  }
    53  
    54  // CheckCredentials checks if the room configuration contains sensitive credentials
    55  // and returns an error if it does.
    56  //
    57  // This is used to prevent sensitive credentials from being leaked to the client.
    58  // It is not used to validate the credentials themselves, as that is done by the
    59  // egress service.
    60  func (c *RoomConfiguration) CheckCredentials() error {
    61  	if c.Egress == nil {
    62  		return nil
    63  	}
    64  
    65  	if c.Egress.Participant != nil {
    66  		for _, output := range c.Egress.Participant.FileOutputs {
    67  			if err := checkOutputForCredentials(output.Output); err != nil {
    68  				return err
    69  			}
    70  		}
    71  		for _, output := range c.Egress.Participant.SegmentOutputs {
    72  			if err := checkOutputForCredentials(output.Output); err != nil {
    73  				return err
    74  			}
    75  		}
    76  	}
    77  	if c.Egress.Room != nil {
    78  		for _, output := range c.Egress.Room.FileOutputs {
    79  			if err := checkOutputForCredentials(output.Output); err != nil {
    80  				return err
    81  			}
    82  		}
    83  		for _, output := range c.Egress.Room.SegmentOutputs {
    84  			if err := checkOutputForCredentials(output.Output); err != nil {
    85  				return err
    86  			}
    87  		}
    88  		for _, output := range c.Egress.Room.ImageOutputs {
    89  			if err := checkOutputForCredentials(output.Output); err != nil {
    90  				return err
    91  			}
    92  		}
    93  		if len(c.Egress.Room.StreamOutputs) > 0 {
    94  			// do not leak stream key
    95  			return ErrSensitiveCredentials
    96  		}
    97  	}
    98  	if c.Egress.Tracks != nil {
    99  		if err := checkOutputForCredentials(c.Egress.Tracks.Output); err != nil {
   100  			return err
   101  		}
   102  	}
   103  	return nil
   104  }
   105  
   106  func checkOutputForCredentials(output any) error {
   107  	if output == nil {
   108  		return nil
   109  	}
   110  
   111  	switch msg := output.(type) {
   112  	case *livekit.EncodedFileOutput_S3:
   113  		if msg.S3.Secret != "" {
   114  			return ErrSensitiveCredentials
   115  		}
   116  	case *livekit.SegmentedFileOutput_S3:
   117  		if msg.S3.Secret != "" {
   118  			return ErrSensitiveCredentials
   119  		}
   120  	case *livekit.AutoTrackEgress_S3:
   121  		if msg.S3.Secret != "" {
   122  			return ErrSensitiveCredentials
   123  		}
   124  	case *livekit.EncodedFileOutput_Gcp:
   125  		if msg.Gcp.Credentials != "" {
   126  			return ErrSensitiveCredentials
   127  		}
   128  	case *livekit.SegmentedFileOutput_Gcp:
   129  		if msg.Gcp.Credentials != "" {
   130  			return ErrSensitiveCredentials
   131  		}
   132  	case *livekit.AutoTrackEgress_Gcp:
   133  		if msg.Gcp.Credentials != "" {
   134  			return ErrSensitiveCredentials
   135  		}
   136  	case *livekit.EncodedFileOutput_Azure:
   137  		if msg.Azure.AccountKey != "" {
   138  			return ErrSensitiveCredentials
   139  		}
   140  	case *livekit.SegmentedFileOutput_Azure:
   141  		if msg.Azure.AccountKey != "" {
   142  			return ErrSensitiveCredentials
   143  		}
   144  	case *livekit.AutoTrackEgress_Azure:
   145  		if msg.Azure.AccountKey != "" {
   146  			return ErrSensitiveCredentials
   147  		}
   148  	case *livekit.EncodedFileOutput_AliOSS:
   149  		if msg.AliOSS.Secret != "" {
   150  			return ErrSensitiveCredentials
   151  		}
   152  	case *livekit.SegmentedFileOutput_AliOSS:
   153  		if msg.AliOSS.Secret != "" {
   154  			return ErrSensitiveCredentials
   155  		}
   156  	case *livekit.AutoTrackEgress_AliOSS:
   157  		if msg.AliOSS.Secret != "" {
   158  			return ErrSensitiveCredentials
   159  		}
   160  	}
   161  	return nil
   162  }
   163  
   164  type ClaimGrants struct {
   165  	Identity string      `json:"-"`
   166  	Name     string      `json:"name,omitempty"`
   167  	Kind     string      `json:"kind,omitempty"`
   168  	Video    *VideoGrant `json:"video,omitempty"`
   169  	SIP      *SIPGrant   `json:"sip,omitempty"`
   170  	Agent    *AgentGrant `json:"agent,omitempty"`
   171  	// Room configuration to use if this participant initiates the room
   172  	RoomConfig *RoomConfiguration `json:"roomConfig,omitempty"`
   173  	// Cloud-only, config preset to use
   174  	// when both room and roomPreset are set, parameters in room overrides the preset
   175  	RoomPreset string `json:"roomPreset,omitempty"`
   176  	// for verifying integrity of the message body
   177  	Sha256   string `json:"sha256,omitempty"`
   178  	Metadata string `json:"metadata,omitempty"`
   179  	// Key/value attributes to attach to the participant
   180  	Attributes map[string]string `json:"attributes,omitempty"`
   181  }
   182  
   183  func (c *ClaimGrants) SetParticipantKind(kind livekit.ParticipantInfo_Kind) {
   184  	c.Kind = kindFromProto(kind)
   185  }
   186  
   187  func (c *ClaimGrants) GetParticipantKind() livekit.ParticipantInfo_Kind {
   188  	return kindToProto(c.Kind)
   189  }
   190  
   191  func (c *ClaimGrants) GetRoomConfiguration() *livekit.RoomConfiguration {
   192  	if c.RoomConfig == nil {
   193  		return nil
   194  	}
   195  	return (*livekit.RoomConfiguration)(c.RoomConfig)
   196  }
   197  
   198  func (c *ClaimGrants) Clone() *ClaimGrants {
   199  	if c == nil {
   200  		return nil
   201  	}
   202  
   203  	clone := *c
   204  	clone.Video = c.Video.Clone()
   205  	clone.SIP = c.SIP.Clone()
   206  	clone.Attributes = maps.Clone(c.Attributes)
   207  	clone.RoomConfig = c.RoomConfig.Clone()
   208  
   209  	return &clone
   210  }
   211  
   212  func (c *ClaimGrants) MarshalLogObject(e zapcore.ObjectEncoder) error {
   213  	if c == nil {
   214  		return nil
   215  	}
   216  
   217  	e.AddString("Identity", c.Identity)
   218  	e.AddString("Kind", c.Kind)
   219  	e.AddObject("Video", c.Video)
   220  	e.AddObject("SIP", c.SIP)
   221  	e.AddObject("RoomConfig", logger.Proto((*livekit.RoomConfiguration)(c.RoomConfig)))
   222  	e.AddString("RoomPreset", c.RoomPreset)
   223  	return nil
   224  }
   225  
   226  // -------------------------------------------------------------
   227  
   228  type VideoGrant struct {
   229  	// actions on rooms
   230  	RoomCreate bool `json:"roomCreate,omitempty"`
   231  	RoomList   bool `json:"roomList,omitempty"`
   232  	RoomRecord bool `json:"roomRecord,omitempty"`
   233  
   234  	// actions on a particular room
   235  	RoomAdmin bool   `json:"roomAdmin,omitempty"`
   236  	RoomJoin  bool   `json:"roomJoin,omitempty"`
   237  	Room      string `json:"room,omitempty"`
   238  
   239  	// permissions within a room, if none of the permissions are set explicitly
   240  	// it will be granted with all publish and subscribe permissions
   241  	CanPublish     *bool `json:"canPublish,omitempty"`
   242  	CanSubscribe   *bool `json:"canSubscribe,omitempty"`
   243  	CanPublishData *bool `json:"canPublishData,omitempty"`
   244  	// TrackSource types that a participant may publish.
   245  	// When set, it supersedes CanPublish. Only sources explicitly set here can be published
   246  	CanPublishSources []string `json:"canPublishSources,omitempty"` // keys keep track of each source
   247  	// by default, a participant is not allowed to update its own metadata
   248  	CanUpdateOwnMetadata *bool `json:"canUpdateOwnMetadata,omitempty"`
   249  
   250  	// actions on ingresses
   251  	IngressAdmin bool `json:"ingressAdmin,omitempty"` // applies to all ingress
   252  
   253  	// participant is not visible to other participants
   254  	Hidden bool `json:"hidden,omitempty"`
   255  	// indicates to the room that current participant is a recorder
   256  	Recorder bool `json:"recorder,omitempty"`
   257  	// indicates that the holder can register as an Agent framework worker
   258  	Agent bool `json:"agent,omitempty"`
   259  
   260  	// if a participant can subscribe to metrics
   261  	CanSubscribeMetrics *bool `json:"canSubscribeMetrics,omitempty"`
   262  
   263  	// destination room which this participant can forward to
   264  	DestinationRoom string `json:"destinationRoom,omitempty"`
   265  }
   266  
   267  func (v *VideoGrant) SetCanPublish(val bool) {
   268  	v.CanPublish = &val
   269  }
   270  
   271  func (v *VideoGrant) SetCanPublishData(val bool) {
   272  	v.CanPublishData = &val
   273  }
   274  
   275  func (v *VideoGrant) SetCanSubscribe(val bool) {
   276  	v.CanSubscribe = &val
   277  }
   278  
   279  func (v *VideoGrant) SetCanPublishSources(sources []livekit.TrackSource) {
   280  	v.CanPublishSources = make([]string, 0, len(sources))
   281  	for _, s := range sources {
   282  		v.CanPublishSources = append(v.CanPublishSources, sourceToString(s))
   283  	}
   284  }
   285  
   286  func (v *VideoGrant) SetCanUpdateOwnMetadata(val bool) {
   287  	v.CanUpdateOwnMetadata = &val
   288  }
   289  
   290  func (v *VideoGrant) SetCanSubscribeMetrics(val bool) {
   291  	v.CanSubscribeMetrics = &val
   292  }
   293  
   294  func (v *VideoGrant) GetCanPublish() bool {
   295  	if v.CanPublish == nil {
   296  		return true
   297  	}
   298  	return *v.CanPublish
   299  }
   300  
   301  func (v *VideoGrant) GetCanPublishSource(source livekit.TrackSource) bool {
   302  	if !v.GetCanPublish() {
   303  		return false
   304  	}
   305  	// don't differentiate between nil and unset, since that distinction doesn't survive serialization
   306  	if len(v.CanPublishSources) == 0 {
   307  		return true
   308  	}
   309  	sourceStr := sourceToString(source)
   310  	for _, s := range v.CanPublishSources {
   311  		if s == sourceStr {
   312  			return true
   313  		}
   314  	}
   315  	return false
   316  }
   317  
   318  func (v *VideoGrant) GetCanPublishSources() []livekit.TrackSource {
   319  	if len(v.CanPublishSources) == 0 {
   320  		return nil
   321  	}
   322  
   323  	sources := make([]livekit.TrackSource, 0, len(v.CanPublishSources))
   324  	for _, s := range v.CanPublishSources {
   325  		sources = append(sources, sourceToProto(s))
   326  	}
   327  	return sources
   328  }
   329  
   330  func (v *VideoGrant) GetCanPublishData() bool {
   331  	if v.CanPublishData == nil {
   332  		return v.GetCanPublish()
   333  	}
   334  	return *v.CanPublishData
   335  }
   336  
   337  func (v *VideoGrant) GetCanSubscribe() bool {
   338  	if v.CanSubscribe == nil {
   339  		return true
   340  	}
   341  	return *v.CanSubscribe
   342  }
   343  
   344  func (v *VideoGrant) GetCanUpdateOwnMetadata() bool {
   345  	if v.CanUpdateOwnMetadata == nil {
   346  		return false
   347  	}
   348  	return *v.CanUpdateOwnMetadata
   349  }
   350  
   351  func (v *VideoGrant) GetCanSubscribeMetrics() bool {
   352  	if v.CanSubscribeMetrics == nil {
   353  		return false
   354  	}
   355  	return *v.CanSubscribeMetrics
   356  }
   357  
   358  func (v *VideoGrant) MatchesPermission(permission *livekit.ParticipantPermission) bool {
   359  	if permission == nil {
   360  		return false
   361  	}
   362  
   363  	if v.GetCanPublish() != permission.CanPublish {
   364  		return false
   365  	}
   366  	if v.GetCanPublishData() != permission.CanPublishData {
   367  		return false
   368  	}
   369  	if v.GetCanSubscribe() != permission.CanSubscribe {
   370  		return false
   371  	}
   372  	if v.GetCanUpdateOwnMetadata() != permission.CanUpdateMetadata {
   373  		return false
   374  	}
   375  	if v.Hidden != permission.Hidden {
   376  		return false
   377  	}
   378  	if v.Recorder != permission.Recorder {
   379  		return false
   380  	}
   381  	if v.Agent != permission.Agent {
   382  		return false
   383  	}
   384  	if !slices.Equal(v.GetCanPublishSources(), permission.CanPublishSources) {
   385  		return false
   386  	}
   387  	if v.GetCanSubscribeMetrics() != permission.CanSubscribeMetrics {
   388  		return false
   389  	}
   390  
   391  	return true
   392  }
   393  
   394  func (v *VideoGrant) UpdateFromPermission(permission *livekit.ParticipantPermission) {
   395  	if permission == nil {
   396  		return
   397  	}
   398  
   399  	v.SetCanPublish(permission.CanPublish)
   400  	v.SetCanPublishData(permission.CanPublishData)
   401  	v.SetCanPublishSources(permission.CanPublishSources)
   402  	v.SetCanSubscribe(permission.CanSubscribe)
   403  	v.SetCanUpdateOwnMetadata(permission.CanUpdateMetadata)
   404  	v.Hidden = permission.Hidden
   405  	v.Recorder = permission.Recorder
   406  	v.Agent = permission.Agent
   407  	v.SetCanSubscribeMetrics(permission.CanSubscribeMetrics)
   408  }
   409  
   410  func (v *VideoGrant) ToPermission() *livekit.ParticipantPermission {
   411  	return &livekit.ParticipantPermission{
   412  		CanPublish:          v.GetCanPublish(),
   413  		CanPublishData:      v.GetCanPublishData(),
   414  		CanSubscribe:        v.GetCanSubscribe(),
   415  		CanPublishSources:   v.GetCanPublishSources(),
   416  		CanUpdateMetadata:   v.GetCanUpdateOwnMetadata(),
   417  		Hidden:              v.Hidden,
   418  		Recorder:            v.Recorder,
   419  		Agent:               v.Agent,
   420  		CanSubscribeMetrics: v.GetCanSubscribeMetrics(),
   421  	}
   422  }
   423  
   424  func (v *VideoGrant) Clone() *VideoGrant {
   425  	if v == nil {
   426  		return nil
   427  	}
   428  
   429  	clone := *v
   430  
   431  	if v.CanPublish != nil {
   432  		canPublish := *v.CanPublish
   433  		clone.CanPublish = &canPublish
   434  	}
   435  
   436  	if v.CanSubscribe != nil {
   437  		canSubscribe := *v.CanSubscribe
   438  		clone.CanSubscribe = &canSubscribe
   439  	}
   440  
   441  	if v.CanPublishData != nil {
   442  		canPublishData := *v.CanPublishData
   443  		clone.CanPublishData = &canPublishData
   444  	}
   445  
   446  	if v.CanPublishSources != nil {
   447  		clone.CanPublishSources = make([]string, len(v.CanPublishSources))
   448  		copy(clone.CanPublishSources, v.CanPublishSources)
   449  	}
   450  
   451  	if v.CanUpdateOwnMetadata != nil {
   452  		canUpdateOwnMetadata := *v.CanUpdateOwnMetadata
   453  		clone.CanUpdateOwnMetadata = &canUpdateOwnMetadata
   454  	}
   455  
   456  	return &clone
   457  }
   458  
   459  func (v *VideoGrant) MarshalLogObject(e zapcore.ObjectEncoder) error {
   460  	if v == nil {
   461  		return nil
   462  	}
   463  
   464  	logBoolPtr := func(prop string, val *bool) {
   465  		if val == nil {
   466  			e.AddString(prop, "not-set")
   467  		} else {
   468  			e.AddBool(prop, *val)
   469  		}
   470  	}
   471  
   472  	logBoolPtr("RoomCreate", &v.RoomCreate)
   473  	logBoolPtr("RoomList", &v.RoomList)
   474  	logBoolPtr("RoomRecord", &v.RoomRecord)
   475  
   476  	logBoolPtr("RoomAdmin", &v.RoomAdmin)
   477  	logBoolPtr("RoomJoin", &v.RoomJoin)
   478  	e.AddString("Room", v.Room)
   479  
   480  	logBoolPtr("CanPublish", v.CanPublish)
   481  	logBoolPtr("CanSubscribe", v.CanSubscribe)
   482  	logBoolPtr("CanPublishData", v.CanPublishData)
   483  	e.AddArray("CanPublishSources", logger.StringSlice(v.CanPublishSources))
   484  	logBoolPtr("CanUpdateOwnMetadata", v.CanUpdateOwnMetadata)
   485  
   486  	logBoolPtr("IngressAdmin", &v.IngressAdmin)
   487  
   488  	logBoolPtr("Hidden", &v.Hidden)
   489  	logBoolPtr("Recorder", &v.Recorder)
   490  	logBoolPtr("Agent", &v.Agent)
   491  
   492  	logBoolPtr("CanSubscribeMetrics", v.CanSubscribeMetrics)
   493  	e.AddString("DestinationRoom", v.DestinationRoom)
   494  	return nil
   495  }
   496  
   497  // ----------------------------------------------------------------
   498  
   499  type SIPGrant struct {
   500  	// Admin grants access to all SIP features.
   501  	Admin bool `json:"admin,omitempty"`
   502  
   503  	// Call allows making outbound SIP calls.
   504  	Call bool `json:"call,omitempty"`
   505  }
   506  
   507  func (s *SIPGrant) Clone() *SIPGrant {
   508  	if s == nil {
   509  		return nil
   510  	}
   511  
   512  	clone := *s
   513  
   514  	return &clone
   515  }
   516  
   517  func (s *SIPGrant) MarshalLogObject(e zapcore.ObjectEncoder) error {
   518  	if s == nil {
   519  		return nil
   520  	}
   521  
   522  	e.AddBool("Admin", s.Admin)
   523  	e.AddBool("Call", s.Call)
   524  	return nil
   525  }
   526  
   527  // ------------------------------------------------------------------
   528  
   529  // ------------------------------------------------------------------
   530  
   531  type AgentGrant struct {
   532  	// Admin grants to create/update/delete Cloud Agents.
   533  	Admin bool `json:"admin,omitempty"`
   534  }
   535  
   536  func (s *AgentGrant) Clone() *AgentGrant {
   537  	if s == nil {
   538  		return nil
   539  	}
   540  
   541  	clone := *s
   542  
   543  	return &clone
   544  }
   545  
   546  func (s *AgentGrant) MarshalLogObject(e zapcore.ObjectEncoder) error {
   547  	if s == nil {
   548  		return nil
   549  	}
   550  
   551  	e.AddBool("Admin", s.Admin)
   552  	return nil
   553  }
   554  
   555  // ------------------------------------------------------------------
   556  
   557  func sourceToString(source livekit.TrackSource) string {
   558  	return strings.ToLower(source.String())
   559  }
   560  
   561  func sourceToProto(sourceStr string) livekit.TrackSource {
   562  	switch strings.ToLower(sourceStr) {
   563  	case "camera":
   564  		return livekit.TrackSource_CAMERA
   565  	case "microphone":
   566  		return livekit.TrackSource_MICROPHONE
   567  	case "screen_share":
   568  		return livekit.TrackSource_SCREEN_SHARE
   569  	case "screen_share_audio":
   570  		return livekit.TrackSource_SCREEN_SHARE_AUDIO
   571  	default:
   572  		return livekit.TrackSource_UNKNOWN
   573  	}
   574  }
   575  
   576  func kindFromProto(source livekit.ParticipantInfo_Kind) string {
   577  	return strings.ToLower(source.String())
   578  }
   579  
   580  func kindToProto(sourceStr string) livekit.ParticipantInfo_Kind {
   581  	switch strings.ToLower(sourceStr) {
   582  	case "", "standard":
   583  		return livekit.ParticipantInfo_STANDARD
   584  	case "ingress":
   585  		return livekit.ParticipantInfo_INGRESS
   586  	case "egress":
   587  		return livekit.ParticipantInfo_EGRESS
   588  	case "sip":
   589  		return livekit.ParticipantInfo_SIP
   590  	case "agent":
   591  		return livekit.ParticipantInfo_AGENT
   592  	default:
   593  		return livekit.ParticipantInfo_STANDARD
   594  	}
   595  }