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 }