WSPR side of JS caveat validation with WSPR tests

This is separated in part to reduce the effect of churn.

Change-Id: Ie9bf42fa2c545cfb672e08fd22a84d08371100cf
diff --git a/services/wsprd/app/app.go b/services/wsprd/app/app.go
index fc15a23..b63904f 100644
--- a/services/wsprd/app/app.go
+++ b/services/wsprd/app/app.go
@@ -435,6 +435,19 @@
 	c.sendRPCResponse(ctx, w, span, vresults)
 }
 
+// HandleCaveatValidationResponse handles the response to caveat validation
+// requests.
+func (c *Controller) HandleCaveatValidationResponse(id int32, data string) {
+	c.Lock()
+	server := c.flowMap[id]
+	c.Unlock()
+	if server == nil {
+		vlog.Errorf("unexpected result from JavaScript. No server found matching id %d.", id)
+		return // ignore unknown server
+	}
+	server.HandleCaveatValidationResponse(id, data)
+}
+
 // HandleVeyronRequest starts a veyron rpc and returns before the rpc has been completed.
 func (c *Controller) HandleVeyronRequest(ctx *context.T, id int32, data string, w lib.ClientWriter) {
 	binbytes, err := hex.DecodeString(data)
diff --git a/services/wsprd/app/app_test.go b/services/wsprd/app/app_test.go
index 03cdf46..c234cb7 100644
--- a/services/wsprd/app/app_test.go
+++ b/services/wsprd/app/app_test.go
@@ -5,8 +5,15 @@
 	"encoding/hex"
 	"fmt"
 	"reflect"
+	"sync"
 	"testing"
 
+	"v.io/core/veyron/lib/testutil"
+	tsecurity "v.io/core/veyron/lib/testutil/security"
+	_ "v.io/core/veyron/profiles"
+	"v.io/core/veyron/runtimes/google/ipc/stream/proxy"
+	vsecurity "v.io/core/veyron/security"
+	mounttable "v.io/core/veyron/services/mounttable/lib"
 	"v.io/v23"
 	"v.io/v23/context"
 	"v.io/v23/ipc"
@@ -21,15 +28,12 @@
 	"v.io/wspr/veyron/services/wsprd/ipc/server"
 	"v.io/wspr/veyron/services/wsprd/lib"
 	"v.io/wspr/veyron/services/wsprd/lib/testwriter"
-
-	"v.io/core/veyron/lib/testutil"
-	tsecurity "v.io/core/veyron/lib/testutil/security"
-	_ "v.io/core/veyron/profiles"
-	"v.io/core/veyron/runtimes/google/ipc/stream/proxy"
-	vsecurity "v.io/core/veyron/security"
-	mounttable "v.io/core/veyron/services/mounttable/lib"
 )
 
+func init() {
+	server.EnableCustomWsprValidator = true
+}
+
 var (
 	testPrincipalBlessing = "test"
 	testPrincipal         = newPrincipal(testPrincipalBlessing)
@@ -335,7 +339,7 @@
 	return hex.EncodeToString(buf.Bytes()), nil
 }
 
