Browse Source

acl: initial interface

Xiang Li 11 years ago
parent
commit
ba7215d7a8
2 changed files with 225 additions and 0 deletions
  1. 144 0
      acl/acl.go
  2. 81 0
      acl/acl_test.go

+ 144 - 0
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
+}

+ 81 - 0
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{}
+}