security/access: Add an "access tag" caveat.

This commit introduces a Caveat implementation that can be used to
restrict methods invokable by a principal based on the tags on the
method being invoked.

My first intented use of this is for the "debug browse" tool,
wherein one user can delegate debugging of their server to
another - restricting the delegate so that it can only invoke
methods with the Debug or Resolve label - and thus not mess
with state of the server being debugged.

MultiPart: 1/2

Change-Id: I7605c230792ac4b0ecd0e42e54becc286b2af9d1
diff --git a/security/access/.api b/security/access/.api
index 294a21e..ab5cecd 100644
--- a/security/access/.api
+++ b/security/access/.api
@@ -5,7 +5,9 @@
 pkg access, const Write Tag
 pkg access, func AllTypicalTags() []Tag
 pkg access, func IsUnenforceablePatterns(error) []security.BlessingPattern
+pkg access, func NewAccessTagCaveat(...Tag) (security.Caveat, error)
 pkg access, func NewErrAccessListMatch(*context.T, []string, []security.RejectedBlessing) error
+pkg access, func NewErrAccessTagCaveatValidation(*context.T, []string, []Tag) error
 pkg access, func NewErrInvalidOpenAccessList(*context.T) error
 pkg access, func NewErrNoPermissions(*context.T, []string, []security.RejectedBlessing, string) error
 pkg access, func NewErrTooBig(*context.T) error
@@ -29,7 +31,9 @@
 pkg access, type AccessList struct, NotIn []string
 pkg access, type Permissions map[string]AccessList
 pkg access, type Tag string
+pkg access, var AccessTagCaveat security.CaveatDescriptor
 pkg access, var ErrAccessListMatch verror.IDAction
+pkg access, var ErrAccessTagCaveatValidation verror.IDAction
 pkg access, var ErrInvalidOpenAccessList verror.IDAction
 pkg access, var ErrNoPermissions verror.IDAction
 pkg access, var ErrTooBig verror.IDAction
diff --git a/security/access/caveat.go b/security/access/caveat.go
new file mode 100644
index 0000000..5acb2ca
--- /dev/null
+++ b/security/access/caveat.go
@@ -0,0 +1,38 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package access
+
+import (
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/vdl"
+)
+
+func init() {
+	security.RegisterCaveatValidator(AccessTagCaveat, func(ctx *context.T, call security.Call, params []Tag) error {
+		wantT := TypicalTagType()
+		methodTags := call.MethodTags()
+		for _, mt := range methodTags {
+			if mt.Type() == wantT {
+				for _, ct := range params {
+					if mt.RawString() == vdl.ValueOf(ct).RawString() {
+						return nil
+					}
+				}
+			}
+		}
+		strs := make([]string, len(methodTags))
+		for i, mt := range methodTags {
+			strs[i] = mt.RawString()
+		}
+		return NewErrAccessTagCaveatValidation(ctx, strs, params)
+	})
+}
+
+// NewAccessTagCaveat returns a Caveat that will validate iff the intersection
+// of the tags on the method being invoked and those in 'tags' is non-empty.
+func NewAccessTagCaveat(tags ...Tag) (security.Caveat, error) {
+	return security.NewCaveat(AccessTagCaveat, tags)
+}
diff --git a/security/access/caveat_test.go b/security/access/caveat_test.go
new file mode 100644
index 0000000..dc7586b
--- /dev/null
+++ b/security/access/caveat_test.go
@@ -0,0 +1,53 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package access_test
+
+import (
+	"testing"
+
+	"v.io/v23/context"
+	"v.io/v23/security"
+	"v.io/v23/security/access"
+	"v.io/v23/vdl"
+)
+
+func TestAccessTagCaveat(t *testing.T) {
+	var (
+		server     = newPrincipal(t)
+		bserver, _ = server.BlessSelf("server")
+		caveat, _  = access.NewAccessTagCaveat(access.Debug, access.Resolve)
+		bclient, _ = server.Bless(newPrincipal(t).PublicKey(), bserver, "debugger", caveat)
+		tests      = []struct {
+			MethodTags []*vdl.Value
+			OK         bool
+		}{
+			{nil, false},
+			{[]*vdl.Value{vdl.ValueOf(access.Debug)}, true},
+			{[]*vdl.Value{vdl.ValueOf(access.Resolve)}, true},
+			{[]*vdl.Value{vdl.ValueOf(access.Read), vdl.ValueOf(access.Debug)}, true},
+			{[]*vdl.Value{vdl.ValueOf(access.Read), vdl.ValueOf(access.Write)}, false},
+			{[]*vdl.Value{vdl.ValueOf("Debug"), vdl.ValueOf("Resolve")}, false},
+		}
+	)
+	security.AddToRoots(server, bserver)
+	ctx, cancel := context.RootContext()
+	defer cancel()
+	for idx, test := range tests {
+		call := security.NewCall(&security.CallParams{
+			MethodTags:      test.MethodTags,
+			LocalPrincipal:  server,
+			RemoteBlessings: bclient,
+		})
+		got, rejected := security.RemoteBlessingNames(ctx, call)
+		if test.OK {
+			if len(got) != 1 || got[0] != "server:debugger" {
+				t.Errorf("Got (%v, %v), wanted ([%q], nil) for method tags %v (test case #%d)", got, rejected, "server:debugger", test.MethodTags, idx)
+			}
+		}
+		if !test.OK && len(got) != 0 {
+			t.Errorf("Got (%v, %v), wanted all blessings to be rejected for method tags %v (test case #%d)", got, rejected, test.MethodTags, idx)
+		}
+	}
+}
diff --git a/security/access/types.vdl b/security/access/types.vdl
index e72bbcf..9405064 100644
--- a/security/access/types.vdl
+++ b/security/access/types.vdl
@@ -100,6 +100,7 @@
 package access
 
 import "v.io/v23/security"
