veyron2/security, veyron/security: Move ACL authorizer to veyron/security.

ACL authorizer is the default implementation of Authorizer, and is
intended to be used by both the Veyron runtime and applications.

Change-Id: Ib17097ccb94ccc2cd629a851b9a1c9fa29ef3d27
diff --git a/security/acl_authorizer.go b/security/acl_authorizer.go
new file mode 100644
index 0000000..82b35d9
--- /dev/null
+++ b/security/acl_authorizer.go
@@ -0,0 +1,103 @@
+package security
+
+// This file provides an implementation of security.Authorizer.
+//
+// Definitions
+// * Self-RPC: An RPC request is said to be a "self-RPC" if the identities
+// at the local and remote ends are identical.
+
+import (
+	"errors"
+	"os"
+	"reflect"
+
+	"veyron2/security"
+)
+
+var (
+	errACL          = errors.New("no matching ACL entry found")
+	errInvalidLabel = errors.New("label is invalid")
+	errNilID        = errors.New("identity being matched is nil")
+	errNilACL       = errors.New("ACL is nil")
+	nullACL         security.ACL
+)
+
+// aclAuthorizer implements Authorizer.
+type aclAuthorizer security.ACL
+
+// Authorize verifies a request iff the identity at the remote end has a name authorized by
+// the aclAuthorizer's ACL for the request's label, or the request corresponds to a self-RPC.
+func (a aclAuthorizer) Authorize(ctx security.Context) error {
+	// Test if the request corresponds to a self-RPC.
+	if ctx.LocalID() != nil && ctx.RemoteID() != nil && reflect.DeepEqual(ctx.LocalID(), ctx.RemoteID()) {
+		return nil
+	}
+	// Match the aclAuthorizer's ACL.
+	return matchesACL(ctx.RemoteID(), ctx.Label(), security.ACL(a))
+}
+
+// NewACLAuthorizer creates an authorizer from the provided ACL. The
+// authorizer authorizes a request iff the identity at the remote end has a name
+// authorized by the provided ACL for the request's label, or the request
+// corresponds to a self-RPC.
+func NewACLAuthorizer(acl security.ACL) security.Authorizer { return aclAuthorizer(acl) }
+
+// fileACLAuthorizer implements Authorizer.
+type fileACLAuthorizer string
+
+// Authorize reads and decodes the fileACLAuthorizer's ACL file into a ACL and
+// then verifies the request according to an aclAuthorizer based on the ACL. If
+// reading or decoding the file fails then no requests are authorized.
+func (a fileACLAuthorizer) Authorize(ctx security.Context) error {
+	acl, err := loadACLFromFile(string(a))
+	if err != nil {
+		return err
+	}
+	return aclAuthorizer(acl).Authorize(ctx)
+}
+
+// NewFileACLAuthorizer creates an authorizer from the provided path to a file
+// containing a JSON-encoded ACL. Each call to "Authorize" involves reading and
+// decoding a ACL from the file and then authorizing the request according to the
+// ACL. The authorizer monitors the file so out of band changes to the contents of
+// the file are reflected in the ACL. If reading or decoding the file fails then
+// no requests are authorized.
+//
+// The JSON-encoding of a ACL is essentially a JSON object describing a map from
+// PrincipalPatterns to encoded LabelSets (see LabelSet.MarshalJSON).
+// Examples:
+// * `{"*" : "RW"}` encodes an ACL that allows all principals to access all methods with
+//   ReadLabel or WriteLabel.
+// * `{"veyron/alice": "RW", "veyron/bob/*": "R"} encodes an ACL that allows all principals
+//   matching "veyron/alice" to access methods with ReadLabel or WriteLabel,
+//   and all principals matching "veyron/bob/*" to access methods with ReadLabel.
+//   (Also see PublicID.Match.)
+//
+// TODO(ataly, ashankar): Instead of reading the file on each call we should use the "inotify"
+// mechanism to watch the file. Eventually we should also support ACLs stored in the Veyron
+// store.
+func NewFileACLAuthorizer(filePath string) security.Authorizer { return fileACLAuthorizer(filePath) }
+
+func matchesACL(id security.PublicID, label security.Label, acl security.ACL) error {
+	if id == nil {
+		return errNilID
+	}
+	if acl == nil {
+		return errNilACL
+	}
+	for key, labels := range acl {
+		if labels.HasLabel(label) && id.Match(key) {
+			return nil
+		}
+	}
+	return errACL
+}
+
+func loadACLFromFile(filePath string) (security.ACL, error) {
+	f, err := os.Open(filePath)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	return security.LoadACL(f)
+}
diff --git a/security/acl_authorizer_test.go b/security/acl_authorizer_test.go
new file mode 100644
index 0000000..6c54ca4
--- /dev/null
+++ b/security/acl_authorizer_test.go
@@ -0,0 +1,249 @@
+package security
+
+import (
+	"io/ioutil"
+	"os"
+	"runtime"
+	"testing"
+	"time"
+
+	"veyron2/naming"
+	"veyron2/security"
+)
+
+type authMap map[security.PublicID]security.LabelSet
+
+// context implements Context.
+type context struct {
+	localID, remoteID    security.PublicID
+	discharges           security.CaveatDischargeMap
+	method, name, suffix string
+	label                security.Label
+}
+
+func (c *context) Method() string                                { return c.method }
+func (c *context) Name() string                                  { return c.name }
+func (c *context) Suffix() string                                { return c.suffix }
+func (c *context) Label() security.Label                         { return c.label }
+func (c *context) CaveatDischarges() security.CaveatDischargeMap { return c.discharges }
+func (c *context) LocalID() security.PublicID                    { return c.localID }
+func (c *context) RemoteID() security.PublicID                   { return c.remoteID }
+func (c *context) LocalEndpoint() naming.Endpoint                { return nil }
+func (c *context) RemoteEndpoint() naming.Endpoint               { return nil }
+
+func saveACLToTempFile(acl security.ACL) string {
+	f, err := ioutil.TempFile("", "saved_acl")
+	if err != nil {
+		panic(err)
+	}
+	defer f.Close()
+	if err := security.SaveACL(f, acl); err != nil {
+		defer os.Remove(f.Name())
+		panic(err)
+	}
+	return f.Name()
+}
+
+func updateACLInFile(fileName string, acl security.ACL) {
+	f, err := os.OpenFile(fileName, os.O_WRONLY, 0600)
+	if err != nil {
+		panic(err)
+	}
+	defer f.Close()
+	if err := security.SaveACL(f, acl); err != nil {
+		panic(err)
+	}
+}
+
+func bless(blessee security.PublicID, blesser security.PrivateID, name string) security.PublicID {
+	blessed, err := blesser.Bless(blessee, name, 5*time.Minute, nil)
+	if err != nil {
+		panic(err)
+	}
+	return blessed
+}
+
+func derive(pub security.PublicID, priv security.PrivateID) security.PrivateID {
+	d, err := priv.Derive(pub)
+	if err != nil {
+		panic(err)
+	}
+	return d
+}
+
+func testSelfRPCs(t *testing.T, authorizer security.Authorizer) {
+	_, file, line, _ := runtime.Caller(1)
+	var (
+		veyron      = security.FakePrivateID("veyron")
+		alice       = security.FakePrivateID("alice")
+		veyronAlice = bless(alice.PublicID(), veyron, "alice")
+	)
+	testData := []struct {
+		localID, remoteID security.PublicID
+		isAuthorized      bool
+	}{
+		{alice.PublicID(), alice.PublicID(), true},
+		{veyron.PublicID(), veyron.PublicID(), true},
+		{veyron.PublicID(), alice.PublicID(), false},
+		{veyronAlice, veyronAlice, true},
+		{veyronAlice, alice.PublicID(), false},
+		{veyronAlice, veyron.PublicID(), false},
+	}
+	for _, d := range testData {
+		ctx := &context{localID: d.localID, remoteID: d.remoteID}
+		if got, want := authorizer.Authorize(ctx), d.isAuthorized; (got == nil) != want {
+			t.Errorf("%s:%d: %+v.Authorize(&context{localID: %v, remoteID: %v}) returned error: %v, want error: %v", file, line, authorizer, d.localID, d.remoteID, got, !want)
+		}
+	}
+}
+
+func testAuthorizations(t *testing.T, authorizer security.Authorizer, authorizations authMap) {
+	_, file, line, _ := runtime.Caller(1)
+	for user, labels := range authorizations {
+		for _, l := range security.ValidLabels {
+			ctx := &context{remoteID: user, label: l}
+			if got, want := authorizer.Authorize(ctx), labels.HasLabel(l); (got == nil) != want {
+				t.Errorf("%s:%d: %+v.Authorize(&context{remoteID: %v, label: %v}) returned error: %v, want error: %v", file, line, authorizer, user, l, got, !want)
+			}
+		}
+	}
+}
+
+func testNothingPermitted(t *testing.T, authorizer security.Authorizer) {
+	_, file, line, _ := runtime.Caller(1)
+	var (
+		veyronPrivateID   = security.FakePrivateID("veyron")
+		alicePrivateID    = security.FakePrivateID("alice")
+		randomPrivateID   = security.FakePrivateID("random")
+		veyron            = veyronPrivateID.PublicID()
+		alice             = alicePrivateID.PublicID()
+		random            = randomPrivateID.PublicID()
+		veyronAlice       = bless(alice, veyronPrivateID, "alice")
+		veyronAliceFriend = bless(random, derive(veyronAlice, alicePrivateID), "friend")
+		veyronBob         = bless(random, veyronPrivateID, "bob")
+	)
+	users := []security.PublicID{
+		veyron,
+		random,
+		alice,
+
+		// Blessed principals
+		veyronAlice,
+		veyronAliceFriend,
+		veyronBob,
+	}
+	// No principal (whether the identity provider is trusted or not)
+	// should have access to any valid or invalid label.
+	for _, u := range users {
+		for _, l := range security.ValidLabels {
+			ctx := &context{remoteID: u, label: l}
+			if got := authorizer.Authorize(ctx); got == nil {
+				t.Errorf("%s:%d: %+v.Authorize(%v) returns nil, want error", file, line, authorizer, ctx)
+			}
+		}
+		invalidLabel := security.Label(3)
+		ctx := &context{remoteID: u, label: invalidLabel}
+		if got := authorizer.Authorize(ctx); got == nil {
+			t.Errorf("%s:%d: %+v.Authorize(%v) returns nil, want error", file, line, authorizer, ctx)
+		}
+	}
+}
+
+func TestACLAuthorizer(t *testing.T) {
+	const (
+		// Shorthands
+		R = security.ReadLabel
+		W = security.WriteLabel
+		A = security.AdminLabel
+		D = security.DebugLabel
+		M = security.MonitoringLabel
+	)
+	// Principals to test
+	var (
+		veyronPrivateID = security.FakePrivateID("veyron")
+		alicePrivateID  = security.FakePrivateID("alice")
+		veyron          = veyronPrivateID.PublicID()
+		alice           = alicePrivateID.PublicID()
+		bob             = security.FakePrivateID("bob").PublicID()
+
+		// Blessed principals
+		veyronAlice       = bless(alice, veyronPrivateID, "alice")
+		veyronBob         = bless(bob, veyronPrivateID, "bob")
+		veyronAliceFriend = bless(bob, derive(veyronAlice, alicePrivateID), "friend")
+	)
+	// Convenience function for combining Labels into a LabelSet.
+	LS := func(labels ...security.Label) security.LabelSet {
+		var ret security.LabelSet
+		for _, l := range labels {
+			ret = ret | security.LabelSet(l)
+		}
+		return ret
+	}
+
+	// ACL for testing
+	acl := security.ACL{
+		"*": LS(R),
+		"fake/veyron/alice/*": LS(W, R),
+		"fake/veyron/alice":   LS(A, D, M),
+		"fake/veyron/bob":     LS(D, M),
+	}
+
+	// Authorizations for the above ACL.
+	authorizations := authMap{
+		// alice and bob have only what "*" has.
+		alice: LS(R),
+		bob:   LS(R),
+		// veyron and veyronAlice have R, W, A, D, M from the "veyron/alice" and
+		// "veyron/alice/*" ACL entries.
+		veyron:      LS(R, W, A, D, M),
+		veyronAlice: LS(R, W, A, D, M),
+		// veyronBob has R, D, M from "*" and "veyron/bob" ACL entries.
+		veyronBob: LS(R, D, M),
+		// veyronAliceFriend has W, R from the "veyron/alice/*" ACL entry.
+		veyronAliceFriend: LS(W, R),
+		// nil PublicIDs are not authorized.
+		nil: LS(),
+	}
+	// Create an aclAuthorizer based on the ACL and verify the authorizations.
+	authorizer := NewACLAuthorizer(acl)
+	testAuthorizations(t, authorizer, authorizations)
+	testSelfRPCs(t, authorizer)
+
+	// Create a fileACLAuthorizer by saving the ACL in a file, and verify the
+	// authorizations.
+	fileName := saveACLToTempFile(acl)
+	defer os.Remove(fileName)
+	fileAuthorizer := NewFileACLAuthorizer(fileName)
+	testAuthorizations(t, fileAuthorizer, authorizations)
+	testSelfRPCs(t, fileAuthorizer)
+
+	// Modify the ACL stored in the file and verify that the authorizations appropriately
+	// change for the fileACLAuthorizer.
+	acl["fake/veyron/bob"] = LS(R, W, A, D, M)
+	updateACLInFile(fileName, acl)
+
+	authorizations[veyronBob] = LS(R, W, A, D, M)
+	testAuthorizations(t, fileAuthorizer, authorizations)
+	testSelfRPCs(t, fileAuthorizer)
+
+	// Update the ACL file with invalid contents and verify that no requests are
+	// authorized.
+	f, err := os.OpenFile(fileName, os.O_WRONLY, 0600)
+	if err != nil {
+		panic(err)
+	}
+	f.Write([]byte("invalid ACL"))
+	f.Close()
+	testNothingPermitted(t, fileAuthorizer)
+
+	// Verify that a fileACLAuthorizer based on a nonexistent file does not authorize any
+	// requests.
+	fileAuthorizer = NewFileACLAuthorizer("fileDoesNotExist")
+	testNothingPermitted(t, fileAuthorizer)
+}
+
+func TestNilACLAuthorizer(t *testing.T) {
+	authorizer := NewACLAuthorizer(nil)
+	testNothingPermitted(t, authorizer)
+	testSelfRPCs(t, authorizer)
+}
diff --git a/security/flag/flag.go b/security/flag/flag.go
index fa032b8..11dfcca 100644
--- a/security/flag/flag.go
+++ b/security/flag/flag.go
@@ -7,6 +7,8 @@
 	"errors"
 	"flag"
 