-func serveServer(ctx *context.T) (*runningTest, error) {
+func serveServer(ctx *context.T, writer lib.ClientWriter, setController func(*Controller)) (*runningTest, error) {
 	mounttableServer, endpoint, err := startMountTableServer(ctx)
 	if err != nil {
 		return nil, fmt.Errorf("unable to start mounttable: %v", err)
@@ -348,10 +352,8 @@
 
 	proxyEndpoint := proxyServer.Endpoint().String()
 
-	writer := testwriter.Writer{}
-
 	writerCreator := func(int32) lib.ClientWriter {
-		return &writer
+		return writer
 	}
 	spec := v23.GetListenSpec(ctx)
 	spec.Proxy = "/" + proxyEndpoint
@@ -360,6 +362,10 @@
 		return nil, err
 	}
 
+	if setController != nil {
+		setController(controller)
+	}
+
 	v23.GetNamespace(controller.Context()).SetRoots("/" + endpoint.String())
 
 	req, err := makeRequest(VeyronRPCRequest{
@@ -369,73 +375,14 @@
 		NumOutArgs: 1,
 		Timeout:    20000000000,
 	}, "adder", 0)
-	controller.HandleVeyronRequest(ctx, 0, req, &writer)
+	controller.HandleVeyronRequest(ctx, 0, req, writer)
 
+	testWriter, _ := writer.(*testwriter.Writer)
 	return &runningTest{
-		controller, &writer, mounttableServer, proxyServer,
+		controller, testWriter, mounttableServer, proxyServer,
 	}, nil
 }
 
-func TestJavascriptServeServer(t *testing.T) {
-	ctx, shutdown := testutil.InitForTest()
-	defer shutdown()
-
-	rt, err := serveServer(ctx)
-	defer rt.mounttableServer.Stop()
-	defer rt.proxyServer.Shutdown()
-	defer rt.controller.Cleanup()
-	if err != nil {
-		t.Fatalf("could not serve server %v", err)
-	}
-
-	if err = rt.writer.WaitForMessage(1); err != nil {
-		t.Fatalf("error waiting for response: %v", err)
-	}
-
-	resp := rt.writer.Stream[0]
-
-	if resp.Type != lib.ResponseFinal {
-		t.Errorf("unknown stream message Got: %v, expected: serve response", resp)
-		return
-	}
-}
-
-func TestJavascriptStopServer(t *testing.T) {
-	ctx, shutdown := testutil.InitForTest()
-	defer shutdown()
-
-	rt, err := serveServer(ctx)
-	defer rt.mounttableServer.Stop()
-	defer rt.proxyServer.Shutdown()
-	defer rt.controller.Cleanup()
-
-	if err != nil {
-		t.Errorf("could not serve server %v", err)
-		return
-	}
-
-	if err = rt.writer.WaitForMessage(1); err != nil {
-		t.Fatalf("error waiting for response: %v", err)
-	}
-
-	// ensure there is only one server and then stop the server
-	if len(rt.controller.servers) != 1 {
-		t.Errorf("expected only one server but got: %d", len(rt.controller.servers))
-		return
-	}
-	for serverId := range rt.controller.servers {
-		rt.controller.Stop(nil, serverId)
-	}
-
-	// ensure there is no more servers now
-	if len(rt.controller.servers) != 0 {
-		t.Errorf("expected no server after stopping the only one but got: %d", len(rt.controller.servers))
-		return
-	}
-
-	return
-}
-
 // A test case to simulate a Javascript server talking to the App.  All the
 // responses from Javascript are mocked and sent back through the method calls.
 // All messages from the client are sent using a go client.
@@ -455,58 +402,40 @@
 	err error
 
 	// Whether or not the Javascript server has an authorizer or not.
-	// If it does have an authorizer, then authError is sent back from the server
+	// If it does have an authorizer, then err is sent back from the server
 	// to the app.
 	hasAuthorizer bool
-	authError     error
 }
 
 func runJsServerTestCase(t *testing.T, test jsServerTestCase) {
 	ctx, shutdown := testutil.InitForTest()
 	defer shutdown()
 
-	rt, err := serveServer(ctx)
-	defer rt.mounttableServer.Stop()
-	defer rt.proxyServer.Shutdown()
-	defer rt.controller.Cleanup()
-
-	if err != nil {
-		t.Errorf("could not serve server %v", err)
-	}
-
-	if err := rt.writer.WaitForMessage(1); err != nil {
-		t.Fatalf("error waiting for message: %v", err)
-	}
-
-	resp := rt.writer.Stream[0]
-
-	if resp.Type != lib.ResponseFinal {
-		t.Errorf("unknown stream message Got: %v, expected: serve response", resp)
-		return
-	}
-
-	rt.writer.Stream = nil
-
 	vomClientStream := []string{}
 	for _, m := range test.clientStream {
 		vomClientStream = append(vomClientStream, lib.VomEncodeOrDie(m))
 	}
 	mock := &mockJSServer{
-		controller:           rt.controller,
 		t:                    t,
 		method:               test.method,
 		serviceSignature:     []signature.Interface{simpleAddrSig},
 		expectedClientStream: vomClientStream,
 		serverStream:         test.serverStream,
 		hasAuthorizer:        test.hasAuthorizer,
-		authError:            test.authError,
 		inArgs:               test.inArgs,
 		finalResponse:        test.finalResponse,
 		finalError:           test.err,
+		controllerReady:      sync.RWMutex{},
 	}
-	// Let's replace the test writer with the mockJSServer
-	rt.controller.writerCreator = func(int32) lib.ClientWriter {
-		return mock
+	rt, err := serveServer(ctx, mock, func(controller *Controller) {
+		mock.controller = controller
+	})
+	defer rt.mounttableServer.Stop()
+	defer rt.proxyServer.Shutdown()
+	defer rt.controller.Cleanup()
+
+	if err != nil {
+		t.Fatalf("could not serve server %v", err)
 	}
 
 	// Get the client that is relevant to the controller so it talks
@@ -514,12 +443,12 @@
 	client := v23.GetClient(rt.controller.Context())
 
 	if err != nil {
-		t.Errorf("unable to create client: %v", err)
+		t.Fatalf("unable to create client: %v", err)
 	}
 
 	call, err := client.StartCall(rt.controller.Context(), "adder/adder", test.method, test.inArgs)
 	if err != nil {
-		t.Errorf("failed to start call: %v", err)
+		t.Fatalf("failed to start call: %v", err)
 	}
 
 	for _, msg := range test.clientStream {
@@ -550,22 +479,36 @@
 	var result *vdl.Value
 	err = call.Finish(&result)
 
-	// Make sure the err matches either test.authError or test.err.
-	if want := test.authError; want != nil {
-		if !verror.Equal(err, want) {
-			t.Errorf("didn't match test.authError: got %#v, want %#v", err, want)
-		}
-	} else if want := test.err; want != nil {
-		if !verror.Equal(err, want) {
-			t.Errorf("didn't match test.err: got %#v, want %#v", err, want)
-		}
-	} else if err != nil {
-		t.Errorf("unexpected error: got %#v, want nil", err)
+	// If err is nil and test.err is nil reflect.DeepEqual will return
+	// false because the types are different.  Because of this, we only use
+	// reflect.DeepEqual if one of the values is non-nil.  If both values
+	// are nil, then we consider them equal.
+	if (err != nil || test.err != nil) && !verror.Equal(err, test.err) {
+		t.Errorf("unexpected err: got %#v, expected %#v", err, test.err)
+	}
+
+	if err != nil {
+		return
 	}
 
 	if got, want := result, test.finalResponse; !vdl.EqualValue(got, want) {
 		t.Errorf("unexected final response: got %v, want %v", got, want)
 	}
+
+	// ensure there is only one server and then stop the server
+	if len(rt.controller.servers) != 1 {
+		t.Errorf("expected only one server but got: %d", len(rt.controller.servers))
+		return
+	}
+	for serverId := range rt.controller.servers {
+		rt.controller.Stop(nil, serverId)
+	}
+
+	// ensure there is no more servers now
+	if len(rt.controller.servers) != 0 {
+		t.Errorf("expected no server after stopping the only one but got: %d", len(rt.controller.servers))
+		return
+	}
 }
 
 func TestSimpleJSServer(t *testing.T) {
@@ -600,7 +543,8 @@
 		method:        "Add",
 		inArgs:        []interface{}{int32(1), int32(2)},
 		hasAuthorizer: true,
-		authError:     err,
+		finalResponse: vdl.Int32Value(3),
+		err:           err,
 	})
 }
 func TestJSServerWihStreamingInputs(t *testing.T) {
@@ -631,9 +575,9 @@
 func TestJSServerWithWrongNumberOfArgs(t *testing.T) {
 	err := verror.New(server.ErrWrongNumberOfArgs, nil, "Add", 3, 2)
 	runJsServerTestCase(t, jsServerTestCase{
-		method:    "Add",
-		inArgs:    []interface{}{int32(1), int32(2), int32(3)},
-		authError: err,
+		method: "Add",
+		inArgs: []interface{}{int32(1), int32(2), int32(3)},
+		err:    err,
 	})
 }
 
@@ -641,8 +585,8 @@
 	methodName := "UnknownMethod"
 	err := verror.New(server.ErrMethodNotFoundInSignature, nil, methodName)
 	runJsServerTestCase(t, jsServerTestCase{
-		method:    methodName,
-		inArgs:    []interface{}{int32(1), int32(2)},
-		authError: err,
+		method: methodName,
+		inArgs: []interface{}{int32(1), int32(2)},
+		err:    err,
 	})
 }
diff --git a/services/wsprd/app/messaging.go b/services/wsprd/app/messaging.go
index 911fe22..657603e 100644
--- a/services/wsprd/app/messaging.go
+++ b/services/wsprd/app/messaging.go
@@ -76,7 +76,10 @@
 	RemoveName = 19
 
 	// A request to get the remove blessings of a server.
-	RemoteBlessings = 20.
+	RemoteBlessings = 20
+
+	// A response to a caveat validation request.
+	CaveatValidationResponse = 21
 )
 
 type Message struct {
@@ -115,6 +118,8 @@
 		go c.HandleLookupResponse(msg.Id, msg.Data)
 	case AuthResponseMessage:
 		go c.HandleAuthResponse(msg.Id, msg.Data)
+	case CaveatValidationResponse:
+		go c.HandleCaveatValidationResponse(msg.Id, msg.Data)
 
 	default:
 		w.Error(verror.New(errUnknownMessageType, ctx, msg.Type))
diff --git a/services/wsprd/app/mock_jsServer_test.go b/services/wsprd/app/mock_jsServer_test.go
index f3f848f..61bed8d 100644
--- a/services/wsprd/app/mock_jsServer_test.go
+++ b/services/wsprd/app/mock_jsServer_test.go
@@ -10,6 +10,7 @@
 
 	"v.io/v23/vdl"
 	"v.io/v23/vdl/vdlroot/src/signature"
+	"v.io/v23/vom"
 	"v.io/wspr/veyron/services/wsprd/ipc/server"
 	"v.io/wspr/veyron/services/wsprd/lib"
 	"v.io/wspr/veyron/services/wsprd/principal"
@@ -26,7 +27,9 @@
 	hasAuthorizer        bool
 	authError            error
 	inArgs               []interface{}
+	controllerReady      sync.RWMutex
 	finalResponse        *vdl.Value
+	receivedResponse     *vdl.Value
 	finalError           error
 	hasCalledAuth        bool
 	// Right now we keep track of the flow count by hand, but maybe we
@@ -46,11 +49,18 @@
 		return m.handleAuthRequest(msg)
 	case lib.ResponseServerRequest:
 		return m.handleServerRequest(msg)
+	case lib.ResponseValidate:
+		return m.handleValidationRequest(msg)
 	case lib.ResponseStream:
 		return m.handleStream(msg)
 	case lib.ResponseStreamClose:
 		return m.handleStreamClose(msg)
-
+	case lib.ResponseFinal:
+		if m.receivedResponse != nil {
+			return fmt.Errorf("Two responses received. First was: %#v. Second was: %#v", m.receivedResponse, msg)
+		}
+		m.receivedResponse = vdl.ValueOf(msg)
+		return nil
 	}
 	return fmt.Errorf("Unknown message type: %d", responseType)
 }
