Files
agents_and_robots/pkg/acl/acl.go
T
egutierrez 386e4d3dcb feat: añadir paquete pkg/acl para control de acceso puro
Nuevo paquete puro (sin I/O) que implementa RBAC basado en roles.
Incluye: ACL, Role, RoleDef, FromMap constructor, CanDo para verificar
permisos, RoleFor para resolver rol de un usuario, y soporte para
wildcards tanto en usuarios ("*") como en acciones ("command:*").
Incluye tests completos cubriendo: ACL vacío, admin wildcard, acciones
específicas, prefix wildcards, prioridad exacto>wildcard, y múltiples
roles por usuario.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:59:00 +00:00

104 lines
2.3 KiB
Go

// Package acl provides pure access control types and functions.
// No I/O, no side effects — only data transformations.
package acl
// Role represents a named role with its users and allowed actions.
type Role struct {
Name string
Users []string // Matrix user IDs; "*" means everyone
Actions []string // allowed actions; "*" means all
}
// ACL is the resolved access control list.
type ACL struct {
roles []Role
}
// Empty returns true if no roles are configured (ACL is inactive).
func (a ACL) Empty() bool {
return len(a.roles) == 0
}
// RoleFor returns the name of the first role that matches the given userID.
// Specific user entries are checked before wildcard ("*") entries.
// Returns "" if no role matches.
func (a ACL) RoleFor(userID string) string {
// First pass: exact match
for _, r := range a.roles {
for _, u := range r.Users {
if u == userID {
return r.Name
}
}
}
// Second pass: wildcard
for _, r := range a.roles {
for _, u := range r.Users {
if u == "*" {
return r.Name
}
}
}
return ""
}
// CanDo checks if a userID is allowed to perform an action.
// If no roles are defined, returns true (open access).
// If roles exist but the user has none, returns false.
func (a ACL) CanDo(userID string, action string) bool {
if a.Empty() {
return true
}
for _, r := range a.roles {
if !matchesUser(r.Users, userID) {
continue
}
if matchesAction(r.Actions, action) {
return true
}
}
return false
}
// AllowedUsers returns the deduplicated list of all explicit user IDs
// (excluding "*") that have at least one role.
func (a ACL) AllowedUsers() []string {
seen := make(map[string]bool)
var result []string
for _, r := range a.roles {
for _, u := range r.Users {
if u != "*" && !seen[u] {
seen[u] = true
result = append(result, u)
}
}
}
return result
}
func matchesUser(users []string, userID string) bool {
for _, u := range users {
if u == userID || u == "*" {
return true
}
}
return false
}
func matchesAction(actions []string, action string) bool {
for _, a := range actions {
if a == "*" || a == action {
return true
}
// Wildcard prefix: "command:*" matches "command:deploy"
if len(a) > 1 && a[len(a)-1] == '*' {
prefix := a[:len(a)-1]
if len(action) >= len(prefix) && action[:len(prefix)] == prefix {
return true
}
}
}
return false
}