identity: Add pretty printing of caveats.

Make known caveats print our neater in the blessings table instead of
just dumping the String method of the Caveat.

Change-Id: I2111ac3be74835dac14c19a7a01a098ce4319d3e
diff --git a/services/identity/internal/caveats/mock_caveat_selector.go b/services/identity/internal/caveats/mock_caveat_selector.go
index 00412bf..f0209fd 100644
--- a/services/identity/internal/caveats/mock_caveat_selector.go
+++ b/services/identity/internal/caveats/mock_caveat_selector.go
@@ -7,6 +7,8 @@
 import (
 	"net/http"
 	"time"
+
+	"v.io/v23/security"
 )
 
 type mockCaveatSelector struct {
@@ -32,6 +34,7 @@
 		CaveatInfo{"Revocation", []interface{}{}},
 		CaveatInfo{"Expiry", []interface{}{time.Now().Add(time.Hour)}},
 		CaveatInfo{"Method", []interface{}{"methodA", "methodB"}},
+		CaveatInfo{"PeerBlessings", []interface{}{security.BlessingPattern("peerA"), security.BlessingPattern("peerB")}},
 	}
 	state = s.state
 	additionalExtension = "test-extension"
diff --git a/services/identity/internal/oauth/handler.go b/services/identity/internal/oauth/handler.go
index 95d8f38..6dddbf5 100644
--- a/services/identity/internal/oauth/handler.go
+++ b/services/identity/internal/oauth/handler.go
@@ -163,7 +163,7 @@
 
 	type tmplentry struct {
 		Timestamp      time.Time
-		Caveats        []security.Caveat
+		Caveats        []string
 		RevocationTime time.Time
 		Blessed        security.Blessings
 		Token          string
@@ -194,9 +194,14 @@
 			tmplEntry := tmplentry{
 				Error:     entry.DecodeError,
 				Timestamp: entry.Timestamp,
-				Caveats:   entry.Caveats,
 				Blessed:   entry.Blessings,
 			}
+			if len(entry.Caveats) > 0 {
+				if tmplEntry.Caveats, err = prettyPrintCaveats(entry.Caveats); err != nil {
+					vlog.Errorf("Failed to pretty print caveats: %v", err)
+					tmplEntry.Error = fmt.Errorf("failed to pretty print caveats: %v", err)
+				}
+			}
 			if len(entry.RevocationCaveatID) > 0 && h.args.RevocationManager != nil {
 				if revocationTime := h.args.RevocationManager.GetRevocationTime(entry.RevocationCaveatID); revocationTime != nil {
 					tmplEntry.RevocationTime = *revocationTime
@@ -217,6 +222,35 @@
 	}
 }
 
+// prettyPrintCaveats returns a user friendly string for vanadium standard caveat.
+// Unrecognized caveats will fall back to the Caveat's String() method.
+func prettyPrintCaveats(cavs []security.Caveat) ([]string, error) {
+	s := make([]string, len(cavs))
+	for i, cav := range cavs {
+		if cav.Id == security.PublicKeyThirdPartyCaveatX.Id {
+			c := cav.ThirdPartyDetails()
+			s[i] = fmt.Sprintf("ThirdPartyCaveat: Requires discharge from %v (ID=%q)", c.Location(), c.ID())
+			continue
+		}
+
+		var param interface{}
+		if err := vom.Decode(cav.ParamVom, &param); err != nil {
+			return nil, err
+		}
+		switch cav.Id {
+		case security.ExpiryCaveatX.Id:
+			s[i] = fmt.Sprintf("Expires at %v", param)
+		case security.MethodCaveatX.Id:
+			s[i] = fmt.Sprintf("Restricted to methods %v", param)
+		case security.PeerBlessingsCaveat.Id:
+			s[i] = fmt.Sprintf("Restricted to peers with blessings %v", param)
+		default:
+			s[i] = cav.String()
+		}
+	}
+	return s, nil
+}
+
 func (h *handler) revoke(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	const (
diff --git a/services/identity/internal/templates/list_blessings.go b/services/identity/internal/templates/list_blessings.go
index 4703b04..f66601d 100644
--- a/services/identity/internal/templates/list_blessings.go
+++ b/services/identity/internal/templates/list_blessings.go
@@ -106,8 +106,11 @@
             <td>{{.Blessed.PublicKey}}</td>
             <td><div class="unixtime" data-unixtime={{.Timestamp.Unix}}>{{.Timestamp.String}}</div></td>
             <td class="td-wide">
-            {{range .Caveats}}
-              {{.}}</br>
+            {{range $index, $cav := .Caveats}}
+              {{if ne $index 0}}
+                <hr>
+              {{end}}
+              {{$cav}}</br>
             {{end}}
             </td>
             <td>