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 }