Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 41 additions & 6 deletions auth/grants.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,24 +553,59 @@ func (s *SIPGrant) MarshalLogObject(e zapcore.ObjectEncoder) error {
type AgentGrant struct {
// Admin grants to create/update/delete Cloud Agents.
Admin bool `json:"admin,omitempty"`

// Endpoints specifies which agent endpoints the holder can use.
// When empty, no endpoint access is granted.
// Use "*" to grant access to all endpoints.
Endpoints []string `json:"endpoints,omitempty"`
}

func (s *AgentGrant) Clone() *AgentGrant {
if s == nil {
func (a *AgentGrant) SetEndpoints(endpoints []string) {
a.Endpoints = endpoints
}

func (a *AgentGrant) GetEndpoints() []string {
return a.Endpoints
}

// CanUseEndpoint returns true if the grant allows using the specified endpoint.
// The endpoint must be explicitly listed in Endpoints, or Endpoints must contain
// "*" for wildcard access. Note: Admin is a separate permission for Cloud Agents
// CRUD operations and does not grant endpoint access.
func (a *AgentGrant) CanUseEndpoint(endpoint string) bool {
if a == nil {
return false
}
for _, e := range a.Endpoints {
if e == "*" || e == endpoint {
return true
}
}
return false
}

func (a *AgentGrant) Clone() *AgentGrant {
if a == nil {
return nil
}

clone := *s
clone := *a

if a.Endpoints != nil {
clone.Endpoints = make([]string, len(a.Endpoints))
copy(clone.Endpoints, a.Endpoints)
}

return &clone
}

func (s *AgentGrant) MarshalLogObject(e zapcore.ObjectEncoder) error {
if s == nil {
func (a *AgentGrant) MarshalLogObject(e zapcore.ObjectEncoder) error {
if a == nil {
return nil
}

e.AddBool("Admin", s.Admin)
e.AddBool("Admin", a.Admin)
e.AddArray("Endpoints", logger.StringSlice(a.Endpoints))
return nil
}

Expand Down
79 changes: 79 additions & 0 deletions auth/grants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,27 @@ func TestGrants(t *testing.T) {
require.True(t, reflect.DeepEqual(grants.Agent, clone.Agent))
})

t.Run("clone with Agent endpoints", func(t *testing.T) {
agent := &AgentGrant{
Admin: false,
Endpoints: []string{"customer-service", "my-agent"},
}
grants := &ClaimGrants{
Identity: "identity",
Agent: agent,
}
clone := grants.Clone()
require.NotSame(t, grants, clone)
require.NotSame(t, grants.Agent, clone.Agent)
require.NotSame(t, &grants.Agent.Endpoints, &clone.Agent.Endpoints)
require.Equal(t, grants.Agent.Endpoints, clone.Agent.Endpoints)
require.True(t, reflect.DeepEqual(grants, clone))

// Modifying clone should not affect original
clone.Agent.Endpoints[0] = "modified"
require.NotEqual(t, grants.Agent.Endpoints[0], clone.Agent.Endpoints[0])
})

t.Run("clone with Inference", func(t *testing.T) {
inference := &InferenceGrant{
Perform: true,
Expand All @@ -159,6 +180,64 @@ func TestGrants(t *testing.T) {
})
}

func TestAgentGrantCanUseEndpoint(t *testing.T) {
t.Parallel()

t.Run("nil grant returns false", func(t *testing.T) {
var agent *AgentGrant
require.False(t, agent.CanUseEndpoint("any-endpoint"))
})

t.Run("admin alone does not grant endpoint access", func(t *testing.T) {
agent := &AgentGrant{Admin: true}
require.False(t, agent.CanUseEndpoint("customer-service"))
require.False(t, agent.CanUseEndpoint("my-agent"))
})

t.Run("empty endpoints denies access", func(t *testing.T) {
agent := &AgentGrant{Admin: false}
require.False(t, agent.CanUseEndpoint("customer-service"))
})

t.Run("explicit endpoint grants access", func(t *testing.T) {
agent := &AgentGrant{
Admin: false,
Endpoints: []string{"customer-service", "my-agent"},
}
require.True(t, agent.CanUseEndpoint("customer-service"))
require.True(t, agent.CanUseEndpoint("my-agent"))
require.False(t, agent.CanUseEndpoint("other-agent"))
})

t.Run("wildcard grants access to all", func(t *testing.T) {
agent := &AgentGrant{
Admin: false,
Endpoints: []string{"*"},
}
require.True(t, agent.CanUseEndpoint("customer-service"))
require.True(t, agent.CanUseEndpoint("my-agent"))
require.True(t, agent.CanUseEndpoint("any-endpoint"))
})

t.Run("wildcard with other endpoints", func(t *testing.T) {
agent := &AgentGrant{
Admin: false,
Endpoints: []string{"specific-agent", "*"},
}
require.True(t, agent.CanUseEndpoint("specific-agent"))
require.True(t, agent.CanUseEndpoint("any-other-agent"))
})

t.Run("admin with endpoints grants endpoint access", func(t *testing.T) {
agent := &AgentGrant{
Admin: true,
Endpoints: []string{"customer-service"},
}
require.True(t, agent.CanUseEndpoint("customer-service"))
require.False(t, agent.CanUseEndpoint("other-agent"))
})
}

func TestParticipantKind(t *testing.T) {
const kindMin, kindMax = livekit.ParticipantInfo_STANDARD, livekit.ParticipantInfo_AGENT
for k := kindMin; k <= kindMax; k++ {
Expand Down
Loading