From ba7215d7a8ca53e3d075a75666a2853376bfe5d9 Mon Sep 17 00:00:00 2001 From: Xiang Li Date: Tue, 3 Feb 2015 23:32:36 -0800 Subject: [PATCH] acl: initial interface --- acl/acl.go | 144 ++++++++++++++++++++++++++++++++++++++++++++++++ acl/acl_test.go | 81 +++++++++++++++++++++++++++ 2 files changed, 225 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 000000000..43658dd59 --- /dev/null +++ b/acl/acl.go @@ -0,0 +1,144 @@ +package acl + +import ( + "bytes" + "log" +) + +const ( + read = "read" + write = "write" + manage = "manage" +) + +type ACL interface { + // Read checks the permission to read the prefix. + // Read permission can read the value of the prefix as a key or list the keys under prefix. + Read(prefix []byte) bool + // Write checks the permission to write the prefix. + // Write permission can write a key under the given prefix (including the prefix as a key). + Write(prefix []byte) bool + // Manage checks the permission to grant /revoke permission for the prefix. + Manage(prefix []byte) bool + + // Manage the ACL + // Before call these functions, the caller must check the management permission on its own. + // For example, A wants to grant B read permission for prefix "foo". A must check its own manage + // permission before calling GrantRead("foo") on B. + GrantRead(prefix []byte) + GrantWrite(prefix []byte) + GrantManage(prefix []byte) + RevokeRead(prefix []byte) + RevokeWrite(prefix []byte) + RevokeManage(prefix []byte) + Clear() +} + +type acl struct { + write [][]byte + read [][]byte + manage [][]byte + + parent ACL +} + +func (a *acl) Read(prefix []byte) bool { + if a.check(read, prefix) { + return true + } + if a.parent != nil { + return a.parent.Read(prefix) + } + return false +} + +func (a *acl) Write(prefix []byte) bool { + if a.check(write, prefix) { + return true + } + if a.parent != nil { + return a.parent.Write(prefix) + } + return false +} + +func (a *acl) Manage(prefix []byte) bool { + if a.check(manage, prefix) { + return true + } + if a.parent != nil { + return a.parent.Manage(prefix) + } + return false +} + +func (a *acl) GrantRead(prefix []byte) { a.grant(read, prefix) } +func (a *acl) GrantWrite(prefix []byte) { a.grant(write, prefix) } +func (a *acl) GrantManage(prefix []byte) { a.grant(manage, prefix) } + +func (a *acl) RevokeRead(prefix []byte) { a.revoke(read, prefix) } +func (a *acl) RevokeWrite(prefix []byte) { a.revoke(write, prefix) } +func (a *acl) RevokeManage(prefix []byte) { a.revoke(manage, prefix) } + +func (a *acl) Clear() { + a.read = nil + a.write = nil + a.manage = nil +} + +func (a *acl) check(op string, prefix []byte) bool { + list := a.list(op) + for _, pp := range list { + if len(pp) == 0 { + continue + } + if bytes.HasPrefix(prefix, pp) { + return true + } + } + return false +} + +func (a *acl) grant(op string, prefix []byte) { + list := a.list(op) + for _, pp := range list { + if bytes.Equal(prefix, pp) { + return + } + } + + switch op { + case read: + a.read = append(a.read, prefix) + case write: + a.write = append(a.write, prefix) + case manage: + a.manage = append(a.manage, prefix) + default: + log.Panicf("acl: unexpected check operation %s", op) + } + return +} + +func (a *acl) revoke(op string, prefix []byte) { + list := a.list(op) + for i, pp := range list { + if bytes.Equal(prefix, pp) { + list[i] = nil + } + } +} + +func (a *acl) list(op string) [][]byte { + switch op { + case read: + return a.read + case write: + return a.write + case manage: + return a.manage + default: + log.Panicf("acl: unexpected check operation %s", op) + } + return nil +} diff --git a/acl/acl_test.go b/acl/acl_test.go new file mode 100644 index 000000000..ab25a066f --- /dev/null +++ b/acl/acl_test.go @@ -0,0 +1,81 @@ +package acl + +import "testing" + +func TestACLRead(t *testing.T) { + readp := []byte("foobar") + acl := newTestEmpty() + acl.GrantRead(readp) + + for i := 0; i < len(readp); i++ { + r := acl.Read(readp[:i]) + if r == true { + t.Errorf("#%d.deny: r = %t, want %t", i, r, false) + } + } + + for i := 0; i < len(readp); i++ { + r := acl.Read(append(readp, readp[:i]...)) + if r == false { + t.Errorf("#%d.allow: r = %t, want %t", i, r, true) + } + } +} + +func TestACLWrite(t *testing.T) { + writep := []byte("foobar") + acl := newTestEmpty() + acl.GrantWrite(writep) + + for i := 0; i < len(writep); i++ { + w := acl.Write(writep[:i]) + if w == true { + t.Errorf("#%d.deny: w = %t, want %t", i, w, false) + } + } + + for i := 0; i < len(writep); i++ { + w := acl.Write(append(writep, writep[:i]...)) + if w == false { + t.Errorf("#%d.allow: w = %t, want %t", i, w, true) + } + } +} + +func TestACLManage(t *testing.T) { + managep := []byte("foobar") + acl := newTestEmpty() + acl.GrantManage(managep) + + for i := 0; i < len(managep); i++ { + m := acl.Manage(managep[:i]) + if m == true { + t.Errorf("#%d.deny: m = %t, want %t", i, m, false) + } + } + + for i := 0; i < len(managep); i++ { + m := acl.Manage(append(managep, managep[:i]...)) + if m == false { + t.Errorf("#%d.allow: m = %t, want %t", i, m, true) + } + } +} + +func TestACLRevoke(t *testing.T) { + readp := []byte("foobar") + acl := newTestEmpty() + acl.GrantRead(readp) + if !acl.Read(readp) { + t.Errorf("r = %t, want %t", false, true) + } + + acl.RevokeRead(readp) + if acl.Read(readp) { + t.Errorf("r = %t, want %t", true, false) + } +} + +func newTestEmpty() ACL { + return &acl{} +}