veyron/security/acl: Implement a LabelACL and a corresponding
Authorizer.

Motivation and design discussed in:
https://docs.google.com/a/google.com/document/d/1DZUu2sGKrf-b4p9JFdIbN0ULhz9Loa8reuPNKsdMKD0/edit#

Change-Id: I181a9ad8a0311e1376cb73cac560b302eeb8bded
diff --git a/security/acl/authorizer.go b/security/acl/authorizer.go
new file mode 100644
index 0000000..f4d60c9
--- /dev/null
+++ b/security/acl/authorizer.go
@@ -0,0 +1,150 @@
+package acl
+
+import (
+	"fmt"
+	"os"
+	"reflect"
+
+	"veyron.io/veyron/veyron2/security"
+)
+
+// TaggedACLAuthorizer implements an authorization policy where access is
+// granted if the remote end presents blessings included in the Access Control
+// Lists (ACLs) associated with the set of relevant tags.
+//
+// The set of relevant tags is the subset of tags associated with the
+// method (security.Context.MethodTags) that have the same type as tagType.
+// Currently, tagType.Kind must be reflect.String, i.e., only tags that are
+// named string types are supported.
+//
+// If multiple tags of tagType are associated with the method, then access is
+// granted if the peer presents blessings that match the ACLs of each one of
+// those tags. If no tags of tagType are associated with the method, then
+// access is denied.
+//
+// If the TaggedACLMap provided is nil, then a nil authorizer is returned.
+//
+// Sample usage:
+//
+// (1) Attach tags to methods in the VDL (eg. myservice.vdl)
+//   package myservice
+//
+//   type MyTag string
+//   const (
+//     ReadAccess  = MyTag("R")
+//     WriteAccess = MyTag("W")
+//   )
+//
+//   type MyService interface {
+//     Get() ([]string, error)       {ReadAccess}
+//     GetIndex(int) (string, error) {ReadAccess}
+//
+//     Set([]string) error           {WriteAccess}
+//     SetIndex(int, string) error   {WriteAccess}
+//
+//     GetAndSet([]string) ([]string, error) {ReadAccess, WriteAccess}
+//   }
+//
+// (2) Setup the ipc.Dispatcher to use the TaggedACLAuthorizer
+//   import (
+//     "reflect"
+//     "veyron.io/veyron/veyron/security/acl"
+//
+//     "veyron.io/veyron/veyron2/ipc"
+//     "veyron.io/veyron/veyron2/security"
+//   )
+//
+//   type dispatcher struct{}
+//   func (d dispatcher) Lookup(suffix, method) (ipc.Invoker, security.Authorizer, error) {
+//      acl := acl.TaggedACLMap{
+//        "R": acl.ACL{In: []security.BlessingPattern{"alice/friends/...", "alice/family/..."} },
+//        "W": acl.ACL{In: []security.BlessingPattern{"alice/family/...", "alice/colleagues/..." } },
+//      }
+//      typ := reflect.TypeOf(ReadAccess)  // equivalently, reflect.TypeOf(WriteAccess)
+//      return newInvoker(), acl.TaggedACLAuthorizer(acl, typ), nil
+//   }
+//
+// With the above dispatcher, the server will grant access to a peer with the blessing
+// "alice/friend/bob" access only to the "Get" and "GetIndex" methods. A peer presenting
+// the blessing "alice/colleague/carol" will get access only to the "Set" and "SetIndex"
+// methods. A peer presenting "alice/family/mom" will get access to all methods, even
+// GetAndSet - which requires that the blessing appear in the ACLs for both the
+// ReadAccess and WriteAccess tags.
+func TaggedACLAuthorizer(acls TaggedACLMap, tagType reflect.Type) (security.Authorizer, error) {
+	if tagType.Kind() != reflect.String {
+		return nil, fmt.Errorf("tag type(%v) must be backed by a string not %v", tagType, tagType.Kind())
+	}
+	return &authorizer{acls, tagType}, nil
+}
+
+// TaggedACLAuthorizerFromFile applies the same authorization policy as
+// TaggedACLAuthorizer, with the TaggedACLMap to be used sourced from a file named
+// filename.
+//
+// Changes to the file are monitored and affect subsequent calls to Authorize.
+// Currently, this is achieved by re-reading the file on every call to
+// Authorize.
+// TODO(ashankar,ataly): Use inotify or a similar mechanism to watch for
+// changes.
+func TaggedACLAuthorizerFromFile(filename string, tagType reflect.Type) (security.Authorizer, error) {
+	if tagType.Kind() != reflect.String {
+		return nil, fmt.Errorf("tag type(%v) must be backed by a string not %v", tagType, tagType.Kind())
+	}
+	return &fileAuthorizer{filename, tagType}, nil
+}
+
+type authorizer struct {
+	acls    TaggedACLMap
+	tagType reflect.Type
+}
+
+func (a *authorizer) Authorize(ctx security.Context) error {
+	// "Self-RPCs" are always authorized.
+	if l, r := ctx.LocalBlessings(), ctx.RemoteBlessings(); l != nil && r != nil && reflect.DeepEqual(l.PublicKey(), r.PublicKey()) {
+		return nil
+	}
+	var blessings []string
+	if ctx.RemoteBlessings() != nil {
+		blessings = ctx.RemoteBlessings().ForContext(ctx)
+	}
+	grant := false
+	for _, tag := range ctx.MethodTags() {
+		if v := reflect.ValueOf(tag); v.Type() == a.tagType {
+			if acl, exists := a.acls[v.String()]; !exists || !acl.Includes(blessings...) {
+				return errACLMatch(blessings)
+			}
+			grant = true
+		}
+	}
+	if grant {
+		return nil
+	}
+	return errACLMatch(blessings)
+}
+
+type fileAuthorizer struct {
+	filename string
+	tagType  reflect.Type
+}
+
+func (a *fileAuthorizer) Authorize(ctx security.Context) error {
+	acl, err := loadTaggedACLMapFromFile(a.filename)
+	if err != nil {
+		// TODO(ashankar): Information leak?
+		return fmt.Errorf("failed to read ACL from file: %v", err)
+	}
+	return (&authorizer{acl, a.tagType}).Authorize(ctx)
+}
+
+func loadTaggedACLMapFromFile(filename string) (TaggedACLMap, error) {
+	file, err := os.Open(filename)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+	return ReadTaggedACLMap(file)
+}
+
+func errACLMatch(blessings []string) error {
+	return fmt.Errorf("%v does not match ACL", blessings)
+}