aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--acl/acl.go134
-rw-r--r--acl/acl_test.go105
2 files changed, 239 insertions, 0 deletions
diff --git a/acl/acl.go b/acl/acl.go
new file mode 100644
index 0000000..544edde
--- /dev/null
+++ b/acl/acl.go
@@ -0,0 +1,134 @@
+package acl
+
+import (
+ "fmt"
+ "os"
+ "sort"
+ "strings"
+)
+
+type Perm uint
+
+const (
+ PermR = 4
+ PermW = 2
+ PermX = 1
+)
+
+func (p Perm) String() string {
+ s := []byte{'r', 'w', 'x'}
+ if p&PermR == 0 {
+ s[0] = '-'
+ }
+ if p&PermW == 0 {
+ s[1] = '-'
+ }
+ if p&PermX == 0 {
+ s[2] = '-'
+ }
+ return string(s)
+}
+
+type QualifiedPerms map[string]Perm
+
+type ACL struct {
+ User, Group, Other, Mask QualifiedPerms
+}
+
+func (acl *ACL) Init() {
+ acl.User = make(QualifiedPerms)
+ acl.Group = make(QualifiedPerms)
+ acl.Other = make(QualifiedPerms)
+ acl.Mask = make(QualifiedPerms)
+}
+
+func ACLFromUnixPerms(perms os.FileMode) (acl ACL) {
+ perms &= os.ModePerm
+
+ acl.User = QualifiedPerms{"": Perm(7 & (perms >> 6))}
+ acl.Group = QualifiedPerms{"": Perm(7 & (perms >> 3))}
+ acl.Other = QualifiedPerms{"": Perm(7 & perms)}
+
+ return acl
+}
+
+func (acl ACL) ToUnixPerms() (m os.FileMode) {
+ if p, ok := acl.User[""]; ok {
+ m |= os.FileMode(p << 6)
+ }
+ if p, ok := acl.Group[""]; ok {
+ m |= os.FileMode(p << 3)
+ }
+ if p, ok := acl.Other[""]; ok {
+ m |= os.FileMode(p)
+ }
+ return
+}
+
+func stringifyAclCategory(parts []string, t string, perms QualifiedPerms) []string {
+ keys := make([]string, 0, len(perms))
+ for k := range perms {
+ keys = append(keys, k)
+ }
+
+ sort.Strings(keys)
+ for _, k := range keys {
+ parts = append(parts, t+":"+k+":"+perms[k].String())
+ }
+ return parts
+}
+
+func (acl ACL) String() string {
+ parts := []string{}
+ parts = stringifyAclCategory(parts, "u", acl.User)
+ parts = stringifyAclCategory(parts, "g", acl.Group)
+ parts = stringifyAclCategory(parts, "o", acl.Other)
+ parts = stringifyAclCategory(parts, "m", acl.Mask)
+
+ return strings.Join(parts, ",")
+}
+
+// ParseACL parses a POSIX ACL. Only the short text form is supported
+func ParseACL(s string) (ACL, error) {
+ acl := ACL{}
+ acl.Init()
+
+ entries := strings.Split(s, ",")
+ for i, entry := range entries {
+ entry = strings.TrimSpace(entry)
+
+ parts := strings.Split(entry, ":")
+ if len(parts) != 3 {
+ return acl, fmt.Errorf("invalid acl: Entry #%d has more or less than 3 ':' separated parts", i+1)
+ }
+
+ var perms *QualifiedPerms = nil
+
+ switch parts[0] {
+ case "u", "user":
+ perms = &acl.User
+ case "g", "group":
+ perms = &acl.Group
+ case "o", "other":
+ perms = &acl.Other
+ case "m", "mask":
+ perms = &acl.Mask
+ default:
+ return acl, fmt.Errorf("invalid acl: Entry #%d has unknown tag \"%s\"", i+1, parts[0])
+ }
+
+ perm := Perm(0)
+ if strings.Contains(parts[2], "r") {
+ perm |= PermR
+ }
+ if strings.Contains(parts[2], "w") {
+ perm |= PermW
+ }
+ if strings.Contains(parts[2], "x") {
+ perm |= PermX
+ }
+ (*perms)[parts[1]] = perm
+ }
+
+ return acl, nil
+}
diff --git a/acl/acl_test.go b/acl/acl_test.go
new file mode 100644
index 0000000..46f8ada
--- /dev/null
+++ b/acl/acl_test.go
@@ -0,0 +1,105 @@
+package acl
+
+import (
+ "os"
+ "testing"
+)
+
+func checkPerm(t *testing.T, cat rune, perms QualifiedPerms, k string, want Perm) {
+ have, ok := perms[k]
+ if ok {
+ if have != want {
+ t.Errorf("Perm %c:%s:%s != %c:%s:%s", cat, k, have, cat, k, want)
+ }
+ } else {
+ t.Errorf("Perm %c:%s: not set", cat, k)
+ }
+}
+
+func TestFromUnix(t *testing.T) {
+ acl := ACLFromUnixPerms(0752)
+ if len(acl.User) != 1 {
+ t.Errorf("Expected exactly 1 user perm, got %d", len(acl.User))
+ }
+ if len(acl.Group) != 1 {
+ t.Errorf("Expected exactly 1 group perm, got %d", len(acl.Group))
+ }
+ if len(acl.Other) != 1 {
+ t.Errorf("Expected exactly 1 other perm, got %d", len(acl.Other))
+ }
+ if len(acl.Mask) != 0 {
+ t.Errorf("Expected exactly 0 mask perm, got %d", len(acl.Mask))
+ }
+
+ checkPerm(t, 'u', acl.User, "", PermR|PermW|PermX)
+ checkPerm(t, 'g', acl.Group, "", PermR|PermX)
+ checkPerm(t, 'o', acl.Other, "", PermW)
+}
+
+func TestToUnix(t *testing.T) {
+ acl := ACL{}
+ acl.Init()
+
+ acl.User[""] = PermR | PermW | PermX
+ acl.Group[""] = PermR | PermX
+ acl.Other[""] = PermR
+
+ want := os.FileMode(0754)
+ have := acl.ToUnixPerms()
+ if have != want {
+ t.Errorf("Unexpected ToUnixPerms result: have 0%o, want 0%o", have, want)
+ }
+}
+
+func TestStringify(t *testing.T) {
+ acl := ACL{}
+ acl.Init()
+
+ acl.User[""] = PermR | PermW | PermX
+ acl.User["foo"] = PermR | PermW
+ acl.User["bar"] = PermR | PermW
+ acl.Group[""] = PermR | PermX
+ acl.Group["baz"] = PermR | PermW | PermX
+ acl.Other[""] = 0
+ acl.Mask[""] = PermX
+
+ want := "u::rwx,u:bar:rw-,u:foo:rw-,g::r-x,g:baz:rwx,o::---,m::--x"
+ have := acl.String()
+ if have != want {
+ t.Errorf("Unexpected String result: have %s, want %s", have, want)
+ }
+}
+
+func TestParsing(t *testing.T) {
+ acl, err := ParseACL("u::rwx,u:bar:rw-,u:foo:rw-,g::r-x,g:baz:rwx,o::---,m::--x")
+ if err != nil {
+ t.Fatalf("unexpected parsing error: %s", err)
+ }
+
+ if len(acl.User) == 3 {
+ checkPerm(t, 'u', acl.User, "", PermR|PermW|PermX)
+ checkPerm(t, 'u', acl.User, "foo", PermR|PermW)
+ checkPerm(t, 'u', acl.User, "bar", PermR|PermW)
+ } else {
+ t.Errorf("Expeced 3 user entries, got %d", len(acl.User))
+ }
+
+ if len(acl.Group) == 2 {
+ checkPerm(t, 'u', acl.Group, "", PermR|PermX)
+ checkPerm(t, 'u', acl.Group, "baz", PermR|PermW|PermX)
+ } else {
+ t.Errorf("Expeced 2 group entries, got %d", len(acl.Group))
+ }
+
+ if len(acl.Other) == 1 {
+ checkPerm(t, 'u', acl.Other, "", 0)
+ } else {
+ t.Errorf("Expeced 1 other entries, got %d", len(acl.Other))
+ }
+
+ if len(acl.Mask) == 1 {
+ checkPerm(t, 'u', acl.Mask, "", PermX)
+ } else {
+ t.Errorf("Expeced 1 mask entries, got %d", len(acl.Mask))
+ }
+}