aboutsummaryrefslogtreecommitdiff
path: root/acl.go
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2020-01-26 18:42:04 +0100
committerAlex Auvolat <alex@adnab.me>2020-01-26 18:42:04 +0100
commit82402749e6edecc46d06c06b867fe39b8a3968d6 (patch)
treec2418f6c01f22309d8fcf97faa9c7c1f4510c325 /acl.go
parent2ad9bce75cdad01089a02518de4b8137f4a3ffe3 (diff)
downloadbottin-82402749e6edecc46d06c06b867fe39b8a3968d6.tar.gz
bottin-82402749e6edecc46d06c06b867fe39b8a3968d6.zip
First ACL implementation
Diffstat (limited to 'acl.go')
-rw-r--r--acl.go131
1 files changed, 131 insertions, 0 deletions
diff --git a/acl.go b/acl.go
new file mode 100644
index 0000000..3607d9c
--- /dev/null
+++ b/acl.go
@@ -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
+}