@@ -88,6 +98,8 @@
 	defer func() {
 		m.flowCount += 2
 	}()
+	m.controllerReady.RLock()
+	defer m.controllerReady.RUnlock()
 	msg, err := normalize(v)
 	if err != nil {
 		m.controller.HandleLookupResponse(m.flowCount, internalErrJSON(err))
@@ -236,11 +248,38 @@
 	return nil
 }
 
+func (m *mockJSServer) handleValidationRequest(v interface{}) error {
+	defer func() {
+		m.flowCount += 2
+	}()
+
+	req := v.(server.CaveatValidationRequest)
+	resp := server.CaveatValidationResponse{
+		Results: make([]error, len(req.Cavs)),
+	}
+
+	var b bytes.Buffer
+	enc, err := vom.NewEncoder(&b)
+	if err != nil {
+		panic(err)
+	}
+	if err := enc.Encode(resp); err != nil {
+		panic(err)
+	}
+
+	m.controllerReady.RLock()
+	m.controller.HandleCaveatValidationResponse(m.flowCount, fmt.Sprintf("%x", b.Bytes()))
+	m.controllerReady.RUnlock()
+	return nil
+}
+
 func (m *mockJSServer) sendServerStream() {
 	defer m.sender.Done()
+	m.controllerReady.RLock()
 	for _, v := range m.serverStream {
 		m.controller.SendOnStream(m.rpcFlow, lib.VomEncodeOrDie(v), m)
 	}
+	m.controllerReady.RUnlock()
 }
 
 func (m *mockJSServer) handleStream(msg interface{}) error {
@@ -268,6 +307,8 @@
 	if err != nil {
 		m.t.Fatalf("Failed to serialize the reply: %v", err)
 	}
+	m.controllerReady.RLock()
 	m.controller.HandleServerResponse(m.rpcFlow, vomReply)
+	m.controllerReady.RUnlock()
 	return nil
 }
