From 2a0620b4fc7e33066998d6fe709625c009a9fe28 Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Thu, 29 Jun 2017 08:16:08 +0200 Subject: Add ACLs --- acl/acl.go | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ acl/acl_test.go | 105 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 acl/acl.go create mode 100644 acl/acl_test.go 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)) + } +} -- cgit v1.2.3-54-g00ecf