+import "v.io/v23/uniqueid"
 
 // AccessList represents a set of blessings that should be granted access.
 //
@@ -148,6 +149,13 @@
   Read    = Tag("Read")     // Operations that do not mutate the state of the object.
   Write   = Tag("Write")    // Operations that mutate the state of the object.
   Resolve = Tag("Resolve")  // Operations involving namespace navigation.
+
+  // AccessTagCaveat represents a caveat that validates iff the method being invoked has
+  // at least one of the tags listed in the caveat.
+  AccessTagCaveat  = security.CaveatDescriptor{
+    Id:        uniqueid.Id{0xef, 0xcd, 0xe3, 0x75, 0x14, 0x16, 0xc7, 0x3b, 0x18, 0x9c, 0xe8, 0x9c, 0xcc, 0x93, 0x80, 0x0},
+    ParamType: typeobject([]Tag),
+  }
 )
 
 // Note: For "bad version" errors, use verror.ErrBadVersion.
@@ -162,4 +170,8 @@
 	UnenforceablePatterns(rejectedPatterns []security.BlessingPattern){"en":"AccessList contains the following invalid or unrecognized patterns in the In list: {rejectedPatterns}"}
 
 	InvalidOpenAccessList(){"en": "AccessList with the pattern ... in its In list must have no other patterns in the In or NotIn lists"}
+
+	AccessTagCaveatValidation(methodTags []string, caveatTags []Tag){
+		"en": "access tags on method ({methodTags}) do not include any of the ones in the caveat ({caveatTags}), or the method is using a different tag type",
+	}
 )
diff --git a/security/access/types.vdl.go b/security/access/types.vdl.go
index d0133d2..2ce46f5 100644
--- a/security/access/types.vdl.go
+++ b/security/access/types.vdl.go
@@ -111,6 +111,7 @@
 
 	// VDL user imports
 	"v.io/v23/security"
+	"v.io/v23/uniqueid"
 )
 
 // AccessList represents a set of blessings that should be granted access.
@@ -184,13 +185,38 @@
 
 const Resolve = Tag("Resolve") // Operations involving namespace navigation.
 