diff --git a/services/wsprd/browspr/browspr.go b/services/wsprd/browspr/browspr.go
index 8da654a..7ed156d 100644
--- a/services/wsprd/browspr/browspr.go
+++ b/services/wsprd/browspr/browspr.go
@@ -76,6 +76,7 @@
 	if !ok {
 		instance = newPipe(b, instanceId, origin)
 		if instance == nil {
+			b.mu.Unlock()
 			return fmt.Errorf("Could not create pipe for origin %v: origin")
 		}
 		b.activeInstances[instanceId] = instance
diff --git a/services/wsprd/browspr/browspr_test.go b/services/wsprd/browspr/browspr_test.go
index c5fcd98..a3dec91 100644
--- a/services/wsprd/browspr/browspr_test.go
+++ b/services/wsprd/browspr/browspr_test.go
@@ -21,9 +21,14 @@
 	"v.io/core/veyron/runtimes/google/ipc/stream/proxy"
 	mounttable "v.io/core/veyron/services/mounttable/lib"
 	"v.io/wspr/veyron/services/wsprd/app"
+	"v.io/wspr/veyron/services/wsprd/ipc/server"
 	"v.io/wspr/veyron/services/wsprd/lib"
 )
 
+func init() {
+	server.EnableCustomWsprValidator = true
+}
+
 func startProxy() (*proxy.Proxy, error) {
 	rid, err := naming.NewRoutingID()
 	if err != nil {
diff --git a/services/wsprd/ipc/server/dispatcher_test.go b/services/wsprd/ipc/server/dispatcher_test.go
index ee923c3..f48818a 100644
--- a/services/wsprd/ipc/server/dispatcher_test.go
+++ b/services/wsprd/ipc/server/dispatcher_test.go
@@ -13,6 +13,10 @@
 	"v.io/wspr/veyron/services/wsprd/lib/testwriter"
 )
 
+func init() {
+	EnableCustomWsprValidator = true
+}
+
 type mockFlowFactory struct {
 	writer testwriter.Writer
 }
@@ -170,7 +174,7 @@
 			t.Errorf("failed to get dispatch request %v", err)
 			t.Fail()
 		}
-		jsonResponse := `{"err":{"id":"veyron2/verror.Exists","msg":"bad stuff"}}`
+		jsonResponse := `{"err":{"id":"v23/verror.Exists","msg":"bad stuff"}}`
 		d.handleLookupResponse(0, jsonResponse)
 	}()
 
diff --git a/services/wsprd/ipc/server/server.go b/services/wsprd/ipc/server/server.go
index 0248268..3822c18 100644
--- a/services/wsprd/ipc/server/server.go
+++ b/services/wsprd/ipc/server/server.go
@@ -22,6 +22,9 @@
 	"v.io/v23/vlog"
 )
 
+// TODO(bprosnitz) Remove this an always enable the custom validator.
+var EnableCustomWsprValidator bool
+
 type Flow struct {
 	ID     int32
 	Writer lib.ClientWriter
@@ -63,20 +66,6 @@
 	Err *verror.Standard
 }
 
