package security import "github.com/enmanuel/agents/pkg/acl" // ResolveACL computes the ACL for a given agentID from a SecurityPolicy. // // Resolution rules: // - An AgentPolicy applies to agentID if its AgentGroup field is: // (a) a group name in p.AgentGroups whose Agents list contains agentID or "*", or // (b) directly equal to agentID (individual assignment without a named group). // - If multiple AgentPolicies apply, their permissions are accumulated (union). // - For each Permission, the UserGroup is resolved to members in p.UserGroups. // A UserGroup with Members = ["*"] grants the actions to all users. // - If no policy applies, an empty ACL is returned (open access per acl semantics). func ResolveACL(agentID string, p SecurityPolicy) acl.ACL { // Build a lookup: group name → members. userGroupMembers := make(map[string][]string, len(p.UserGroups)) for _, ug := range p.UserGroups { userGroupMembers[ug.Name] = ug.Members } // Collect all roles from every AgentPolicy that applies to this agent. var roles []acl.Role for _, ap := range p.Policies { if !agentPolicyApplies(agentID, ap.AgentGroup, p.AgentGroups) { continue } for _, perm := range ap.Permissions { members := resolveMembers(perm.UserGroup, userGroupMembers) roles = append(roles, acl.Role{ Name: perm.UserGroup, Users: members, Actions: perm.Actions, }) } } return acl.FromRoles(roles) } // agentPolicyApplies returns true if an AgentPolicy with the given agentGroupRef // should apply to agentID. func agentPolicyApplies(agentID, agentGroupRef string, groups []AgentGroup) bool { // Try to find a named group first. for _, ag := range groups { if ag.Name != agentGroupRef { continue } for _, a := range ag.Agents { if a == "*" || a == agentID { return true } } return false // group found but agent not in it } // No matching group found — treat agentGroupRef as a direct agent ID. return agentGroupRef == agentID } // resolveMembers returns the member list for a user group name. // If the group is not defined, it falls back to treating the name as a literal user ID. func resolveMembers(userGroupName string, lookup map[string][]string) []string { if members, ok := lookup[userGroupName]; ok { return members } // Fallback: treat as a direct user ID. return []string{userGroupName} }