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"