veyron/runtimes/google/ipc: use discharges for third-party caveat authorization

Change-Id: I385e60c5f7115a2a3472481a098335b8c2c8cd85
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index 49b3915..0aa2458 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -40,6 +40,8 @@
 	vcMapMu sync.Mutex
 	// TODO(ashankar): Additionally, should vcMap be keyed with other options also?
 	vcMap map[string]*vcInfo // map from endpoint.String() to vc info
+
+	dischargeCache dischargeCache
 }
 
 type vcInfo struct {
@@ -50,10 +52,11 @@
 
 func InternalNewClient(streamMgr stream.Manager, ns naming.Namespace, opts ...ipc.ClientOpt) (ipc.Client, error) {
 	c := &client{
-		streamMgr:   streamMgr,
-		ns:          ns,
-		vcMap:       make(map[string]*vcInfo),
-		callTimeout: defaultCallTimeout,
+		streamMgr:      streamMgr,
+		ns:             ns,
+		vcMap:          make(map[string]*vcInfo),
+		callTimeout:    defaultCallTimeout,
+		dischargeCache: dischargeCache{CaveatDischargeMap: make(security.CaveatDischargeMap)},
 	}
 	for _, opt := range opts {
 		// Collect all client opts that are also vc opts.
@@ -152,8 +155,11 @@
 			flow.Close()
 			continue
 		}
+
+		discharges := c.prepareDischarges(ctx, flow.LocalID(), flow.RemoteID(), method, opts)
+
 		lastErr = nil
-		fc := newFlowClient(flow)
+		fc := newFlowClient(flow, &c.dischargeCache, discharges)
 		if verr := fc.start(suffix, method, args, timeout, blessing); verr != nil {
 			return nil, verr
 		}
@@ -172,7 +178,7 @@
 	if server == nil {
 		return nil, fmt.Errorf("server identity cannot be nil")
 	}
-	// TODO(ataly): Fetch third-party discharges from the server.
+	// TODO(ataly,andreser): Check the third-party discharges the server presents
 	// TODO(ataly): What should the label be for the context? Typically the label is the security.Label
 	// of the method but we don't have that information here at the client.
 	authID, err := server.Authorize(isecurity.NewContext(isecurity.ContextArgs{
@@ -236,16 +242,21 @@
 	flow     stream.Flow  // the underlying flow
 	response ipc.Response // each decoded response message is kept here
 
+	discharges     []security.ThirdPartyDischarge // discharges used for this request
+	dischargeCache *dischargeCache                // client-global discharge cache reference type
+
 	sendClosedMu sync.Mutex
 	sendClosed   bool // is the send side already closed? GUARDED_BY(sendClosedMu)
 }
 
-func newFlowClient(flow stream.Flow) *flowClient {
+func newFlowClient(flow stream.Flow, dischargeCache *dischargeCache, discharges []security.ThirdPartyDischarge) *flowClient {
 	return &flowClient{
 		// TODO(toddw): Support different codecs
-		dec:  vom.NewDecoder(flow),
-		enc:  vom.NewEncoder(flow),
-		flow: flow,
+		dec:            vom.NewDecoder(flow),
+		enc:            vom.NewEncoder(flow),
+		flow:           flow,
+		discharges:     discharges,
+		dischargeCache: dischargeCache,
 	}
 }
 
@@ -258,11 +269,12 @@
 
 func (fc *flowClient) start(suffix, method string, args []interface{}, timeout time.Duration, blessing security.PublicID) verror.E {
 	req := ipc.Request{
-		Suffix:      suffix,
-		Method:      method,
-		NumPosArgs:  uint64(len(args)),
-		Timeout:     int64(timeout),
-		HasBlessing: blessing != nil,
+		Suffix:        suffix,
+		Method:        method,
+		NumPosArgs:    uint64(len(args)),
+		Timeout:       int64(timeout),
+		HasBlessing:   blessing != nil,
+		NumDischarges: uint64(len(fc.discharges)),
 	}
 	if err := fc.enc.Encode(req); err != nil {
 		return fc.close(verror.BadProtocolf("ipc: request encoding failed: %v", err))
@@ -272,6 +284,11 @@
 			return fc.close(verror.BadProtocolf("ipc: blessing encoding failed: %v", err))
 		}
 	}
+	for _, d := range fc.discharges {
+		if err := fc.enc.Encode(d); err != nil {
+			return fc.close(verror.BadProtocolf("ipc: failed to encode discharge for %x: %v", d.CaveatID(), err))
+		}
+	}
 	for ix, arg := range args {
 		if err := fc.enc.Encode(arg); err != nil {
 			return fc.close(verror.BadProtocolf("ipc: arg %d encoding failed: %v", ix, err))
@@ -374,6 +391,14 @@
 		}
 	}
 	if fc.response.Error != nil {
+		if verror.Is(fc.response.Error, verror.NotAuthorized) {
+			// In case the error was caused by a bad discharge, we do not want to get stuck
+			// with retrying again and again with this discharge. As there is no direct way
+			// to detect it, we conservatively flush all discharges we used from the cache.
+			// TODO(ataly,andreser): add verror.BadDischarge and handle it explicitly?
+			vlog.VI(3).Infof("Discarding %d discharges as RPC failed with %v", len(fc.discharges), fc.response.Error)
+			fc.dischargeCache.Invalidate(fc.discharges...)
+		}
 		return fc.close(verror.ConvertWithDefault(verror.Internal, fc.response.Error))
 	}
 	if got, want := fc.response.NumPosResults, uint64(len(resultptrs)); got != want {
diff --git a/runtimes/google/ipc/discharges.go b/runtimes/google/ipc/discharges.go
new file mode 100644
index 0000000..75d99f3
--- /dev/null
+++ b/runtimes/google/ipc/discharges.go
@@ -0,0 +1,179 @@
+package ipc
+
+import (
+	"sync"
+	"veyron2"
+	"veyron2/context"
+	"veyron2/ipc"
+	"veyron2/security"
+	"veyron2/vdl"
+	"veyron2/vlog"
+)
+
+// prepareDischarges retrieves the caveat discharges required for using blessing
+// at server. The discharges are either found in the client cache, in the call
+// options, or requested from the discharge issuer indicated on the caveat.
+// Note that requesting a discharge is an ipc call, so one copy of this
+// function must be able to successfully terminate while another is blocked.
+func (c *client) prepareDischarges(ctx context.T, blessing, server security.PublicID,
+	method string, opts []ipc.CallOpt) (ret []security.ThirdPartyDischarge) {
+	// TODO(andreser,ataly): figure out whether this should return an error and how that should be handled
+	// Missing discharges do not necessarily mean the blessing is invalid (e.g., SetID)
+	if blessing == nil {
+		return
+	}
+
+	var caveats []security.ThirdPartyCaveat
+	for _, cav := range blessing.ThirdPartyCaveats() {
+		if server.Match(cav.Service) {
+			caveats = append(caveats, cav.Caveat.(security.ThirdPartyCaveat))
+		}
+	}
+	if len(caveats) == 0 {
+		return
+	}
+
+	discharges := make([]security.ThirdPartyDischarge, len(caveats))
+	dischargesFromOpts(caveats, opts, discharges)
+	c.dischargeCache.Discharges(caveats, discharges)
+	if shouldFetchDischarges(opts) {
+		c.fetchDischarges(ctx, caveats, opts, discharges)
+	}
+	for _, d := range discharges {
+		if d != nil {
+			ret = append(ret, d)
+		}
+	}
+	return
+}
+
+// dischargeCache is a concurrency-safe cache for third party caveat discharges.
+type dischargeCache struct {
+	sync.RWMutex
+	security.CaveatDischargeMap // GUARDED_BY(RWMutex)
+}
+
+// Add inserts the argument to the cache, possibly overwriting previous
+// discharges for the same caveat.
+func (dcc *dischargeCache) Add(discharges ...security.ThirdPartyDischarge) {
+	dcc.Lock()
+	for _, d := range discharges {
+		dcc.CaveatDischargeMap[d.CaveatID()] = d
+	}
+	dcc.Unlock()
+}
+
+// Invalidate removes discharges from the cache.
+func (dcc *dischargeCache) Invalidate(discharges ...security.ThirdPartyDischarge) {
+	dcc.Lock()
+	for _, d := range discharges {
+		if dcc.CaveatDischargeMap[d.CaveatID()] == d {
+			delete(dcc.CaveatDischargeMap, d.CaveatID())
+		}
+	}
+	dcc.Unlock()
+}
+
+// Discharges takes a slice of caveats and a slice of discharges of the same
+// length and fills in nil entries in the discharges slice with discharges
+// from the cache (if there are any).
+// REQUIRES: len(caveats) == len(out)
+func (dcc *dischargeCache) Discharges(caveats []security.ThirdPartyCaveat, out []security.ThirdPartyDischarge) {
+	dcc.Lock()
+	for i, d := range out {
+		if d != nil {
+			continue
+		}
+		out[i] = dcc.CaveatDischargeMap[caveats[i].ID()]
+	}
+	dcc.Unlock()
+}
+
+// dischargesFromOpts fills in the nils in the out argument with discharges in
+// opts that match the caveat at the same index in caveats.
+// REQUIRES: len(caveats) == len(out)
+func dischargesFromOpts(caveats []security.ThirdPartyCaveat, opts []ipc.CallOpt,
+	out []security.ThirdPartyDischarge) {
+	for _, opt := range opts {
+		d, ok := opt.(veyron2.DischargeOpt)
+		if !ok {
+			continue
+		}
+		for i, cav := range caveats {
+			if out[i] == nil && d.CaveatID() == cav.ID() {
+				out[i] = d
+			}
+		}
+	}
+}
+
+// fetchDischarges fills in out by fetching discharges for caveats from the
+// appropriate discharge service. Since there may be dependencies in the
+// caveats, fetchDischarges keeps retrying until either all discharges can be
+// fetched or no new discharges are fetched.
+// REQUIRES: len(caveats) == len(out)
+func (c *client) fetchDischarges(ctx context.T, caveats []security.ThirdPartyCaveat, opts []ipc.CallOpt, out []security.ThirdPartyDischarge) {
+	opts = append([]ipc.CallOpt{dontFetchDischarges{}}, opts...)
+	var wg sync.WaitGroup
+	for {
+		type fetched struct {
+			idx       int
+			discharge security.ThirdPartyDischarge
+		}
+		discharges := make(chan fetched, len(caveats))
+		for i := range caveats {
+			if out[i] != nil {
+				continue
+			}
+			wg.Add(1)
+			go func(i int, cav security.ThirdPartyCaveat) {
+				defer wg.Done()
+				vlog.VI(3).Infof("Fetching discharge for %T from %v", cav.ID(), cav, cav.Location())
+				call, err := c.StartCall(ctx, cav.Location(), "Discharge", []interface{}{cav}, opts...)
+				if err != nil {
+					vlog.VI(3).Infof("Discharge fetch for caveat %T from %v failed: %v", cav, cav.Location(), err)
+					return
+				}
+				var dAny vdl.Any
+				// TODO(ashankar): Retry on errors like no-route-to-service, name resolution failures etc.
+				ierr := call.Finish(&dAny, &err)
+				if ierr != nil || err != nil {
+					vlog.VI(3).Infof("Discharge fetch for caveat %T from %v failed: (%v, %v)", cav, cav.Location(), err, ierr)
+					return
+				}
+				d, ok := dAny.(security.ThirdPartyDischarge)
+				if !ok {
+					vlog.Errorf("fetchDischarges: server at %s sent a %T (%v) instead of a ThirdPartyDischarge", cav.Location(), dAny, dAny)
+				}
+				discharges <- fetched{i, d}
+			}(i, caveats[i])
+		}
+		wg.Wait()
+		close(discharges)
+		var got int
+		for fetched := range discharges {
+			c.dischargeCache.Add(fetched.discharge)
+			out[fetched.idx] = fetched.discharge
+			got++
+		}
+		vlog.VI(2).Infof("fetchDischarges: got %d discharges", got)
+		if got == 0 {
+			return
+		}
+	}
+}
+
+// dontFetchDischares is an ipc.CallOpt that indicates that no extre ipc-s
+// should be done to fetch discharges for the call with this opt.
+// Discharges in the cache and in the call options are still used.
+type dontFetchDischarges struct{}
+
+func (dontFetchDischarges) IPCCallOpt() {}
+func shouldFetchDischarges(opts []ipc.CallOpt) bool {
+	for _, opt := range opts {
+		if _, ok := opt.(dontFetchDischarges); ok {
+			return false
+		}
+	}
+	return true
+}
diff --git a/runtimes/google/ipc/flow_test.go b/runtimes/google/ipc/flow_test.go
index 7009258..8850b21 100644
--- a/runtimes/google/ipc/flow_test.go
+++ b/runtimes/google/ipc/flow_test.go
@@ -117,7 +117,7 @@
 	ipcServer := &server{disp: testDisp{newEchoInvoker}}
 	for _, test := range tests {
 		clientFlow, serverFlow := newTestFlows()
-		client := newFlowClient(clientFlow)
+		client := newFlowClient(clientFlow, nil, nil)
 		server := newFlowServer(serverFlow, ipcServer)
 		err := client.start(test.suffix, test.method, test.args, time.Duration(0), nil)
 		if err != nil {
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index 86b5a18..66e1cc7 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -30,17 +30,46 @@
 	"veyron2/ipc/stream"
 	"veyron2/naming"
 	"veyron2/security"
+	"veyron2/vdl"
 	"veyron2/verror"
 	"veyron2/vlog"
+	"veyron2/vom"
 )
 
 var (
+	errAuthorizer = errors.New("ipc: application Authorizer denied access")
 	errMethod = verror.Abortedf("server returned an error")
 	clientID  security.PrivateID
 	serverID  security.PrivateID
+	clock     = new(fakeClock)
 )
 
-var errAuthorizer = errors.New("ipc: application Authorizer denied access")
+type fakeClock struct {
+	sync.Mutex
+	time int
+}
+
+func (c *fakeClock) Now() int {
+	c.Lock()
+	defer c.Unlock()
+	return c.time
+}
+
+func (c *fakeClock) Advance(steps uint) {
+	c.Lock()
+	c.time += int(steps)
+	c.Unlock()
+}
+
+type fakeTimeCaveat int
+
+func (c fakeTimeCaveat) Validate(security.Context) error {
+	now := clock.Now()
+	if now > int(c) {
+		return fmt.Errorf("fakeTimeCaveat expired: now=%d > then=%d", now, c)
+	}
+	return nil
+}
 
 type fakeContext struct{}
 
@@ -99,6 +128,18 @@
 	return "UnauthorizedResult", fmt.Errorf("Unauthorized should never be called")
 }
 
+type dischargeServer struct{}
+
+func (*dischargeServer) Discharge(ctx ipc.ServerCall, caveat vdl.Any) (vdl.Any, error) {
+	c, ok := caveat.(security.ThirdPartyCaveat)
+	if !ok {
+		return nil, fmt.Errorf("discharger: unknown caveat(%T)", caveat)
+	}
+	// Add a fakeTimeCaveat to allow the discharge to expire
+	expiry := fakeTimeCaveat(clock.Now())
+	return serverID.MintDischarge(c, ctx, time.Hour, []security.ServiceCaveat{security.UniversalCaveat(expiry)})
+}
+
 type testServerAuthorizer struct{}
 
 func (testServerAuthorizer) Authorize(c security.Context) error {
@@ -113,18 +154,22 @@
 func (t testServerDisp) Lookup(suffix string) (ipc.Invoker, security.Authorizer, error) {
 	// If suffix is "nilAuth" we use default authorization, if it is "aclAuth" we
 	// use an ACL based authorizer, and otherwise we use the custom testServerAuthorizer.
-	if suffix == "nilAuth" {
-		return ipc.ReflectInvoker(t.server), nil, nil
-	}
-	if suffix == "aclAuth" {
+	var authorizer security.Authorizer
+	switch suffix {
+	case "discharger":
+		return ipc.ReflectInvoker(&dischargeServer{}), testServerAuthorizer{}, nil
+	case "nilAuth":
+		authorizer = nil
+	case "aclAuth":
 		// Only authorize clients matching patterns "client" or "server/*".
-		acl := security.ACL{
+		authorizer = security.NewACLAuthorizer(security.ACL{
 			"server/*": security.LabelSet(security.AdminLabel),
 			"client":   security.LabelSet(security.AdminLabel),
-		}
-		return ipc.ReflectInvoker(t.server), security.NewACLAuthorizer(acl), nil
+		})
+	default:
+		authorizer = testServerAuthorizer{}
 	}
-	return ipc.ReflectInvoker(t.server), testServerAuthorizer{}, nil
+	return ipc.ReflectInvoker(t.server), authorizer, nil
 }
 
 // namespace is a simple partial implementation of naming.Namespace.  In
@@ -228,6 +273,9 @@
 	if err := server.Serve("mountpoint/server", disp); err != nil {
 		t.Errorf("server.Publish failed: %v", err)
 	}
+	if err := server.Serve("mountpoint/discharger", disp); err != nil {
+		t.Errorf("server.Publish for discharger failed: %v", err)
+	}
 	return ep, server
 }
 
@@ -315,6 +363,27 @@
 	return derivedID
 }
 
+// deriveForThirdPartyCaveats creates a SetPrivateID that can be used for
+//  1. talking to the server, if the caveats are fulfilled
+//  2. getting discharges, even if the caveats are not fulfilled
+// As an identity with an unfulfilled caveat is invalid (even for asking for  a
+// discharge), this function creates a set of two identities. The first will
+// have the caveats, the second will always be valid, but only for getting
+// discharges. The client presents both blessings in both cases, the discharger
+// ignores the first if it is invalid.
+func deriveForThirdPartyCaveats(blessor security.PrivateID, name string, caveats ...security.ServiceCaveat) security.PrivateID {
+	id := derive(blessor, name, caveats...)
+	dischargeID, err := id.Derive(bless(blessor, id.PublicID(), name, security.UniversalCaveat(caveat.MethodRestriction{"Discharge"})))
+	if err != nil {
+		panic(err)
+	}
+	id, err = isecurity.NewSetPrivateID(id, dischargeID)
+	if err != nil {
+		panic(err)
+	}
+	return id
+}
+
 func matchesErrorPattern(err error, pattern string) bool {
 	if (len(pattern) == 0) != (err == nil) {
 		return false
@@ -370,20 +439,23 @@
 }
 
 func TestStartCall(t *testing.T) {
-	authorizeErr := "not authorized because"
-	nameErr := "does not match the provided pattern"
+	const (
+		authorizeErr = "not authorized because"
+		nameErr      = "does not match the provided pattern"
+	)
+	var (
+		now        = time.Now()
+		cavOnlyV1  = security.UniversalCaveat(caveat.PeerIdentity{"client/v1"})
+		cavExpired = security.ServiceCaveat{
+			Service: security.AllPrincipals,
+			Caveat:  &caveat.Expiry{IssueTime: now, ExpiryTime: now},
+		}
 
-	cavOnlyV1 := security.UniversalCaveat(caveat.PeerIdentity{"client/v1"})
-	now := time.Now()
-	cavExpired := security.ServiceCaveat{
-		Service: security.AllPrincipals,
-		Caveat:  &caveat.Expiry{IssueTime: now, ExpiryTime: now},
-	}
-
-	clientV1ID := derive(clientID, "v1")
-	clientV2ID := derive(clientID, "v2")
-	serverV1ID := derive(serverID, "v1", cavOnlyV1)
-	serverExpiredID := derive(serverID, "expired", cavExpired)
+		clientV1ID      = derive(clientID, "v1")
+		clientV2ID      = derive(clientID, "v2")
+		serverV1ID      = derive(serverID, "v1", cavOnlyV1)
+		serverExpiredID = derive(serverID, "expired", cavExpired)
+	)
 
 	tests := []struct {
 		clientID, serverID security.PrivateID
@@ -546,21 +618,46 @@
 	}
 }
 
+func mkThirdPartyCaveat(discharger security.PublicID, location string, c security.Caveat) security.ThirdPartyCaveat {
+	tpc, err := caveat.NewPublicKeyCaveat(c, discharger, location)
+	if err != nil {
+		panic(err)
+	}
+	return tpc
+}
+
 func TestRPCAuthorization(t *testing.T) {
-	cavOnlyEcho := security.ServiceCaveat{
-		Service: security.AllPrincipals,
-		Caveat:  caveat.MethodRestriction{"Echo"},
-	}
-	now := time.Now()
-	cavExpired := security.ServiceCaveat{
-		Service: security.AllPrincipals,
-		Caveat:  &caveat.Expiry{IssueTime: now, ExpiryTime: now},
-	}
+	var (
+		now = time.Now()
+		// First-party caveats
+		cavOnlyEcho = security.ServiceCaveat{
+			Service: security.AllPrincipals,
+			Caveat:  caveat.MethodRestriction{"Echo"},
+		}
+		cavExpired = security.ServiceCaveat{
+			Service: security.AllPrincipals,
+			Caveat:  &caveat.Expiry{IssueTime: now, ExpiryTime: now},
+		}
+		// Third-party caveats
+		// The Discharge service can be run by any identity, but in our tests the same server runs
+		// a Discharge service as well.
+		dischargerID = serverID.PublicID()
+		cavTPValid   = security.ServiceCaveat{
+			Service: security.PrincipalPattern(serverID.PublicID().Names()[0]),
+			Caveat:  mkThirdPartyCaveat(dischargerID, "mountpoint/server/discharger", &caveat.Expiry{ExpiryTime: now.Add(24 * time.Hour)}),
+		}
+		cavTPExpired = security.ServiceCaveat{
+			Service: security.PrincipalPattern(serverID.PublicID().Names()[0]),
+			Caveat:  mkThirdPartyCaveat(dischargerID, "mountpoint/server/discharger", &caveat.Expiry{IssueTime: now, ExpiryTime: now}),
+		}
 
-	blessedByServerOnlyEcho := derive(serverID, "onlyEcho", cavOnlyEcho)
-	blessedByServerExpired := derive(serverID, "expired", cavExpired)
-	blessedByClient := derive(clientID, "blessed")
-
+		// Client blessings that will be tested
+		blessedByServerOnlyEcho  = derive(serverID, "onlyEcho", cavOnlyEcho)
+		blessedByServerExpired   = derive(serverID, "expired", cavExpired)
+		blessedByServerTPValid   = deriveForThirdPartyCaveats(serverID, "tpvalid", cavTPValid)
+		blessedByServerTPExpired = deriveForThirdPartyCaveats(serverID, "tpexpired", cavTPExpired)
+		blessedByClient          = derive(clientID, "blessed")
+	)
 	const (
 		expiredIDErr = "forbids credential from being used at this time"
 		aclAuthErr   = "no matching ACL entry found"
@@ -599,7 +696,6 @@
 		{clientID, "mountpoint/server/aclAuth", "Closure", nil, nil, ""},
 		{blessedByClient, "mountpoint/server/aclAuth", "Closure", nil, nil, aclAuthErr},
 		{serverID, "mountpoint/server/aclAuth", "Closure", nil, nil, ""},
-
 		// All methods except "Unauthorized" are authorized by the custom authorizer.
 		{clientID, "mountpoint/server/suffix", "Echo", v{"foo"}, v{`method:"Echo",suffix:"suffix",arg:"foo"`}, ""},
 		{blessedByClient, "mountpoint/server/suffix", "Echo", v{"foo"}, v{`method:"Echo",suffix:"suffix",arg:"foo"`}, ""},
@@ -611,6 +707,9 @@
 		{clientID, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, "application Authorizer denied access"},
 		{blessedByClient, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, "application Authorizer denied access"},
 		{serverID, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, "application Authorizer denied access"},
+		// Third-party caveat discharges should be fetched and forwarded
+		{blessedByServerTPValid, "mountpoint/server/suffix", "Echo", v{"foo"}, v{`method:"Echo",suffix:"suffix",arg:"foo"`}, ""},
+		{blessedByServerTPExpired, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, "missing discharge"},
 	}
 	name := func(t testcase) string {
 		return fmt.Sprintf("%q RPCing %s.%s(%v)", t.clientID.PublicID(), t.name, t.method, t.args)
@@ -640,6 +739,49 @@
 	}
 }
 
+type alwaysValidCaveat struct{}
+
+func (alwaysValidCaveat) Validate(security.Context) error { return nil }
+
+func TestDischargePurgeFromCache(t *testing.T) {
+	var (
+		dischargerID = serverID.PublicID()
+		caveat       = mkThirdPartyCaveat(dischargerID, "mountpoint/server/discharger", alwaysValidCaveat{})
+		clientCID    = deriveForThirdPartyCaveats(serverID, "client", security.UniversalCaveat(caveat))
+	)
+	b := createBundle(t, clientCID, serverID, &testServer{})
+	defer b.cleanup(t)
+
+	call := func() error {
+		call, err := b.client.StartCall(&fakeContext{}, "mountpoint/server/suffix", "Echo", []interface{}{"batman"})
+		if err != nil {
+			return fmt.Errorf("client.StartCall failed: %v", err)
+		}
+		var got string
+		if err := call.Finish(&got); err != nil {
+			return fmt.Errorf("client.Finish failed: %v", err)
+		}
+		if want := `method:"Echo",suffix:"suffix",arg:"batman"`; got != want {
+			return fmt.Errorf("Got [%v] want [%v]", got, want)
+		}
+		return nil
+	}
+
+	// First call should succeed
+	if err := call(); err != nil {
+		t.Fatal(err)
+	}
+	// Advance virtual clock, which will invalidate the discharge
+	clock.Advance(1)
+	if err := call(); !matchesErrorPattern(err, "fakeTimeCaveat expired") {
+		t.Errorf("Got error [%v] wanted to match pattern 'fakeTimeCaveat expired'", err)
+	}
+	// But retrying will succeed since the discharge should be purged from cache and refreshed
+	if err := call(); err != nil {
+		t.Fatal(err)
+	}
+}
+
 type cancelTestServer struct {
 	started   chan struct{}
 	cancelled chan struct{}
@@ -1095,4 +1237,6 @@
 
 	blackbox.CommandTable["runServer"] = runServer
 	blackbox.CommandTable["runProxy"] = runProxy
+
+	vom.Register(fakeTimeCaveat(0))
 }
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index f162fdf..1a88570 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -332,9 +332,10 @@
 		server: server,
 		disp:   server.disp,
 		// TODO(toddw): Support different codecs
-		dec:  vom.NewDecoder(flow),
-		enc:  vom.NewEncoder(flow),
-		flow: flow,
+		dec:        vom.NewDecoder(flow),
+		enc:        vom.NewEncoder(flow),
+		flow:       flow,
+		discharges: make(security.CaveatDischargeMap),
 	}
 }
 
@@ -450,7 +451,14 @@
 		// should servers be able to assume that a blessing is something that does not
 		// have the authorizations that the server's own identity has?
 	}
-
+	// Receive third party caveat discharges the client sent
+	for i := uint64(0); i < req.NumDischarges; i++ {
+		var d security.ThirdPartyDischarge
+		if err := fs.dec.Decode(&d); err != nil {
+			return nil, verror.BadProtocolf("ipc: decoding discharge %d of %d failed: %v", i, req.NumDischarges, err)
+		}
+		fs.discharges[d.CaveatID()] = d
+	}
 	// Lookup the invoker.
 	invoker, auth, suffix, verr := fs.lookup(req.Suffix)
 	fs.suffix = suffix // with leading /'s stripped
@@ -461,7 +469,6 @@
 	numArgs := int(req.NumPosArgs)
 	argptrs, label, err := invoker.Prepare(req.Method, numArgs)
 	fs.label = label
-	// TODO(ataly, ashankar): Populate the "discharges" field from the request object req.
 	if err != nil {
 		return nil, verror.Makef(verror.ErrorID(err), "%s: name: %q", err, req.Suffix)
 	}