aboutsummaryrefslogtreecommitdiff
path: root/acl.go
blob: 8cb433f0a741eddcd8b4b45e13579505434e2b09 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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[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
}