forked from Deuxfleurs/bottin
131 lines
2.8 KiB
Go
131 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
)
|
|
|
|
type Login struct {
|
|
user string
|
|
groups []string
|
|
}
|
|
|
|
type ACL []ACLEntry
|
|
|
|
type ACLEntry struct {
|
|
// The authenticated user (or ANONYMOUS if not authenticated) must match this string
|
|
user string
|
|
// For each of this groups, the authenticated user must belong to one group that matches
|
|
reqGroups []string
|
|
// The action requested must match one of these strings
|
|
actions []string
|
|
// The requested target must match this string. The special word SELF is replaced in the pattern by the user's dn before matching
|
|
target string
|
|
// All attributes requested must match one of these patterns
|
|
attributes []string
|
|
// All attributes requested must not match any of these patterns
|
|
exclAttributes []string
|
|
}
|
|
|
|
func splitNoEmpty(s string) []string {
|
|
if len(s) == 0 {
|
|
return []string{}
|
|
}
|
|
return strings.Split(s, " ")
|
|
}
|
|
|
|
func ParseACL(def []string) (ACL, error) {
|
|
acl := []ACLEntry{}
|
|
for _, item := range def {
|
|
parts := strings.Split(item, ":")
|
|
if len(parts) != 5 {
|
|
return nil, fmt.Errorf("Invalid ACL entry: %s", item)
|
|
}
|
|
attr, exclAttr := []string{}, []string{}
|
|
for _, s := range splitNoEmpty(parts[4]) {
|
|
if s[0] == '!' {
|
|
exclAttr = append(exclAttr, s[1:])
|
|
} else {
|
|
attr = append(attr, s)
|
|
}
|
|
}
|
|
item_def := ACLEntry{
|
|
user: parts[0],
|
|
reqGroups: splitNoEmpty(parts[1]),
|
|
actions: splitNoEmpty(parts[2]),
|
|
target: parts[3],
|
|
attributes: attr,
|
|
exclAttributes: exclAttr,
|
|
}
|
|
acl = append(acl, item_def)
|
|
}
|
|
return acl, nil
|
|
}
|
|
|
|
func (acl ACL) Check(login *Login, action string, target string, attributes []string) bool {
|
|
for _, item := range acl {
|
|
if item.Check(login, action, target, attributes) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (entry *ACLEntry) Check(login *Login, action string, target string, attributes []string) bool {
|
|
if !match(entry.user, login.user) {
|
|
return false
|
|
}
|
|
|
|
for _, grp := range entry.reqGroups {
|
|
if !matchAny(grp, login.groups) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
rule_target_with_self := strings.ReplaceAll(entry.target, "SELF", login.user)
|
|
if !match(rule_target_with_self, target) {
|
|
return false
|
|
}
|
|
|
|
if !anyMatch(entry.actions, action) {
|
|
return false
|
|
}
|
|
|
|
for _, attrib := range attributes {
|
|
if !anyMatch(entry.attributes, attrib) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
for _, exclAttr := range entry.exclAttributes {
|
|
if matchAny(exclAttr, attributes) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func match(pattern string, val string) bool {
|
|
rv, err := path.Match(strings.ToLower(pattern), strings.ToLower(val))
|
|
return err == nil && rv
|
|
}
|
|
|
|
func matchAny(pattern string, vals []string) bool {
|
|
for _, val := range vals {
|
|
if match(pattern, val) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func anyMatch(patterns []string, val string) bool {
|
|
for _, pattern := range patterns {
|
|
if match(pattern, val) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|