cmd/principal: fulfill the spirit of v.io/i/201 by prominently printing expiry

The goal of https://github.com/veyron/release-issues/issues/1855 was to make it
obvious to users when their credentials were expired.  Suharsh's change
(v.io/c/11377) added the mechanism to blessings to expose this, but only used it
for principal dumpblessings (which is not something users typically run --
unless they already suspect that their blessings are expired).

This change adds the [EXPIRED] tag to the principal dump command (both with and
without the -s option). This will ensure that it shows up in the vbash prompt,
and also that people running principal dump by hand see it.

Change-Id: I384ae627817450016ab6a842c6658a718baf8ad3
diff --git a/cmd/principal/main.go b/cmd/principal/main.go
index f2a3de6..b604464 100644
--- a/cmd/principal/main.go
+++ b/cmd/principal/main.go
@@ -91,10 +91,19 @@
 		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline.Env, args []string) error {
 			p := v23.GetPrincipal(ctx)
 			if flagDumpShort {
-				fmt.Printf("%v\n", p.BlessingStore().Default())
+				fmt.Printf("%s\n", printAnnotatedBlessingsNames(p.BlessingStore().Default()))
 				return nil
 			}
 			fmt.Printf("Public key : %v\n", p.PublicKey())
+			// NOTE(caprita): We print the default blessings name
+			// twice (it's also printed as part of the blessing
+			// store below) -- the reason we print it here is to
+			// expose whether the blessings are expired.  Ideally,
+			// the blessings store would print the expiry
+			// information about each blessing in the store, but
+			// that would require deeper changes beyond the
+			// principal tool.
+			fmt.Printf("Default Blessings : %s\n", printAnnotatedBlessingsNames(p.BlessingStore().Default()))
 			fmt.Println("---------------- BlessingStore ----------------")
 			fmt.Printf("%v", p.BlessingStore().DebugString())
 			fmt.Println("---------------- BlessingRoots ----------------")
@@ -127,12 +136,7 @@
 			if err != nil {
 				return fmt.Errorf("failed to decode certificate chains: %v", err)
 			}
-			// If the Blessings are expired, print a message saying so.
-			expiredMessage := ""
-			if exp := blessings.Expiry(); !exp.IsZero() && exp.Before(time.Now()) {
-				expiredMessage = " [EXPIRED]"
-			}
-			fmt.Printf("Blessings          : %v%s\n", blessings, expiredMessage)
+			fmt.Printf("Blessings          : %s\n", printAnnotatedBlessingsNames(blessings))
 			fmt.Printf("PublicKey          : %v\n", blessings.PublicKey())
 			fmt.Printf("Certificate chains : %d\n", len(wire.CertificateChains))
 			for idx, chain := range wire.CertificateChains {
@@ -787,6 +791,15 @@
 	}
 )
 
+func printAnnotatedBlessingsNames(b security.Blessings) string {
+	// If the Blessings are expired, print a message saying so.
+	expiredMessage := ""
+	if exp := b.Expiry(); !exp.IsZero() && exp.Before(time.Now()) {
+		expiredMessage = " [EXPIRED]"
+	}
+	return fmt.Sprintf("%v%s", b, expiredMessage)
+}
+
 func blessArgs(args []string) (tobless, extension, remoteKey, remoteToken string, err error) {
 	if len(flagRemoteArgFile) > 0 && (len(flagBlessRemoteKey)+len(flagBlessRemoteToken) > 0) {
 		return "", "", "", "", fmt.Errorf("--remote-key and --remote-token cannot be provided with --remote-arg-file")
diff --git a/cmd/principal/principal_v23_test.go b/cmd/principal/principal_v23_test.go
index 734b4d1..9e80947 100644
--- a/cmd/principal/principal_v23_test.go
+++ b/cmd/principal/principal_v23_test.go
@@ -128,9 +128,10 @@
 
 func V23TestDump(t *v23tests.T) {
 	var (
-		outputDir = t.NewTempDir("")
-		bin       = t.BuildGoPkg("v.io/x/ref/cmd/principal")
-		aliceDir  = filepath.Join(outputDir, "alice")
+		outputDir       = t.NewTempDir("")
+		bin             = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		aliceDir        = filepath.Join(outputDir, "alice")
+		aliceExpiredDir = filepath.Join(outputDir, "alice-expired")
 	)
 
 	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
@@ -138,6 +139,7 @@
 	blessEnv := credEnv(aliceDir)
 	got := removePublicKeys(bin.WithEnv(blessEnv).Start("dump").Output())
 	want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Default Blessings : alice
 ---------------- BlessingStore ----------------
 Default Blessings                alice
 Peer pattern                     Blessings
@@ -149,6 +151,35 @@
 	if want != got {
 		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
 	}
+
+	got = bin.WithEnv(blessEnv).Start("dump", "-s").Output()
+	want = "alice\n"
+	if want != got {
+		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
+
+	bin.Start("--v23.credentials="+aliceDir, "fork", "--for", "-1h", aliceExpiredDir, "expired").WaitOrDie(os.Stdout, os.Stderr)
+	blessEnv = credEnv(aliceExpiredDir)
+	got = removePublicKeys(bin.WithEnv(blessEnv).Start("dump").Output())
+	want = `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Default Blessings : alice/expired [EXPIRED]
+---------------- BlessingStore ----------------
+Default Blessings                alice/expired
+Peer pattern                     Blessings
+...                              alice/expired
+---------------- BlessingRoots ----------------
+Public key                                        Pattern
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX   [alice]
+`
+	if want != got {
+		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
+
+	got = bin.WithEnv(blessEnv).Start("dump", "-s").Output()
+	want = "alice/expired [EXPIRED]\n"
+	if want != got {
+		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
 }
 
 func V23TestGetRecognizedRoots(t *v23tests.T) {
@@ -286,6 +317,7 @@
 	// first "bless" command. (alice/friend/carol).
 	got := removePublicKeys(bin.Start("--v23.credentials="+carolDir, "dump").Output())
 	want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Default Blessings : alice/friend/carol
 ---------------- BlessingStore ----------------
 Default Blessings                alice/friend/carol
 Peer pattern                     Blessings
@@ -324,6 +356,7 @@
 	{
 		got := removePublicKeys(bin.Start("--v23.credentials="+alicePhoneDir, "dump").Output())
 		want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Default Blessings : alice/phone
 ---------------- BlessingStore ----------------
 Default Blessings                alice/phone
 Peer pattern                     Blessings
@@ -359,6 +392,7 @@
 	{
 		got := removePublicKeys(bin.Start("--v23.credentials="+alicePhoneCalendarDir, "dump").Output())
 		want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
+Default Blessings : alice/phone/calendar
 ---------------- BlessingStore ----------------
 Default Blessings                alice/phone/calendar
 Peer pattern                     Blessings