github.com/livekit/protocol@v1.39.3/auth/grants_test.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  	"reflect"
    19  	"strconv"
    20  	"testing"
    21  
    22  	"github.com/stretchr/testify/require"
    23  
    24  	"github.com/livekit/protocol/livekit"
    25  )
    26  
    27  func TestGrants(t *testing.T) {
    28  	t.Parallel()
    29  
    30  	t.Run("clone default grant", func(t *testing.T) {
    31  		grants := &ClaimGrants{}
    32  		clone := grants.Clone()
    33  		require.NotSame(t, grants, clone)
    34  		require.Same(t, grants.Video, clone.Video)
    35  		require.True(t, reflect.DeepEqual(grants, clone))
    36  		require.True(t, reflect.DeepEqual(grants.Video, clone.Video))
    37  	})
    38  
    39  	t.Run("clone nil video", func(t *testing.T) {
    40  		grants := &ClaimGrants{
    41  			Identity: "identity",
    42  			Name:     "name",
    43  			Sha256:   "sha256",
    44  			Metadata: "metadata",
    45  		}
    46  		clone := grants.Clone()
    47  		require.NotSame(t, grants, clone)
    48  		require.Same(t, grants.Video, clone.Video)
    49  		require.True(t, reflect.DeepEqual(grants, clone))
    50  		require.True(t, reflect.DeepEqual(grants.Video, clone.Video))
    51  	})
    52  
    53  	t.Run("clone with video", func(t *testing.T) {
    54  		tr := true
    55  		fa := false
    56  		video := &VideoGrant{
    57  			RoomCreate:          true,
    58  			RoomList:            false,
    59  			RoomRecord:          true,
    60  			RoomAdmin:           false,
    61  			RoomJoin:            true,
    62  			Room:                "room",
    63  			CanPublish:          &tr,
    64  			CanSubscribe:        &fa,
    65  			CanPublishData:      nil,
    66  			Hidden:              true,
    67  			Recorder:            false,
    68  			CanSubscribeMetrics: &tr,
    69  		}
    70  		grants := &ClaimGrants{
    71  			Identity: "identity",
    72  			Name:     "name",
    73  			Kind:     "kind",
    74  			Video:    video,
    75  			Sha256:   "sha256",
    76  			Metadata: "metadata",
    77  		}
    78  		clone := grants.Clone()
    79  		require.NotSame(t, grants, clone)
    80  		require.NotSame(t, grants.Video, clone.Video)
    81  		require.NotSame(t, grants.Video.CanPublish, clone.Video.CanPublish)
    82  		require.NotSame(t, grants.Video.CanSubscribe, clone.Video.CanSubscribe)
    83  		require.Same(t, grants.Video.CanPublishData, clone.Video.CanPublishData)
    84  		require.True(t, reflect.DeepEqual(grants, clone))
    85  		require.True(t, reflect.DeepEqual(grants.Video, clone.Video))
    86  	})
    87  }
    88  
    89  func TestParticipantKind(t *testing.T) {
    90  	const kindMin, kindMax = livekit.ParticipantInfo_STANDARD, livekit.ParticipantInfo_AGENT
    91  	for k := kindMin; k <= kindMax; k++ {
    92  		k := k
    93  		t.Run(k.String(), func(t *testing.T) {
    94  			require.Equal(t, k, kindToProto(kindFromProto(k)))
    95  		})
    96  	}
    97  	const kindNext = kindMax + 1
    98  	if _, err := strconv.Atoi(kindNext.String()); err != nil {
    99  		t.Errorf("Please update kindMax to match protobuf. Missing value: %s", kindNext)
   100  	}
   101  }
   102  
   103  func TestRoomConfiguration_CheckCredentials(t *testing.T) {
   104  	t.Parallel()
   105  
   106  	t.Run("nil egress returns nil", func(t *testing.T) {
   107  		config := &RoomConfiguration{}
   108  		require.NoError(t, config.CheckCredentials())
   109  	})
   110  
   111  	t.Run("empty egress returns nil", func(t *testing.T) {
   112  		config := &RoomConfiguration{
   113  			Egress: &livekit.RoomEgress{},
   114  		}
   115  		require.NoError(t, config.CheckCredentials())
   116  	})
   117  
   118  	t.Run("participant file output with S3 secret fails", func(t *testing.T) {
   119  		config := &RoomConfiguration{
   120  			Egress: &livekit.RoomEgress{
   121  				Participant: &livekit.AutoParticipantEgress{
   122  					FileOutputs: []*livekit.EncodedFileOutput{
   123  						{
   124  							Output: &livekit.EncodedFileOutput_S3{
   125  								S3: &livekit.S3Upload{
   126  									AccessKey: "access",
   127  									Secret:    "secret", // This should trigger error
   128  									Bucket:    "bucket",
   129  								},
   130  							},
   131  						},
   132  					},
   133  				},
   134  			},
   135  		}
   136  		require.ErrorIs(t, config.CheckCredentials(), ErrSensitiveCredentials)
   137  	})
   138  
   139  	t.Run("participant file output with S3 but no secret passes", func(t *testing.T) {
   140  		config := &RoomConfiguration{
   141  			Egress: &livekit.RoomEgress{
   142  				Participant: &livekit.AutoParticipantEgress{
   143  					FileOutputs: []*livekit.EncodedFileOutput{
   144  						{
   145  							Output: &livekit.EncodedFileOutput_S3{
   146  								S3: &livekit.S3Upload{
   147  									AccessKey: "access",
   148  									Secret:    "", // No secret
   149  									Bucket:    "bucket",
   150  									Region:    "us-west-2",
   151  								},
   152  							},
   153  						},
   154  					},
   155  				},
   156  			},
   157  		}
   158  		require.NoError(t, config.CheckCredentials())
   159  	})
   160  
   161  	t.Run("participant segment output with GCP credentials fails", func(t *testing.T) {
   162  		config := &RoomConfiguration{
   163  			Egress: &livekit.RoomEgress{
   164  				Participant: &livekit.AutoParticipantEgress{
   165  					SegmentOutputs: []*livekit.SegmentedFileOutput{
   166  						{
   167  							Output: &livekit.SegmentedFileOutput_Gcp{
   168  								Gcp: &livekit.GCPUpload{
   169  									Credentials: "credentials", // This should trigger error
   170  									Bucket:      "bucket",
   171  								},
   172  							},
   173  						},
   174  					},
   175  				},
   176  			},
   177  		}
   178  		require.ErrorIs(t, config.CheckCredentials(), ErrSensitiveCredentials)
   179  	})
   180  
   181  	t.Run("participant segment output with GCP but no credentials passes", func(t *testing.T) {
   182  		config := &RoomConfiguration{
   183  			Egress: &livekit.RoomEgress{
   184  				Participant: &livekit.AutoParticipantEgress{
   185  					SegmentOutputs: []*livekit.SegmentedFileOutput{
   186  						{
   187  							Output: &livekit.SegmentedFileOutput_Gcp{
   188  								Gcp: &livekit.GCPUpload{
   189  									Credentials: "", // No credentials
   190  									Bucket:      "bucket",
   191  								},
   192  							},
   193  						},
   194  					},
   195  				},
   196  			},
   197  		}
   198  		require.NoError(t, config.CheckCredentials())
   199  	})
   200  
   201  	t.Run("room file output with Azure account key fails", func(t *testing.T) {
   202  		config := &RoomConfiguration{
   203  			Egress: &livekit.RoomEgress{
   204  				Room: &livekit.RoomCompositeEgressRequest{
   205  					FileOutputs: []*livekit.EncodedFileOutput{
   206  						{
   207  							Output: &livekit.EncodedFileOutput_Azure{
   208  								Azure: &livekit.AzureBlobUpload{
   209  									AccountName:   "account",
   210  									AccountKey:    "key", // This should trigger error
   211  									ContainerName: "container",
   212  								},
   213  							},
   214  						},
   215  					},
   216  				},
   217  			},
   218  		}
   219  		require.ErrorIs(t, config.CheckCredentials(), ErrSensitiveCredentials)
   220  	})
   221  
   222  	t.Run("room segment output with AliOSS secret fails", func(t *testing.T) {
   223  		config := &RoomConfiguration{
   224  			Egress: &livekit.RoomEgress{
   225  				Room: &livekit.RoomCompositeEgressRequest{
   226  					SegmentOutputs: []*livekit.SegmentedFileOutput{
   227  						{
   228  							Output: &livekit.SegmentedFileOutput_AliOSS{
   229  								AliOSS: &livekit.AliOSSUpload{
   230  									AccessKey: "access",
   231  									Secret:    "secret", // This should trigger error
   232  									Bucket:    "bucket",
   233  								},
   234  							},
   235  						},
   236  					},
   237  				},
   238  			},
   239  		}
   240  		require.ErrorIs(t, config.CheckCredentials(), ErrSensitiveCredentials)
   241  	})
   242  
   243  	t.Run("room image output with valid config passes", func(t *testing.T) {
   244  		config := &RoomConfiguration{
   245  			Egress: &livekit.RoomEgress{
   246  				Room: &livekit.RoomCompositeEgressRequest{
   247  					ImageOutputs: []*livekit.ImageOutput{
   248  						{
   249  							CaptureInterval: 5,
   250  							Width:           1920,
   251  							Height:          1080,
   252  							Output: &livekit.ImageOutput_S3{
   253  								S3: &livekit.S3Upload{
   254  									AccessKey: "access",
   255  									Secret:    "", // No secret
   256  									Bucket:    "bucket",
   257  								},
   258  							},
   259  						},
   260  					},
   261  				},
   262  			},
   263  		}
   264  		require.NoError(t, config.CheckCredentials())
   265  	})
   266  
   267  	t.Run("room stream outputs always fail", func(t *testing.T) {
   268  		config := &RoomConfiguration{
   269  			Egress: &livekit.RoomEgress{
   270  				Room: &livekit.RoomCompositeEgressRequest{
   271  					StreamOutputs: []*livekit.StreamOutput{
   272  						{
   273  							Protocol: livekit.StreamProtocol_RTMP,
   274  							Urls:     []string{"rtmp://example.com/live"},
   275  						},
   276  					},
   277  				},
   278  			},
   279  		}
   280  		require.ErrorIs(t, config.CheckCredentials(), ErrSensitiveCredentials)
   281  	})
   282  
   283  	t.Run("tracks output with S3 secret fails", func(t *testing.T) {
   284  		config := &RoomConfiguration{
   285  			Egress: &livekit.RoomEgress{
   286  				Tracks: &livekit.AutoTrackEgress{
   287  					Filepath: "output.mp4",
   288  					Output: &livekit.AutoTrackEgress_S3{
   289  						S3: &livekit.S3Upload{
   290  							AccessKey: "access",
   291  							Secret:    "secret", // This should trigger error
   292  							Bucket:    "bucket",
   293  						},
   294  					},
   295  				},
   296  			},
   297  		}
   298  		require.ErrorIs(t, config.CheckCredentials(), ErrSensitiveCredentials)
   299  	})
   300  
   301  	t.Run("tracks output without credentials passes", func(t *testing.T) {
   302  		config := &RoomConfiguration{
   303  			Egress: &livekit.RoomEgress{
   304  				Tracks: &livekit.AutoTrackEgress{
   305  					Filepath: "output.mp4",
   306  					Output: &livekit.AutoTrackEgress_Gcp{
   307  						Gcp: &livekit.GCPUpload{
   308  							Credentials: "", // No credentials
   309  							Bucket:      "bucket",
   310  						},
   311  					},
   312  				},
   313  			},
   314  		}
   315  		require.NoError(t, config.CheckCredentials())
   316  	})
   317  
   318  	t.Run("multiple outputs with mixed credentials", func(t *testing.T) {
   319  		config := &RoomConfiguration{
   320  			Egress: &livekit.RoomEgress{
   321  				Participant: &livekit.AutoParticipantEgress{
   322  					FileOutputs: []*livekit.EncodedFileOutput{
   323  						{
   324  							Output: &livekit.EncodedFileOutput_S3{
   325  								S3: &livekit.S3Upload{
   326  									AccessKey: "access",
   327  									Secret:    "", // No secret - OK
   328  									Bucket:    "bucket1",
   329  								},
   330  							},
   331  						},
   332  						{
   333  							Output: &livekit.EncodedFileOutput_Gcp{
   334  								Gcp: &livekit.GCPUpload{
   335  									Credentials: "credentials", // Has credentials - should fail
   336  									Bucket:      "bucket2",
   337  								},
   338  							},
   339  						},
   340  					},
   341  				},
   342  			},
   343  		}
   344  		require.ErrorIs(t, config.CheckCredentials(), ErrSensitiveCredentials)
   345  	})
   346  
   347  	t.Run("all cloud providers without credentials pass", func(t *testing.T) {
   348  		config := &RoomConfiguration{
   349  			Egress: &livekit.RoomEgress{
   350  				Room: &livekit.RoomCompositeEgressRequest{
   351  					FileOutputs: []*livekit.EncodedFileOutput{
   352  						{
   353  							Output: &livekit.EncodedFileOutput_S3{
   354  								S3: &livekit.S3Upload{
   355  									AccessKey: "access",
   356  									Secret:    "", // No secret
   357  									Bucket:    "s3bucket",
   358  								},
   359  							},
   360  						},
   361  						{
   362  							Output: &livekit.EncodedFileOutput_Gcp{
   363  								Gcp: &livekit.GCPUpload{
   364  									Credentials: "", // No credentials
   365  									Bucket:      "gcpbucket",
   366  								},
   367  							},
   368  						},
   369  						{
   370  							Output: &livekit.EncodedFileOutput_Azure{
   371  								Azure: &livekit.AzureBlobUpload{
   372  									AccountName:   "account",
   373  									AccountKey:    "", // No key
   374  									ContainerName: "container",
   375  								},
   376  							},
   377  						},
   378  						{
   379  							Output: &livekit.EncodedFileOutput_AliOSS{
   380  								AliOSS: &livekit.AliOSSUpload{
   381  									AccessKey: "access",
   382  									Secret:    "", // No secret
   383  									Bucket:    "alibucket",
   384  								},
   385  							},
   386  						},
   387  					},
   388  				},
   389  			},
   390  		}
   391  		require.NoError(t, config.CheckCredentials())
   392  	})
   393  }