+// AccessTagCaveat represents a caveat that validates iff the method being invoked has
+// at least one of the tags listed in the caveat.
+var AccessTagCaveat = security.CaveatDescriptor{
+	Id: uniqueid.Id{
+		239,
+		205,
+		227,
+		117,
+		20,
+		22,
+		199,
+		59,
+		24,
+		156,
+		232,
+		156,
+		204,
+		147,
+		128,
+		0,
+	},
+	ParamType: vdl.TypeOf([]Tag(nil)),
+}
+
 var (
 	// The AccessList is too big.  Use groups to represent large sets of principals.
-	ErrTooBig                = verror.Register("v.io/v23/security/access.TooBig", verror.NoRetry, "{1:}{2:} AccessList is too big")
-	ErrNoPermissions         = verror.Register("v.io/v23/security/access.NoPermissions", verror.NoRetry, "{1:}{2:} {3} does not have {5} access (rejected blessings: {4})")
-	ErrAccessListMatch       = verror.Register("v.io/v23/security/access.AccessListMatch", verror.NoRetry, "{1:}{2:} {3} does not match the access list (rejected blessings: {4})")
-	ErrUnenforceablePatterns = verror.Register("v.io/v23/security/access.UnenforceablePatterns", verror.NoRetry, "{1:}{2:} AccessList contains the following invalid or unrecognized patterns in the In list: {3}")
-	ErrInvalidOpenAccessList = verror.Register("v.io/v23/security/access.InvalidOpenAccessList", verror.NoRetry, "{1:}{2:} AccessList with the pattern ... in its In list must have no other patterns in the In or NotIn lists")
+	ErrTooBig                    = verror.Register("v.io/v23/security/access.TooBig", verror.NoRetry, "{1:}{2:} AccessList is too big")
+	ErrNoPermissions             = verror.Register("v.io/v23/security/access.NoPermissions", verror.NoRetry, "{1:}{2:} {3} does not have {5} access (rejected blessings: {4})")
+	ErrAccessListMatch           = verror.Register("v.io/v23/security/access.AccessListMatch", verror.NoRetry, "{1:}{2:} {3} does not match the access list (rejected blessings: {4})")
+	ErrUnenforceablePatterns     = verror.Register("v.io/v23/security/access.UnenforceablePatterns", verror.NoRetry, "{1:}{2:} AccessList contains the following invalid or unrecognized patterns in the In list: {3}")
+	ErrInvalidOpenAccessList     = verror.Register("v.io/v23/security/access.InvalidOpenAccessList", verror.NoRetry, "{1:}{2:} AccessList with the pattern ... in its In list must have no other patterns in the In or NotIn lists")
+	ErrAccessTagCaveatValidation = verror.Register("v.io/v23/security/access.AccessTagCaveatValidation", verror.NoRetry, "{1:}{2:} access tags on method ({3}) do not include any of the ones in the caveat ({4}), or the method is using a different tag type")
 )
 
 func init() {
@@ -199,6 +225,7 @@
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrAccessListMatch.ID), "{1:}{2:} {3} does not match the access list (rejected blessings: {4})")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrUnenforceablePatterns.ID), "{1:}{2:} AccessList contains the following invalid or unrecognized patterns in the In list: {3}")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrInvalidOpenAccessList.ID), "{1:}{2:} AccessList with the pattern ... in its In list must have no other patterns in the In or NotIn lists")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrAccessTagCaveatValidation.ID), "{1:}{2:} access tags on method ({3}) do not include any of the ones in the caveat ({4}), or the method is using a different tag type")
 }
 
 // NewErrTooBig returns an error with the ErrTooBig ID.
@@ -225,3 +252,8 @@
 func NewErrInvalidOpenAccessList(ctx *context.T) error {
 	return verror.New(ErrInvalidOpenAccessList, ctx)
 }
+
+// NewErrAccessTagCaveatValidation returns an error with the ErrAccessTagCaveatValidation ID.
+func NewErrAccessTagCaveatValidation(ctx *context.T, methodTags []string, caveatTags []Tag) error {
+	return verror.New(ErrAccessTagCaveatValidation, ctx, methodTags, caveatTags)
+}
diff --git a/security/caveat.go b/security/caveat.go
index 380dce6..f8e31cd 100644
--- a/security/caveat.go
+++ b/security/caveat.go
@@ -221,21 +221,13 @@
 
 // NewExpiryCaveat returns a Caveat that validates iff the current time is before t.
 func NewExpiryCaveat(t time.Time) (Caveat, error) {
-	c, err := NewCaveat(ExpiryCaveat, t)
-	if err != nil {
-		return c, err
-	}
-	return c, nil
+	return NewCaveat(ExpiryCaveat, t)
 }
 
 // NewMethodCaveat returns a Caveat that validates iff the method being invoked by
 // the peer is listed in an argument to this function.
 func NewMethodCaveat(method string, additionalMethods ...string) (Caveat, error) {
-	c, err := NewCaveat(MethodCaveat, append(additionalMethods, method))
-	if err != nil {
-		return c, err
-	}
-	return c, nil
+	return NewCaveat(MethodCaveat, append(additionalMethods, method))
 }
 
 // NewPublicKeyCaveat returns a third-party caveat, i.e., the returned