-// Contex is the security context passed to Javascript.
-// This is exported to make the app test easier.
-type SecurityContext struct {
-	Method                string
-	Suffix                string
-	MethodTags            []*vdl.Value
-	LocalBlessings        principal.BlessingsHandle
-	LocalBlessingStrings  []string
-	RemoteBlessings       principal.BlessingsHandle
-	RemoteBlessingStrings []string
-	LocalEndpoint         string
-	RemoteEndpoint        string
-}
-
 // AuthRequest is a request for a javascript authorizer to run
 // This is exported to make the app test easier.
 type AuthRequest struct {
@@ -86,7 +75,9 @@
 }
 
 type Server struct {
-	mu sync.Mutex
+	// serverStateLock should be aquired when starting or stopping the server.
+	// This should be locked before outstandingRequestLock.
+	serverStateLock sync.Mutex
 
 	// The ipc.ListenSpec to use with server.Listen
 	listenSpec *ipc.ListenSpec
@@ -105,22 +96,33 @@
 	id     uint32
 	helper ServerHelper
 
+	// outstandingRequestLock should be acquired only to update the
+	// outstanding request maps below.
+	outstandingRequestLock sync.Mutex
+
 	// The set of outstanding server requests.
 	outstandingServerRequests map[int32]chan *lib.ServerRPCReply
 
 	outstandingAuthRequests map[int32]chan error
+
+	outstandingValidationRequests map[int32]chan []error
 }
 
 func NewServer(id uint32, listenSpec *ipc.ListenSpec, helper ServerHelper) (*Server, error) {
 	server := &Server{
-		id:                        id,
-		helper:                    helper,
-		listenSpec:                listenSpec,
-		outstandingServerRequests: make(map[int32]chan *lib.ServerRPCReply),
-		outstandingAuthRequests:   make(map[int32]chan error),
+		id:                            id,
+		helper:                        helper,
+		listenSpec:                    listenSpec,
+		outstandingServerRequests:     make(map[int32]chan *lib.ServerRPCReply),
+		outstandingAuthRequests:       make(map[int32]chan error),
+		outstandingValidationRequests: make(map[int32]chan []error),
 	}
 	var err error
-	if server.server, err = v23.NewServer(helper.Context()); err != nil {
+	ctx := helper.Context()
+	if EnableCustomWsprValidator {
+		ctx = context.WithValue(ctx, "customChainValidator", server.wsprCaveatValidator)
+	}
+	if server.server, err = v23.NewServer(ctx); err != nil {
 		return nil, err
 	}
 	return server, nil
@@ -132,11 +134,13 @@
 
 func (s *Server) createRemoteInvokerFunc(handle int32) remoteInvokeFunc {
 	return func(methodName string, args []interface{}, call ipc.ServerCall) <-chan *lib.ServerRPCReply {
+		securityContext := s.convertSecurityContext(call, true)
+
 		flow := s.helper.CreateNewFlow(s, call)
 		replyChan := make(chan *lib.ServerRPCReply, 1)
-		s.mu.Lock()
+		s.outstandingRequestLock.Lock()
 		s.outstandingServerRequests[flow.ID] = replyChan
-		s.mu.Unlock()
+		s.outstandingRequestLock.Unlock()
 
 		timeout := lib.JSIPCNoTimeout
 		if deadline, ok := call.Context().Deadline(); ok {
@@ -154,7 +158,7 @@
 		}
 
 		context := ServerRPCRequestContext{
-			SecurityContext: s.convertSecurityContext(call),
+			SecurityContext: securityContext,
 			Timeout:         timeout,
 		}
 
@@ -226,15 +230,20 @@
 
 func (s *Server) createRemoteGlobFunc(handle int32) remoteGlobFunc {
 	return func(pattern string, call ipc.ServerContext) (<-chan naming.VDLGlobReply, error) {
+		// Until the tests get fixed, we need to create a security context before creating the flow
+		// because creating the security context creates a flow and flow ids will be off.
+		// See https://github.com/veyron/release-issues/issues/1181
+		securityContext := s.convertSecurityContext(call, true)
+
 		globChan := make(chan naming.VDLGlobReply, 1)
 		flow := s.helper.CreateNewFlow(s, &globStream{
 			ch:  globChan,
 			ctx: call.Context(),
 		})
 		replyChan := make(chan *lib.ServerRPCReply, 1)
-		s.mu.Lock()
+		s.outstandingRequestLock.Lock()
 		s.outstandingServerRequests[flow.ID] = replyChan
-		s.mu.Unlock()
+		s.outstandingRequestLock.Unlock()
 
 		timeout := lib.JSIPCNoTimeout
 		if deadline, ok := call.Context().Deadline(); ok {
@@ -249,7 +258,7 @@
 		}
 
 		context := ServerRPCRequestContext{
-			SecurityContext: s.convertSecurityContext(call),
+			SecurityContext: securityContext,
 			Timeout:         timeout,
 		}
 
@@ -314,35 +323,112 @@
 	return *principal.ConvertBlessingsToHandle(blessings, s.helper.AddBlessings(blessings))
 }
 
-func (s *Server) convertSecurityContext(ctx security.Context) SecurityContext {
-	local, _ := ctx.LocalBlessings().ForContext(ctx)
-	remote, _ := ctx.RemoteBlessings().ForContext(ctx)
-	return SecurityContext{
-		Method:                lib.LowercaseFirstCharacter(ctx.Method()),
-		Suffix:                ctx.Suffix(),
-		MethodTags:            ctx.MethodTags(),
-		LocalEndpoint:         ctx.LocalEndpoint().String(),
-		RemoteEndpoint:        ctx.RemoteEndpoint().String(),
-		LocalBlessings:        s.convertBlessingsToHandle(ctx.LocalBlessings()),
-		LocalBlessingStrings:  local,
-		RemoteBlessings:       s.convertBlessingsToHandle(ctx.RemoteBlessings()),
-		RemoteBlessingStrings: remote,
+func makeListOfErrors(numErrors int, err error) []error {
+	errs := make([]error, numErrors)
+	for i := 0; i < numErrors; i++ {
+		errs[i] = err
 	}
+	return errs
+}
+
+// wsprCaveatValidator validates caveats in javascript.
+// It resolves each []security.Caveat in cavs to an error (or nil) and collects them in a slice.
+func (s *Server) wsprCaveatValidator(ctx security.Context, cavs [][]security.Caveat) []error {
+	flow := s.helper.CreateNewFlow(s, nil)
+	req := CaveatValidationRequest{
+		Ctx:  s.convertSecurityContext(ctx, false),
+		Cavs: cavs,
+	}
+
+	replyChan := make(chan []error, 1)
+	s.outstandingRequestLock.Lock()
+	s.outstandingValidationRequests[flow.ID] = replyChan
+	s.outstandingRequestLock.Unlock()
+
+	defer func() {
+		s.outstandingRequestLock.Lock()
+		delete(s.outstandingValidationRequests, flow.ID)
+		s.outstandingRequestLock.Unlock()
+		s.cleanupFlow(flow.ID)
+	}()
+
+	if err := flow.Writer.Send(lib.ResponseValidate, req); err != nil {
+		vlog.VI(2).Infof("Failed to send validate response: %v", err)
+		replyChan <- makeListOfErrors(len(cavs), err)
+	}
+
+	// TODO(bprosnitz) Consider using a different timeout than the standard ipc timeout.
+	delay := time.Duration(ipc.NoTimeout)
+	if dl, ok := ctx.VanadiumContext().Deadline(); ok {
+		delay = dl.Sub(time.Now())
+	}
+	timeoutChan := time.After(delay)
+
+	select {
+	case <-timeoutChan:
+		return makeListOfErrors(len(cavs), NewErrCaveatValidationTimeout(ctx.VanadiumContext()))
+	case reply := <-replyChan:
+		if len(reply) != len(cavs) {
+			vlog.VI(2).Infof("Wspr caveat validator received %d results from javascript but expected %d", len(reply), len(cavs))
+			return makeListOfErrors(len(cavs), NewErrInvalidValidationResponseFromJavascript(ctx.VanadiumContext()))
+		}
+
+		return reply
+	}
+}
+
+func (s *Server) convertSecurityContext(ctx security.Context, includeBlessingStrings bool) SecurityContext {
+	// TODO(bprosnitz) Local/Remote Endpoint should always be non-nil, but isn't
+	// due to a TODO in vc/auth.go
+	var localEndpoint string
+	if ctx.LocalEndpoint() != nil {
+		localEndpoint = ctx.LocalEndpoint().String()
+	}
+	var remoteEndpoint string
+	if ctx.RemoteEndpoint() != nil {
+		remoteEndpoint = ctx.RemoteEndpoint().String()
+	}
+	var localBlessings principal.BlessingsHandle
+	if ctx.LocalBlessings() != nil {
+		localBlessings = s.convertBlessingsToHandle(ctx.LocalBlessings())
+	}
+	anymtags := make([]*vdl.Value, len(ctx.MethodTags()))
+	for i, mtag := range ctx.MethodTags() {
+		anymtags[i] = mtag
+	}
+	secCtx := SecurityContext{
+		Method:          lib.LowercaseFirstCharacter(ctx.Method()),
+		Suffix:          ctx.Suffix(),
+		MethodTags:      anymtags,
+		LocalEndpoint:   localEndpoint,
+		RemoteEndpoint:  remoteEndpoint,
+		LocalBlessings:  localBlessings,
+		RemoteBlessings: s.convertBlessingsToHandle(ctx.RemoteBlessings()),
+	}
+	if includeBlessingStrings {
+		secCtx.LocalBlessingStrings, _ = ctx.LocalBlessings().ForContext(ctx)
+		secCtx.RemoteBlessingStrings, _ = ctx.RemoteBlessings().ForContext(ctx)
+	}
+	return secCtx
 }
 
 type remoteAuthFunc func(ctx security.Context) error
 
 func (s *Server) createRemoteAuthFunc(handle int32) remoteAuthFunc {
 	return func(ctx security.Context) error {
+		// Until the tests get fixed, we need to create a security context before creating the flow
+		// because creating the security context creates a flow and flow ids will be off.
+		securityContext := s.convertSecurityContext(ctx, true)
+
 		flow := s.helper.CreateNewFlow(s, nil)
 		replyChan := make(chan error, 1)
-		s.mu.Lock()
+		s.outstandingRequestLock.Lock()
 		s.outstandingAuthRequests[flow.ID] = replyChan
-		s.mu.Unlock()
+		s.outstandingRequestLock.Unlock()
 		message := AuthRequest{
 			ServerID: s.id,
 			Handle:   handle,
-			Context:  s.convertSecurityContext(ctx),
+			Context:  securityContext,
 		}
 		vlog.VI(0).Infof("Sending out auth request for %v, %v", flow.ID, message)
 
@@ -355,17 +441,17 @@
 
 		err = <-replyChan
 		vlog.VI(0).Infof("going to respond with %v", err)
-		s.mu.Lock()
+		s.outstandingRequestLock.Lock()
 		delete(s.outstandingAuthRequests, flow.ID)
-		s.mu.Unlock()
+		s.outstandingRequestLock.Unlock()
 		s.helper.CleanupFlow(flow.ID)
 		return err
 	}
 }
 
 func (s *Server) Serve(name string) error {
-	s.mu.Lock()
-	defer s.mu.Unlock()
+	s.serverStateLock.Lock()
+	defer s.serverStateLock.Unlock()
 
 	if s.dispatcher == nil {
 		s.dispatcher = newDispatcher(s.id, s, s, s)
@@ -385,8 +471,8 @@
 }
 
 func (s *Server) popServerRequest(id int32) chan *lib.ServerRPCReply {
-	s.mu.Lock()
-	defer s.mu.Unlock()
+	s.outstandingRequestLock.Lock()
+	defer s.outstandingRequestLock.Unlock()
 	ch := s.outstandingServerRequests[id]
 	delete(s.outstandingServerRequests, id)
 
@@ -419,9 +505,9 @@
 }
 
 func (s *Server) HandleAuthResponse(id int32, data string) {
-	s.mu.Lock()
+	s.outstandingRequestLock.Lock()
 	ch := s.outstandingAuthRequests[id]
-	s.mu.Unlock()
+	s.outstandingRequestLock.Unlock()
 	if ch == nil {
 		vlog.Errorf("unexpected result from JavaScript. No channel "+
 			"for MessageId: %d exists. Ignoring the results(%s)", id, data)
@@ -448,6 +534,27 @@
 	ch <- err
 }
 
+func (s *Server) HandleCaveatValidationResponse(id int32, data string) {
+	s.outstandingRequestLock.Lock()
+	ch := s.outstandingValidationRequests[id]
+	s.outstandingRequestLock.Unlock()
+	if ch == nil {
+		vlog.Errorf("unexpected result from JavaScript. No channel "+
+			"for validation response with MessageId: %d exists. Ignoring the results(%s)", id, data)
+		//Ignore unknown responses that don't belong to any channel
+		return
+	}
+
+	var reply CaveatValidationResponse
+	if err := lib.VomDecode(data, &reply); err != nil {
+		vlog.Errorf("failed to decode validation response %q: error %v", data, err)
+		ch <- []error{}
+		return
+	}
+
+	ch <- reply.Results
+}
+
 func (s *Server) createFlow() *Flow {
 	return s.helper.CreateNewFlow(s, nil)
 }
@@ -478,8 +585,7 @@
 		Results: nil,
 		Err:     &stdErr,
 	}
-	s.mu.Lock()
-	defer s.mu.Unlock()
+	s.serverStateLock.Lock()
 
 	if s.dispatcher != nil {
 		s.dispatcher.Cleanup()
@@ -496,7 +602,12 @@
 		default:
 		}
 	}
+	s.outstandingRequestLock.Lock()
+	s.outstandingAuthRequests = make(map[int32]chan error)
 	s.outstandingServerRequests = make(map[int32]chan *lib.ServerRPCReply)
+	s.outstandingValidationRequests = make(map[int32]chan []error)
+	s.outstandingRequestLock.Unlock()
+	s.serverStateLock.Unlock()
 	s.server.Stop()
 }
 
diff --git a/services/wsprd/ipc/server/server.vdl b/services/wsprd/ipc/server/server.vdl
new file mode 100644
index 0000000..fb0e050
--- /dev/null
+++ b/services/wsprd/ipc/server/server.vdl
@@ -0,0 +1,32 @@
+package server
+
+import (
+  "v.io/wspr/veyron/services/wsprd/principal"
+  "v.io/v23/security"
+)
+
+type SecurityContext struct {
+    Method                string
+    Suffix                string
+    MethodTags            []any
+    LocalBlessings        principal.BlessingsHandle
+    LocalBlessingStrings  []string
+    RemoteBlessings       principal.BlessingsHandle
+    RemoteBlessingStrings []string
+    LocalEndpoint         string
+    RemoteEndpoint        string
+}
+
+type CaveatValidationRequest struct {
+    Ctx SecurityContext
+    Cavs [][]security.Caveat
+}
+
+type CaveatValidationResponse struct {
+    Results []error
+}
+
+error (
+    CaveatValidationTimeout() {"en": "Caveat validation has timed out"}
+    InvalidValidationResponseFromJavascript() {"en": "Invalid validation response from javascript"}
+)
\ No newline at end of file
diff --git a/services/wsprd/ipc/server/server.vdl.go b/services/wsprd/ipc/server/server.vdl.go
new file mode 100644
index 0000000..93a26fd
--- /dev/null
+++ b/services/wsprd/ipc/server/server.vdl.go
@@ -0,0 +1,78 @@
+// This file was auto-generated by the veyron vdl tool.
+// Source: server.vdl
+
+package server
+
+import (
+	// VDL system imports
+	"v.io/v23/context"
+	"v.io/v23/i18n"
+	"v.io/v23/vdl"
+	"v.io/v23/verror"
+
+	// VDL user imports
+	"v.io/v23/security"
+	"v.io/wspr/veyron/services/wsprd/principal"
+)
+
+type SecurityContext struct {
+	Method                string
+	Suffix                string
+	MethodTags            []*vdl.Value
+	LocalBlessings        principal.BlessingsHandle
+	LocalBlessingStrings  []string
+	RemoteBlessings       principal.BlessingsHandle
+	RemoteBlessingStrings []string
+	LocalEndpoint         string
+	RemoteEndpoint        string
+}
+
+func (SecurityContext) __VDLReflect(struct {
+	Name string "v.io/wspr/veyron/services/wsprd/ipc/server.SecurityContext"
+}) {
+}
+
+type CaveatValidationRequest struct {
+	Ctx  SecurityContext
+	Cavs [][]security.Caveat
+}
+
+func (CaveatValidationRequest) __VDLReflect(struct {
+	Name string "v.io/wspr/veyron/services/wsprd/ipc/server.CaveatValidationRequest"
+}) {
+}
+
+type CaveatValidationResponse struct {
+	Results []error
+}
+
+func (CaveatValidationResponse) __VDLReflect(struct {
+	Name string "v.io/wspr/veyron/services/wsprd/ipc/server.CaveatValidationResponse"
+}) {
+}
+
+func init() {
+	vdl.Register((*SecurityContext)(nil))
+	vdl.Register((*CaveatValidationRequest)(nil))
+	vdl.Register((*CaveatValidationResponse)(nil))
+}
+
+var (
+	ErrCaveatValidationTimeout                 = verror.Register("v.io/wspr/veyron/services/wsprd/ipc/server.CaveatValidationTimeout", verror.NoRetry, "{1:}{2:} Caveat validation has timed out")
+	ErrInvalidValidationResponseFromJavascript = verror.Register("v.io/wspr/veyron/services/wsprd/ipc/server.InvalidValidationResponseFromJavascript", verror.NoRetry, "{1:}{2:} Invalid validation response from javascript")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrCaveatValidationTimeout.ID), "{1:}{2:} Caveat validation has timed out")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrInvalidValidationResponseFromJavascript.ID), "{1:}{2:} Invalid validation response from javascript")
+}
+
+// NewErrCaveatValidationTimeout returns an error with the ErrCaveatValidationTimeout ID.
+func NewErrCaveatValidationTimeout(ctx *context.T) error {
+	return verror.New(ErrCaveatValidationTimeout, ctx)
+}
+
+// NewErrInvalidValidationResponseFromJavascript returns an error with the ErrInvalidValidationResponseFromJavascript ID.
+func NewErrInvalidValidationResponseFromJavascript(ctx *context.T) error {
+	return verror.New(ErrInvalidValidationResponseFromJavascript, ctx)
+}
diff --git a/services/wsprd/lib/testwriter/writer.go b/services/wsprd/lib/testwriter/writer.go
index 48dda10..06dbe72 100644
--- a/services/wsprd/lib/testwriter/writer.go
+++ b/services/wsprd/lib/testwriter/writer.go
@@ -18,7 +18,7 @@
 
 type Writer struct {
 	sync.Mutex
-	Stream []lib.Response
+	Stream []lib.Response // TODO Why not use channel?
 	err    error
 	// If this channel is set then a message will be sent
 	// to this channel after recieving a call to FinishMessage()
@@ -49,6 +49,21 @@
 
 }
 
+// ImmediatelyConsumeItem consumes an item on the stream without waiting.
+func (w *Writer) ImmediatelyConsumeItem() (lib.Response, error) {
+	w.Lock()
+	defer w.Unlock()
+
+	if len(w.Stream) < 1 {
+		return lib.Response{}, fmt.Errorf("Expected an item on the stream, none found")
+	}
+
+	item := w.Stream[0]
+	w.Stream = w.Stream[1:]
+
+	return item, nil
+}
+
 func (w *Writer) Error(err error) {
 	w.err = err
 }
diff --git a/services/wsprd/lib/writer.go b/services/wsprd/lib/writer.go
index fb29903..24b18d6 100644
--- a/services/wsprd/lib/writer.go
+++ b/services/wsprd/lib/writer.go
@@ -11,6 +11,7 @@
 	ResponseDispatcherLookup              = 5
 	ResponseAuthRequest                   = 6
 	ResponseCancel                        = 7
+	ResponseValidate                      = 8 // Request to validate caveats.
 )
 
 type Response struct {
diff --git a/services/wsprd/principal/blessings.go b/services/wsprd/principal/blessings.go
index ef6a281..ed17a65 100644
--- a/services/wsprd/principal/blessings.go
+++ b/services/wsprd/principal/blessings.go
@@ -5,11 +5,6 @@
 	"v.io/v23/security"
 )
 
-type BlessingsHandle struct {
-	Handle    int32
-	PublicKey string
-}
-
 func ConvertBlessingsToHandle(blessings security.Blessings, handle int32) *BlessingsHandle {
 	encoded, err := EncodePublicKey(blessings.PublicKey())
 	if err != nil {
diff --git a/services/wsprd/principal/blessings.vdl b/services/wsprd/principal/blessings.vdl
new file mode 100644
index 0000000..4ea082b
--- /dev/null
+++ b/services/wsprd/principal/blessings.vdl
@@ -0,0 +1,6 @@
+package principal
+
+type BlessingsHandle struct {
+    Handle    int32
+    PublicKey string
+}
\ No newline at end of file
diff --git a/services/wsprd/principal/blessings.vdl.go b/services/wsprd/principal/blessings.vdl.go
new file mode 100644
index 0000000..a5fbd04
--- /dev/null
+++ b/services/wsprd/principal/blessings.vdl.go
@@ -0,0 +1,23 @@
+// This file was auto-generated by the veyron vdl tool.
+// Source: blessings.vdl
+
+package principal
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+)
+
+type BlessingsHandle struct {
+	Handle    int32
+	PublicKey string
+}
+
+func (BlessingsHandle) __VDLReflect(struct {
+	Name string "v.io/wspr/veyron/services/wsprd/principal.BlessingsHandle"
+}) {
+}
+
+func init() {
+	vdl.Register((*BlessingsHandle)(nil))
+}
diff --git a/services/wsprd/principal/principal_test.go b/services/wsprd/principal/principal_test.go
index a59d9c8..147d150 100644
--- a/services/wsprd/principal/principal_test.go
+++ b/services/wsprd/principal/principal_test.go
@@ -96,8 +96,8 @@
 	if got, _ := bOrigin.ForContext(ctx("Foo")); !reflect.DeepEqual(got, want) {
 		return fmt.Errorf("with method 'Foo', got blessing: %v, want: %v", got, want)
 	}
-	if got, _ := bOrigin.ForContext(ctx("Bar")); got != nil {
-		return fmt.Errorf("with method 'Bar', got blessing: %v, want nil", got)
+	if got, _ := bOrigin.ForContext(ctx("Bar")); len(got) != 0 {
+		return fmt.Errorf("with method 'Bar', got blessing: %v, want empty", got)
 	}
 
 	unknownOrigin := "http://unknown.com:80"