fc644ecd6e
Reemplaza el scaffold del echobot por la plataforma completa de bots traida desde ~/DataProyects/Github/agents_and_robots tras la operacion Matrix-out: los bots ya no hablan por Matrix sino por el bus unibus (modelo todo-rooms + E2E via shell/transportunibus sobre github.com/enmanuel/unibus/pkg/client). - go.mod: replace de unibus -> ../unibus y de fn-registry -> ../../../.. (paths relativos reajustados a la nueva ubicacion dentro de fn_registry). - app.md: bump a 0.2.0, descripcion + arquitectura + comandos + gotchas reales. - modulo Go conservado como github.com/enmanuel/agents (sin reescribir imports). agents_and_robots queda archivado como museo de la era Matrix.
104 lines
2.3 KiB
Go
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
|
|
}
|