141 lines
3 KiB
Go
141 lines
3 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 {
|
|
tmp := strings.Split(s, " ")
|
|
ret := []string{}
|
|
for _, s := range tmp {
|
|
if len(s) > 0 {
|
|
ret = append(ret, s)
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
matchTarget := match(entry.target, target)
|
|
if !matchTarget && len(target) >= len(login.user) {
|
|
start := len(target) - len(login.user)
|
|
if target[start:] == login.user {
|
|
matchTarget = match(entry.target, target[:start]+"SELF")
|
|
}
|
|
}
|
|
if !matchTarget {
|
|
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
|
|
}
|