670 lines
17 KiB
Go
670 lines
17 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 (
|
|
"errors"
|
|
"maps"
|
|
"strings"
|
|
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
"golang.org/x/exp/slices"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
|
|
"github.com/livekit/protocol/livekit"
|
|
"github.com/livekit/protocol/logger"
|
|
"github.com/livekit/protocol/utils"
|
|
)
|
|
|
|
type RoomConfiguration livekit.RoomConfiguration
|
|
|
|
var tokenMarshaler = protojson.MarshalOptions{
|
|
EmitDefaultValues: false,
|
|
}
|
|
|
|
var ErrSensitiveCredentials = errors.New("room configuration should not contain sensitive credentials")
|
|
|
|
func (c *RoomConfiguration) Clone() *RoomConfiguration {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return (*RoomConfiguration)(utils.CloneProto((*livekit.RoomConfiguration)(c)))
|
|
}
|
|
|
|
func (c *RoomConfiguration) MarshalJSON() ([]byte, error) {
|
|
return tokenMarshaler.Marshal((*livekit.RoomConfiguration)(c))
|
|
}
|
|
|
|
func (c *RoomConfiguration) UnmarshalJSON(data []byte) error {
|
|
return protojson.Unmarshal(data, (*livekit.RoomConfiguration)(c))
|
|
}
|
|
|
|
// CheckCredentials checks if the room configuration contains sensitive credentials
|
|
// and returns an error if it does.
|
|
//
|
|
// This is used to prevent sensitive credentials from being leaked to the client.
|
|
// It is not used to validate the credentials themselves, as that is done by the
|
|
// egress service.
|
|
func (c *RoomConfiguration) CheckCredentials() error {
|
|
if c.Egress == nil {
|
|
return nil
|
|
}
|
|
|
|
if c.Egress.Participant != nil {
|
|
for _, output := range c.Egress.Participant.FileOutputs {
|
|
if err := checkOutputForCredentials(output.Output); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, output := range c.Egress.Participant.SegmentOutputs {
|
|
if err := checkOutputForCredentials(output.Output); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if c.Egress.Room != nil {
|
|
for _, output := range c.Egress.Room.FileOutputs {
|
|
if err := checkOutputForCredentials(output.Output); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, output := range c.Egress.Room.SegmentOutputs {
|
|
if err := checkOutputForCredentials(output.Output); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, output := range c.Egress.Room.ImageOutputs {
|
|
if err := checkOutputForCredentials(output.Output); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if len(c.Egress.Room.StreamOutputs) > 0 {
|
|
// do not leak stream key
|
|
return ErrSensitiveCredentials
|
|
}
|
|
}
|
|
if c.Egress.Tracks != nil {
|
|
if err := checkOutputForCredentials(c.Egress.Tracks.Output); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkOutputForCredentials(output any) error {
|
|
if output == nil {
|
|
return nil
|
|
}
|
|
|
|
switch msg := output.(type) {
|
|
case *livekit.EncodedFileOutput_S3:
|
|
if msg.S3.Secret != "" {
|
|
return ErrSensitiveCredentials
|
|
}
|
|
case *livekit.SegmentedFileOutput_S3:
|
|
if msg.S3.Secret != "" {
|
|
return ErrSensitiveCredentials
|
|
}
|
|
case *livekit.AutoTrackEgress_S3:
|
|
if msg.S3.Secret != "" {
|
|
return ErrSensitiveCredentials
|
|
}
|
|
case *livekit.EncodedFileOutput_Gcp:
|
|
if msg.Gcp.Credentials != "" {
|
|
return ErrSensitiveCredentials
|
|
}
|
|
case *livekit.SegmentedFileOutput_Gcp:
|
|
if msg.Gcp.Credentials != "" {
|
|
return ErrSensitiveCredentials
|
|
}
|
|
case *livekit.AutoTrackEgress_Gcp:
|
|
if msg.Gcp.Credentials != "" {
|
|
return ErrSensitiveCredentials
|
|
}
|
|
case *livekit.EncodedFileOutput_Azure:
|
|
if msg.Azure.AccountKey != "" {
|
|
return ErrSensitiveCredentials
|
|
}
|
|
case *livekit.SegmentedFileOutput_Azure:
|
|
if msg.Azure.AccountKey != "" {
|
|
return ErrSensitiveCredentials
|
|
}
|
|
case *livekit.AutoTrackEgress_Azure:
|
|
if msg.Azure.AccountKey != "" {
|
|
return ErrSensitiveCredentials
|
|
}
|
|
case *livekit.EncodedFileOutput_AliOSS:
|
|
if msg.AliOSS.Secret != "" {
|
|
return ErrSensitiveCredentials
|
|
}
|
|
case *livekit.SegmentedFileOutput_AliOSS:
|
|
if msg.AliOSS.Secret != "" {
|
|
return ErrSensitiveCredentials
|
|
}
|
|
case *livekit.AutoTrackEgress_AliOSS:
|
|
if msg.AliOSS.Secret != "" {
|
|
return ErrSensitiveCredentials
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type ClaimGrants struct {
|
|
Identity string `json:"identity,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Kind string `json:"kind,omitempty"`
|
|
KindDetails []string `json:"kindDetails,omitempty"`
|
|
Video *VideoGrant `json:"video,omitempty"`
|
|
SIP *SIPGrant `json:"sip,omitempty"`
|
|
Agent *AgentGrant `json:"agent,omitempty"`
|
|
Inference *InferenceGrant `json:"inference,omitempty"`
|
|
Observability *ObservabilityGrant `json:"observability,omitempty"`
|
|
// Room configuration to use if this participant initiates the room
|
|
RoomConfig *RoomConfiguration `json:"roomConfig,omitempty"`
|
|
// Cloud-only, config preset to use
|
|
// when both room and roomPreset are set, parameters in room overrides the preset
|
|
RoomPreset string `json:"roomPreset,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) SetKindDetail(details ...livekit.ParticipantInfo_KindDetail) {
|
|
c.KindDetails = kindDetailsFromProto(details)
|
|
}
|
|
|
|
func (c *ClaimGrants) GetKindDetails() []livekit.ParticipantInfo_KindDetail {
|
|
return kindDetailsToProto(c.KindDetails)
|
|
}
|
|
|
|
func (c *ClaimGrants) GetRoomConfiguration() *livekit.RoomConfiguration {
|
|
if c.RoomConfig == nil {
|
|
return nil
|
|
}
|
|
return (*livekit.RoomConfiguration)(c.RoomConfig)
|
|
}
|
|
|
|
func (c *ClaimGrants) Clone() *ClaimGrants {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
clone := *c
|
|
clone.Video = c.Video.Clone()
|
|
clone.SIP = c.SIP.Clone()
|
|
clone.Agent = c.Agent.Clone()
|
|
clone.Inference = c.Inference.Clone()
|
|
clone.Observability = c.Observability.Clone()
|
|
clone.Attributes = maps.Clone(c.Attributes)
|
|
clone.RoomConfig = c.RoomConfig.Clone()
|
|
if len(c.KindDetails) > 0 {
|
|
clone.KindDetails = append([]string{}, c.KindDetails...)
|
|
}
|
|
|
|
return &clone
|
|
}
|
|
|
|
func (c *ClaimGrants) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
|
|
e.AddString("Identity", c.Identity)
|
|
e.AddString("Kind", c.Kind)
|
|
zap.Strings("KindDetails", c.KindDetails).AddTo(e)
|
|
e.AddObject("Video", c.Video)
|
|
e.AddObject("SIP", c.SIP)
|
|
e.AddObject("Agent", c.Agent)
|
|
e.AddObject("Inference", c.Inference)
|
|
e.AddObject("Observability", c.Observability)
|
|
e.AddObject("RoomConfig", logger.Proto((*livekit.RoomConfiguration)(c.RoomConfig)))
|
|
e.AddString("RoomPreset", c.RoomPreset)
|
|
return nil
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
|
|
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"`
|
|
|
|
// 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
|
|
Agent bool `json:"agent,omitempty"`
|
|
|
|
// if a participant can subscribe to metrics
|
|
CanSubscribeMetrics *bool `json:"canSubscribeMetrics,omitempty"`
|
|
|
|
// destination room which this participant can forward to
|
|
DestinationRoom string `json:"destinationRoom,omitempty"`
|
|
}
|
|
|
|
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) SetCanSubscribeMetrics(val bool) {
|
|
v.CanSubscribeMetrics = &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) GetCanSubscribeMetrics() bool {
|
|
if v.CanSubscribeMetrics == nil {
|
|
return false
|
|
}
|
|
return *v.CanSubscribeMetrics
|
|
}
|
|
|
|
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
|
|
}
|
|
if v.GetCanSubscribeMetrics() != permission.CanSubscribeMetrics {
|
|
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
|
|
v.SetCanSubscribeMetrics(permission.CanSubscribeMetrics)
|
|
}
|
|
|
|
func (v *VideoGrant) ToPermission() *livekit.ParticipantPermission {
|
|
return &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,
|
|
CanSubscribeMetrics: v.GetCanSubscribeMetrics(),
|
|
}
|
|
}
|
|
|
|
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 (v *VideoGrant) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
|
|
logBoolPtr := func(prop string, val *bool) {
|
|
if val == nil {
|
|
e.AddString(prop, "not-set")
|
|
} else {
|
|
e.AddBool(prop, *val)
|
|
}
|
|
}
|
|
|
|
logBoolPtr("RoomCreate", &v.RoomCreate)
|
|
logBoolPtr("RoomList", &v.RoomList)
|
|
logBoolPtr("RoomRecord", &v.RoomRecord)
|
|
|
|
logBoolPtr("RoomAdmin", &v.RoomAdmin)
|
|
logBoolPtr("RoomJoin", &v.RoomJoin)
|
|
e.AddString("Room", v.Room)
|
|
|
|
logBoolPtr("CanPublish", v.CanPublish)
|
|
logBoolPtr("CanSubscribe", v.CanSubscribe)
|
|
logBoolPtr("CanPublishData", v.CanPublishData)
|
|
e.AddArray("CanPublishSources", logger.StringSlice(v.CanPublishSources))
|
|
logBoolPtr("CanUpdateOwnMetadata", v.CanUpdateOwnMetadata)
|
|
|
|
logBoolPtr("IngressAdmin", &v.IngressAdmin)
|
|
|
|
logBoolPtr("Hidden", &v.Hidden)
|
|
logBoolPtr("Recorder", &v.Recorder)
|
|
logBoolPtr("Agent", &v.Agent)
|
|
|
|
logBoolPtr("CanSubscribeMetrics", v.CanSubscribeMetrics)
|
|
e.AddString("DestinationRoom", v.DestinationRoom)
|
|
return nil
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
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"`
|
|
}
|
|
|
|
func (s *SIPGrant) Clone() *SIPGrant {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
clone := *s
|
|
|
|
return &clone
|
|
}
|
|
|
|
func (s *SIPGrant) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
e.AddBool("Admin", s.Admin)
|
|
e.AddBool("Call", s.Call)
|
|
return nil
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
type AgentGrant struct {
|
|
// Admin grants to create/update/delete Cloud Agents.
|
|
Admin bool `json:"admin,omitempty"`
|
|
}
|
|
|
|
func (s *AgentGrant) Clone() *AgentGrant {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
clone := *s
|
|
|
|
return &clone
|
|
}
|
|
|
|
func (s *AgentGrant) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
e.AddBool("Admin", s.Admin)
|
|
return nil
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
type InferenceGrant struct {
|
|
// Perform grants to all inference features (LLM, STT, TTS)
|
|
Perform bool `json:"perform,omitempty"`
|
|
}
|
|
|
|
func (s *InferenceGrant) Clone() *InferenceGrant {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
clone := *s
|
|
|
|
return &clone
|
|
}
|
|
|
|
func (s *InferenceGrant) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
e.AddBool("Perform", s.Perform)
|
|
return nil
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
type ObservabilityGrant struct {
|
|
// Write grants to publish observability data
|
|
Write bool `json:"write,omitempty"`
|
|
}
|
|
|
|
func (s *ObservabilityGrant) Clone() *ObservabilityGrant {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
clone := *s
|
|
|
|
return &clone
|
|
}
|
|
|
|
func (s *ObservabilityGrant) MarshalLogObject(e zapcore.ObjectEncoder) error {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
|
|
e.AddBool("Write", s.Write)
|
|
return nil
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
func sourceToString(source livekit.TrackSource) string {
|
|
return strings.ToLower(source.String())
|
|
}
|
|
|
|
func sourceToProto(sourceStr string) livekit.TrackSource {
|
|
if val, ok := livekit.TrackSource_value[strings.ToUpper(sourceStr)]; ok {
|
|
return livekit.TrackSource(val)
|
|
}
|
|
return livekit.TrackSource_UNKNOWN
|
|
}
|
|
|
|
func kindFromProto(source livekit.ParticipantInfo_Kind) string {
|
|
return strings.ToLower(source.String())
|
|
}
|
|
|
|
func kindToProto(sourceStr string) livekit.ParticipantInfo_Kind {
|
|
if val, ok := livekit.ParticipantInfo_Kind_value[strings.ToUpper(sourceStr)]; ok {
|
|
return livekit.ParticipantInfo_Kind(val)
|
|
}
|
|
return livekit.ParticipantInfo_STANDARD
|
|
}
|
|
|
|
func kindDetailsFromProto(details []livekit.ParticipantInfo_KindDetail) []string {
|
|
result := make([]string, 0, len(details))
|
|
for _, d := range details {
|
|
result = append(result, strings.ToLower(d.String()))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func kindDetailsToProto(details []string) []livekit.ParticipantInfo_KindDetail {
|
|
result := make([]livekit.ParticipantInfo_KindDetail, 0, len(details))
|
|
for _, d := range details {
|
|
if val, ok := livekit.ParticipantInfo_KindDetail_value[strings.ToUpper(d)]; ok {
|
|
result = append(result, livekit.ParticipantInfo_KindDetail(val))
|
|
}
|
|
}
|
|
return result
|
|
}
|