+	vsecurity "veyron/security"
+
 	"veyron2/security"
 )
 
@@ -27,11 +29,11 @@
 		panic(errors.New("only one of the flags \"--acl\" or \"--acl_file\" must be provided"))
 	}
 	if len(*aclFile) != 0 {
-		return security.NewFileACLAuthorizer(*aclFile)
+		return vsecurity.NewFileACLAuthorizer(*aclFile)
 	}
 	a, err := security.LoadACL(bytes.NewBufferString(*acl))
 	if err != nil {
 		return nil
 	}
-	return security.NewACLAuthorizer(a)
+	return vsecurity.NewACLAuthorizer(a)
 }
diff --git a/security/flag/flag_test.go b/security/flag/flag_test.go
index 5fa5616..03ae915 100644
--- a/security/flag/flag_test.go
+++ b/security/flag/flag_test.go
@@ -7,6 +7,7 @@
 	"testing"
 
 	tsecurity "veyron/lib/testutil/security"
+	vsecurity "veyron/security"
 
 	"veyron2/security"
 )
@@ -45,19 +46,19 @@
 		},
 		{
 			flags:    flagValue{"acl": "{}"},
-			wantAuth: security.NewACLAuthorizer(acl1),
+			wantAuth: vsecurity.NewACLAuthorizer(acl1),
 		},
 		{
 			flags:    flagValue{"acl": "{\"veyron/alice\":\"RW\", \"veyron/bob\": \"R\"}"},
-			wantAuth: security.NewACLAuthorizer(acl2),
+			wantAuth: vsecurity.NewACLAuthorizer(acl2),
 		},
 		{
 			flags:    flagValue{"acl": "{\"veyron/bob\":\"R\", \"veyron/alice\": \"WR\"}"},
-			wantAuth: security.NewACLAuthorizer(acl2),
+			wantAuth: vsecurity.NewACLAuthorizer(acl2),
 		},
 		{
 			flags:    flagValue{"acl_file": acl2File},
-			wantAuth: security.NewFileACLAuthorizer(acl2File),
+			wantAuth: vsecurity.NewFileACLAuthorizer(acl2File),
 		},
 		{
 			flags:     flagValue{"acl_file": acl2File, "acl": "{\"veyron/alice\":\"RW\", \"veyron/bob\": \"R\"}"},