diff options
author | Alex Auvolat <alex@adnab.me> | 2020-01-26 18:42:04 +0100 |
---|---|---|
committer | Alex Auvolat <alex@adnab.me> | 2020-01-26 18:42:04 +0100 |
commit | 82402749e6edecc46d06c06b867fe39b8a3968d6 (patch) | |
tree | c2418f6c01f22309d8fcf97faa9c7c1f4510c325 /acl.go | |
parent | 2ad9bce75cdad01089a02518de4b8137f4a3ffe3 (diff) | |
download | bottin-82402749e6edecc46d06c06b867fe39b8a3968d6.tar.gz bottin-82402749e6edecc46d06c06b867fe39b8a3968d6.zip |
First ACL implementation
Diffstat (limited to 'acl.go')
-rw-r--r-- | acl.go | 131 |
1 files changed, 131 insertions, 0 deletions
@@ -0,0 +1,131 @@ +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) + } 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 +} |