Merge "test/v23tests: added an option to set prefix arguments"
diff --git a/profiles/internal/naming/namespace/glob.go b/profiles/internal/naming/namespace/glob.go
index e81d052..f2dd4cf 100644
--- a/profiles/internal/naming/namespace/glob.go
+++ b/profiles/internal/naming/namespace/glob.go
@@ -181,7 +181,7 @@
 		// If no tasks are running, return.
 		if t.error != nil {
 			if !notAnMT(t.error) {
-				reply <- naming.GlobReplyError{naming.GlobError{Name: naming.Join(prefix, t.me.Name), Error: t.error}}
+				reply <- &naming.GlobReplyError{naming.GlobError{Name: naming.Join(prefix, t.me.Name), Error: t.error}}
 			}
 			inFlight--
 			continue
diff --git a/profiles/internal/naming/namespace/mount.go b/profiles/internal/naming/namespace/mount.go
index b42ddc4..766fc3c 100644
--- a/profiles/internal/naming/namespace/mount.go
+++ b/profiles/internal/naming/namespace/mount.go
@@ -19,7 +19,7 @@
 // mountIntoMountTable mounts a single server into a single mount table.
 func mountIntoMountTable(ctx *context.T, client rpc.Client, name, server string, ttl time.Duration, flags naming.MountFlag, id string, opts ...rpc.CallOpt) (s status) {
 	s.id = id
-	ctx, _ = context.WithTimeout(ctx, callTimeout)
+	ctx = withTimeout(ctx)
 	s.err = client.Call(ctx, name, "Mount", []interface{}{server, uint32(ttl.Seconds()), flags}, nil, append(opts, options.NoResolve{})...)
 	return
 }
@@ -60,7 +60,7 @@
 // unmountFromMountTable removes a single mounted server from a single mount table.
 func unmountFromMountTable(ctx *context.T, client rpc.Client, name, server string, id string, opts ...rpc.CallOpt) (s status) {
 	s.id = id
-	ctx, _ = context.WithTimeout(ctx, callTimeout)
+	ctx = withTimeout(ctx)
 	s.err = client.Call(ctx, name, "Unmount", []interface{}{server}, nil, append(opts, options.NoResolve{})...)
 	return
 }
@@ -82,7 +82,7 @@
 // and deleteSubtree isn't true, nothing is deleted.
 func deleteFromMountTable(ctx *context.T, client rpc.Client, name string, deleteSubtree bool, id string, opts ...rpc.CallOpt) (s status) {
 	s.id = id
-	ctx, _ = context.WithTimeout(ctx, callTimeout)
+	ctx = withTimeout(ctx)
 	s.err = client.Call(ctx, name, "Delete", []interface{}{deleteSubtree}, nil, append(opts, options.NoResolve{})...)
 	return
 }
diff --git a/profiles/internal/naming/namespace/namespace.go b/profiles/internal/naming/namespace/namespace.go
index bda109e..1a9639e 100644
--- a/profiles/internal/naming/namespace/namespace.go
+++ b/profiles/internal/naming/namespace/namespace.go
@@ -10,6 +10,7 @@
 
 	inaming "v.io/x/ref/profiles/internal/naming"
 
+	"v.io/v23/context"
 	"v.io/v23/naming"
 	"v.io/v23/rpc"
 	"v.io/v23/security"
@@ -169,10 +170,27 @@
 	return false
 }
 
-// all operations against the mount table service use this fixed timeout for the
-// time being.
+// All operations against the mount table service use this fixed timeout unless overridden.
 const callTimeout = 30 * time.Second
 
+// withTimeout returns a new context if the orinal has no timeout set.
+func withTimeout(ctx *context.T) *context.T {
+	if _, ok := ctx.Deadline(); !ok {
+		ctx, _ = context.WithTimeout(ctx, callTimeout)
+	}
+	return ctx
+}
+
+// withTimeoutAndCancel returns a new context with a deadline and a cancellation function.
+func withTimeoutAndCancel(ctx *context.T) (nctx *context.T, cancel context.CancelFunc) {
+	if _, ok := ctx.Deadline(); !ok {
+		nctx, cancel = context.WithTimeout(ctx, callTimeout)
+	} else {
+		nctx, cancel = context.WithCancel(ctx)
+	}
+	return
+}
+
 // CacheCtl implements naming.Namespace.CacheCtl
 func (ns *namespace) CacheCtl(ctls ...naming.CacheCtl) []naming.CacheCtl {
 	defer vlog.LogCall()()
diff --git a/profiles/internal/naming/namespace/parallelstartcall.go b/profiles/internal/naming/namespace/parallelstartcall.go
index f753e4e..ff0fda8 100644
--- a/profiles/internal/naming/namespace/parallelstartcall.go
+++ b/profiles/internal/naming/namespace/parallelstartcall.go
@@ -34,7 +34,7 @@
 	c := make(chan startStatus, len(servers))
 	cancelFuncs := make([]context.CancelFunc, len(servers))
 	for index, server := range servers {
-		callCtx, cancel := context.WithTimeout(ctx, callTimeout)
+		callCtx, cancel := withTimeoutAndCancel(ctx)
 		cancelFuncs[index] = cancel
 		go tryStartCall(callCtx, client, server, method, args, c, index, opts...)
 	}
diff --git a/profiles/internal/naming/namespace/perms.go b/profiles/internal/naming/namespace/perms.go
index ed97813..41883eb 100644
--- a/profiles/internal/naming/namespace/perms.go
+++ b/profiles/internal/naming/namespace/perms.go
@@ -17,7 +17,7 @@
 // setPermsInMountTable sets the Permissions in a single server.
 func setPermsInMountTable(ctx *context.T, client rpc.Client, name string, perms access.Permissions, version, id string, opts []rpc.CallOpt) (s status) {
 	s.id = id
-	ctx, _ = context.WithTimeout(ctx, callTimeout)
+	ctx = withTimeout(ctx)
 	s.err = client.Call(ctx, name, "SetPermissions", []interface{}{perms, version}, nil, append(opts, options.NoResolve{})...)
 	return
 }
diff --git a/profiles/internal/naming/namespace/resolve.go b/profiles/internal/naming/namespace/resolve.go
index 96b08f8..6b01635 100644
--- a/profiles/internal/naming/namespace/resolve.go
+++ b/profiles/internal/naming/namespace/resolve.go
@@ -33,7 +33,7 @@
 		if _, hasDeadline := ctx.Deadline(); !hasDeadline {
 			// Only set a per-call timeout if a deadline has not already
 			// been set.
-			callCtx, _ = context.WithTimeout(ctx, callTimeout)
+			callCtx = withTimeout(ctx)
 		}
 		entry := new(naming.MountEntry)
 		if err := client.Call(callCtx, name, "ResolveStep", nil, []interface{}{entry}, opts...); err != nil {
diff --git a/services/device/internal/impl/app_service.go b/services/device/internal/impl/app_service.go
index 066b667..9d6ab22 100644
--- a/services/device/internal/impl/app_service.go
+++ b/services/device/internal/impl/app_service.go
@@ -414,7 +414,7 @@
 	installationID := generateID()
 	installationDir := filepath.Join(i.config.Root, applicationDirName(envelope.Title), installationDirName(installationID))
 	deferrer := func() {
-		cleanupDir(installationDir, "")
+		CleanupDir(installationDir, "")
 	}
 	if err := mkdirPerm(installationDir, 0711); err != nil {
 		return "", verror.New(ErrOperationFailed, nil)
@@ -917,12 +917,12 @@
 	helper := i.config.Helper
 	instanceDir, instanceID, err := i.newInstance(ctx, call)
 	if err != nil {
-		cleanupDir(instanceDir, helper)
+		CleanupDir(instanceDir, helper)
 		return "", err
 	}
 	systemName := suidHelper.usernameForPrincipal(ctx, call.Security(), i.uat)
 	if err := saveSystemNameForInstance(instanceDir, systemName); err != nil {
-		cleanupDir(instanceDir, helper)
+		CleanupDir(instanceDir, helper)
 		return "", err
 	}
 	return instanceID, nil
@@ -1101,7 +1101,7 @@
 	}
 	versionDir, err := newVersion(ctx, installationDir, newEnvelope, oldVersionDir)
 	if err != nil {
-		cleanupDir(versionDir, "")
+		CleanupDir(versionDir, "")
 		return err
 	}
 	return nil
diff --git a/services/device/internal/impl/device_service.go b/services/device/internal/impl/device_service.go
index 1678fb8..5b732f3 100644
--- a/services/device/internal/impl/device_service.go
+++ b/services/device/internal/impl/device_service.go
@@ -504,7 +504,7 @@
 	}
 
 	deferrer := func() {
-		cleanupDir(workspace, "")
+		CleanupDir(workspace, "")
 	}
 	defer func() {
 		if deferrer != nil {
diff --git a/services/device/internal/impl/impl_helper_test.go b/services/device/internal/impl/impl_helper_test.go
index 4eed0cb..1ee97bf 100644
--- a/services/device/internal/impl/impl_helper_test.go
+++ b/services/device/internal/impl/impl_helper_test.go
@@ -38,12 +38,12 @@
 	// Setup a helper.
 	helper := utiltest.GenerateSuidHelperScript(t, dir)
 
-	impl.WrapBaseCleanupDir(helperTarget, helper)
+	impl.BaseCleanupDir(helperTarget, helper)
 	if _, err := os.Stat(helperTarget); err == nil || os.IsExist(err) {
 		t.Fatalf("%s should be missing but isn't", helperTarget)
 	}
 
-	impl.WrapBaseCleanupDir(nohelperTarget, "")
+	impl.BaseCleanupDir(nohelperTarget, "")
 	if _, err := os.Stat(nohelperTarget); err == nil || os.IsExist(err) {
 		t.Fatalf("%s should be missing but isn't", nohelperTarget)
 	}
diff --git a/services/device/internal/impl/only_for_test.go b/services/device/internal/impl/only_for_test.go
index d257860..9b02bb7 100644
--- a/services/device/internal/impl/only_for_test.go
+++ b/services/device/internal/impl/only_for_test.go
@@ -6,11 +6,8 @@
 
 import (
 	"fmt"
-	"os"
-	"path/filepath"
 
 	"v.io/v23/rpc"
-	"v.io/x/lib/vlog"
 )
 
 // This file contains code in the impl package that we only want built for tests
@@ -32,25 +29,3 @@
 		panic(fmt.Sprintf("unexpected type: %T", d))
 	}
 }
-
-func init() {
-	cleanupDir = func(dir, helper string) {
-		if dir == "" {
-			return
-		}
-		parentDir, base := filepath.Dir(dir), filepath.Base(dir)
-		var renamed string
-		if helper != "" {
-			renamed = filepath.Join(parentDir, "helper_deleted_"+base)
-		} else {
-			renamed = filepath.Join(parentDir, "deleted_"+base)
-		}
-		if err := os.Rename(dir, renamed); err != nil {
-			vlog.Errorf("Rename(%v, %v) failed: %v", dir, renamed, err)
-		}
-	}
-}
-
-func WrapBaseCleanupDir(path, helper string) {
-	baseCleanupDir(path, helper)
-}
diff --git a/services/device/internal/impl/util.go b/services/device/internal/impl/util.go
index dd22307..d7aa4f2 100644
--- a/services/device/internal/impl/util.go
+++ b/services/device/internal/impl/util.go
@@ -129,7 +129,7 @@
 	return nil
 }
 
-func baseCleanupDir(path, helper string) {
+func BaseCleanupDir(path, helper string) {
 	if helper != "" {
 		out, err := exec.Command(helper, "--rm", path).CombinedOutput()
 		if err != nil {
@@ -150,7 +150,7 @@
 	return filepath.Join(c.Root, "device-manager", "device-data", "acls")
 }
 
-// cleanupDir is defined like this so we can override its implementation for
-// tests. cleanupDir will use the helper to delete application state possibly
+// CleanupDir is defined like this so we can override its implementation for
+// tests. CleanupDir will use the helper to delete application state possibly
 // owned by different accounts if helper is provided.
-var cleanupDir = baseCleanupDir
+var CleanupDir = BaseCleanupDir
diff --git a/services/device/internal/impl/utiltest/helpers.go b/services/device/internal/impl/utiltest/helpers.go
index 35d1e06..6469493 100644
--- a/services/device/internal/impl/utiltest/helpers.go
+++ b/services/device/internal/impl/utiltest/helpers.go
@@ -52,6 +52,22 @@
 	impl.Describe = func() (descr device.Description, err error) {
 		return device.Description{Profiles: map[string]struct{}{"test-profile": struct{}{}}}, nil
 	}
+
+	impl.CleanupDir = func(dir, helper string) {
+		if dir == "" {
+			return
+		}
+		parentDir, base := filepath.Dir(dir), filepath.Base(dir)
+		var renamed string
+		if helper != "" {
+			renamed = filepath.Join(parentDir, "helper_deleted_"+base)
+		} else {
+			renamed = filepath.Join(parentDir, "deleted_"+base)
+		}
+		if err := os.Rename(dir, renamed); err != nil {
+			vlog.Errorf("Rename(%v, %v) failed: %v", dir, renamed, err)
+		}
+	}
 }
 
 func EnvelopeFromShell(sh *modules.Shell, env []string, cmd, title string, args ...string) application.Envelope {
diff --git a/services/identity/identityd/main.go b/services/identity/identityd/main.go
index 1639b9d..1c57547 100644
--- a/services/identity/identityd/main.go
+++ b/services/identity/identityd/main.go
@@ -73,11 +73,6 @@
 		vlog.Fatalf("Failed to start RevocationManager: %v", err)
 	}
 
-	bname, _, err := util.RootCertificateDetails(v23.GetPrincipal(ctx).BlessingStore().Default())
-	if err != nil {
-		vlog.Fatalf("Failed to get root Blessings name: %v", err)
-	}
-
 	listenSpec := v23.GetListenSpec(ctx)
 	s := server.NewIdentityServer(
 		googleoauth,
@@ -85,7 +80,7 @@
 		reader,
 		revocationManager,
 		googleOAuthBlesserParams(googleoauth, revocationManager),
-		caveats.NewBrowserCaveatSelector(*assetsPrefix, bname),
+		caveats.NewBrowserCaveatSelector(*assetsPrefix),
 		&emailClassifier,
 		*assetsPrefix,
 		*mountPrefix)
diff --git a/services/identity/identitylib/test_identityd.go b/services/identity/identitylib/test_identityd.go
index 53c8587..1559091 100644
--- a/services/identity/identitylib/test_identityd.go
+++ b/services/identity/identitylib/test_identityd.go
@@ -55,7 +55,7 @@
 		}
 		host, _, err := net.SplitHostPort(addr)
 		if err != nil {
-			return fmt.Errorf("Failed to parse %q: %v", httpAddr, err)
+			return fmt.Errorf("Failed to parse %q: %v", addr, err)
 		}
 		certFile, keyFile, err := util.WriteCertAndKey(host, duration)
 		if err != nil {
@@ -79,7 +79,7 @@
 
 	auditor, reader := auditor.NewMockBlessingAuditor()
 	revocationManager := revocation.NewMockRevocationManager()
-	oauthProvider := oauth.NewMockOAuth()
+	oauthProvider := oauth.NewMockOAuth("testemail@example.com")
 
 	params := blesser.OAuthBlesserParams{
 		OAuthProvider:     oauthProvider,
diff --git a/services/identity/internal/blesser/oauth_test.go b/services/identity/internal/blesser/oauth_test.go
index 48b569f..91c5a23 100644
--- a/services/identity/internal/blesser/oauth_test.go
+++ b/services/identity/internal/blesser/oauth_test.go
@@ -6,6 +6,7 @@
 
 import (
 	"reflect"
+	"strings"
 	"testing"
 	"time"
 
@@ -15,13 +16,18 @@
 	"v.io/v23/security"
 )
 
+func join(elements ...string) string {
+	return strings.Join(elements, security.ChainSeparator)
+}
+
 func TestOAuthBlesser(t *testing.T) {
 	var (
 		provider, user = testutil.NewPrincipal(), testutil.NewPrincipal()
 		ctx, call      = fakeContextAndCall(provider, user)
 	)
+	mockEmail := "testemail@example.com"
 	blesser := NewOAuthBlesserServer(OAuthBlesserParams{
-		OAuthProvider:    oauth.NewMockOAuth(),
+		OAuthProvider:    oauth.NewMockOAuth(mockEmail),
 		BlessingDuration: time.Hour,
 	})
 
@@ -30,7 +36,7 @@
 		t.Errorf("BlessUsingAccessToken failed: %v", err)
 	}
 
-	wantExtension := "users" + security.ChainSeparator + oauth.MockEmail + security.ChainSeparator + oauth.MockClient
+	wantExtension := join("users", mockEmail, oauth.MockClient)
 	if extension != wantExtension {
 		t.Errorf("got extension: %s, want: %s", extension, wantExtension)
 	}
@@ -45,13 +51,13 @@
 		t.Errorf("Got blessing with info %v, want nil", got)
 	}
 	// But once it recognizes the provider, it should see exactly the name
-	// "provider/testemail@google.com/test-client".
+	// "provider/testemail@example.com/test-client".
 	user.AddToRoots(b)
 	binfo := user.BlessingsInfo(b)
 	if num := len(binfo); num != 1 {
 		t.Errorf("Got blessings with %d names, want exactly one name", num)
 	}
-	if _, ok := binfo["provider"+security.ChainSeparator+wantExtension]; !ok {
+	if _, ok := binfo[join("provider", wantExtension)]; !ok {
 		t.Errorf("BlessingsInfo %v does not have name %s", binfo, wantExtension)
 	}
 }
diff --git a/services/identity/internal/caveats/browser_caveat_selector.go b/services/identity/internal/caveats/browser_caveat_selector.go
index 37e1079..cbb0400 100644
--- a/services/identity/internal/caveats/browser_caveat_selector.go
+++ b/services/identity/internal/caveats/browser_caveat_selector.go
@@ -17,19 +17,18 @@
 
 type browserCaveatSelector struct {
 	assetsPrefix string
-	blessingName string
 }
 
 // NewBrowserCaveatSelector returns a caveat selector that renders a form in the
 // to accept user caveat selections.
-func NewBrowserCaveatSelector(assetsPrefix, blessingName string) CaveatSelector {
-	return &browserCaveatSelector{assetsPrefix, blessingName}
+func NewBrowserCaveatSelector(assetsPrefix string) CaveatSelector {
+	return &browserCaveatSelector{assetsPrefix}
 }
 
-func (s *browserCaveatSelector) Render(blessingExtension, state, redirectURL string, w http.ResponseWriter, r *http.Request) error {
+func (s *browserCaveatSelector) Render(blessingName, state, redirectURL string, w http.ResponseWriter, r *http.Request) error {
 	tmplargs := struct {
-		Email, Macaroon, MacaroonURL, AssetsPrefix, BlessingName string
-	}{blessingExtension, state, redirectURL, s.assetsPrefix, s.blessingName}
+		BlessingName, Macaroon, MacaroonURL, AssetsPrefix string
+	}{blessingName, state, redirectURL, s.assetsPrefix}
 	w.Header().Set("Context-Type", "text/html")
 	if err := templates.SelectCaveats.Execute(w, tmplargs); err != nil {
 		return err
diff --git a/services/identity/internal/caveats/caveat_selector.go b/services/identity/internal/caveats/caveat_selector.go
index f0746b1..487c5a6 100644
--- a/services/identity/internal/caveats/caveat_selector.go
+++ b/services/identity/internal/caveats/caveat_selector.go
@@ -13,10 +13,10 @@
 type CaveatSelector interface {
 	// Render renders the caveat input form. When the user has completed inputing caveats,
 	// Render should redirect to the specified redirect route.
-	// blessingExtension is the extension used for the blessings that is being caveated.
+	// blessingName is the name used for the blessings that is being caveated.
 	// state is any state passed by the caller (e.g., for CSRF mitigation) and is returned by ParseSelections.
 	// redirectRoute is the route to be returned to.
-	Render(blessingExtension, state, redirectURL string, w http.ResponseWriter, r *http.Request) error
+	Render(blessingName, state, redirectURL string, w http.ResponseWriter, r *http.Request) error
 	// ParseSelections parse the users choices of Caveats, and returns the information needed to create them,
 	// the state passed to Render, and any additionalExtension selected by the user to further extend the blessing.
 	ParseSelections(r *http.Request) (caveats []CaveatInfo, state string, additionalExtension string, err error)
diff --git a/services/identity/internal/identityd_test/main.go b/services/identity/internal/identityd_test/main.go
index ca59165..f7977de 100644
--- a/services/identity/internal/identityd_test/main.go
+++ b/services/identity/internal/identityd_test/main.go
@@ -33,11 +33,13 @@
 	assetsPrefix     = flag.String("assets-prefix", "", "host serving the web assets for the identity server")
 	mountPrefix      = flag.String("mount-prefix", "identity", "mount name prefix to use. May be rooted.")
 	browser          = flag.Bool("browser", false, "whether to open a browser caveat selector")
+	oauthEmail       = flag.String("oauth-email", "testemail@example.com", "Username for the mock oauth to put in the returned blessings")
 )
 
 func main() {
 	flag.Usage = usage
-	flag.Parse()
+	ctx, shutdown := v23.Init()
+	defer shutdown()
 
 	// Duration to use for tls cert and blessing duration.
 	duration := 365 * 24 * time.Hour
@@ -50,7 +52,10 @@
 		}
 		host, _, err := net.SplitHostPort(addr)
 		if err != nil {
-			vlog.Fatalf("Failed to parse %q: %v", httpAddr, err)
+			// NOTE(caprita): The (non-test) identityd binary
+			// accepts an address with no port.  Should this test
+			// binary do the same instead?
+			vlog.Fatalf("Failed to parse %q: %v", addr, err)
 		}
 		certFile, keyFile, err := util.WriteCertAndKey(host, duration)
 		if err != nil {
@@ -63,7 +68,7 @@
 
 	auditor, reader := auditor.NewMockBlessingAuditor()
 	revocationManager := revocation.NewMockRevocationManager()
-	oauthProvider := oauth.NewMockOAuth()
+	oauthProvider := oauth.NewMockOAuth(*oauthEmail)
 
 	params := blesser.OAuthBlesserParams{
 		OAuthProvider:     oauthProvider,
@@ -71,16 +76,9 @@
 		RevocationManager: revocationManager,
 	}
 
-	ctx, shutdown := v23.Init()
-	defer shutdown()
-
 	caveatSelector := caveats.NewMockCaveatSelector()
 	if *browser {
-		bname, _, err := util.RootCertificateDetails(v23.GetPrincipal(ctx).BlessingStore().Default())
-		if err != nil {
-			vlog.Fatalf("Failed to get root Blessings name: %v", err)
-		}
-		caveatSelector = caveats.NewBrowserCaveatSelector(*assetsPrefix, bname)
+		caveatSelector = caveats.NewBrowserCaveatSelector(*assetsPrefix)
 	}
 
 	listenSpec := v23.GetListenSpec(ctx)
diff --git a/services/identity/internal/oauth/handler.go b/services/identity/internal/oauth/handler.go
index fcb6c41..c2de6ab 100644
--- a/services/identity/internal/oauth/handler.go
+++ b/services/identity/internal/oauth/handler.go
@@ -381,7 +381,19 @@
 		util.HTTPServerError(w, fmt.Errorf("failed to create new token: %v", err))
 		return
 	}
-	if err := h.args.CaveatSelector.Render(email, outputMacaroon, redirectURL(h.args.Addr, sendMacaroonRoute), w, r); err != nil {
+	localBlessings := security.DefaultBlessingPatterns(h.args.Principal)
+	if len(localBlessings) == 0 {
+		vlog.Infof("server principal has no blessings: %v", h.args.Principal)
+		util.HTTPServerError(w, fmt.Errorf("failed to get server blessings"))
+		return
+	}
+	parts := []string{
+		string(localBlessings[0]),
+		h.args.EmailClassifier.Classify(email),
+		email,
+	}
+	fullBlessingName := strings.Join(parts, security.ChainSeparator)
+	if err := h.args.CaveatSelector.Render(fullBlessingName, outputMacaroon, redirectURL(h.args.Addr, sendMacaroonRoute), w, r); err != nil {
 		vlog.Errorf("Unable to invoke render caveat selector: %v", err)
 		util.HTTPServerError(w, err)
 	}
diff --git a/services/identity/internal/oauth/mockoauth.go b/services/identity/internal/oauth/mockoauth.go
index c122af7..3a0e828 100644
--- a/services/identity/internal/oauth/mockoauth.go
+++ b/services/identity/internal/oauth/mockoauth.go
@@ -4,16 +4,15 @@
 
 package oauth
 
-const (
-	MockEmail  = "testemail@google.com"
-	MockClient = "test-client"
-)
+const MockClient = "test-client"
 
 // mockOAuth is a mock OAuthProvider for use in tests.
-type mockOAuth struct{}
+type mockOAuth struct {
+	email string
+}
 
-func NewMockOAuth() OAuthProvider {
-	return &mockOAuth{}
+func NewMockOAuth(mockEmail string) OAuthProvider {
+	return &mockOAuth{email: mockEmail}
 }
 
 func (m *mockOAuth) AuthURL(redirectUrl string, state string, _ AuthURLApproval) string {
@@ -21,9 +20,9 @@
 }
 
 func (m *mockOAuth) ExchangeAuthCodeForEmail(string, string) (string, error) {
-	return MockEmail, nil
+	return m.email, nil
 }
 
 func (m *mockOAuth) GetEmailAndClientName(string, []AccessTokenClient) (string, string, error) {
-	return MockEmail, MockClient, nil
+	return m.email, MockClient, nil
 }
diff --git a/services/identity/internal/templates/caveats.go b/services/identity/internal/templates/caveats.go
index 72caced..eef93ad 100644
--- a/services/identity/internal/templates/caveats.go
+++ b/services/identity/internal/templates/caveats.go
@@ -25,14 +25,14 @@
       <span class="service-name">Identity Provider</span>
     </nav>
     <nav class="right">
-      <a href="#">{{.Email}}</a>
+      <a href="#">{{.BlessingName}}</a>
     </nav>
   </header>
 
   <main class="add-blessing">
 
     <form method="POST" id="caveats-form" name="input"
-    action="{{.MacaroonURL}}" role="form">
+    action="{{.MacaroonURL}}" role="form" novalidate>
       <input type="text" class="hidden" name="macaroon" value="{{.Macaroon}}">
 
       <h1 class="page-head">Add blessing</h1>
@@ -59,7 +59,7 @@
 
       <label for="blessingExtension">Blessing name</label>
       <div class="value">
-        {{.BlessingName}}/{{.Email}}/
+        {{.BlessingName}}/
         <input name="blessingExtension" type="text" placeholder="extension">
         <input type="hidden" id="timezoneOffset" name="timezoneOffset">
       </div>
@@ -226,6 +226,8 @@
 
     // Set the datetime picker to have a default value of one day from now.
     $('.expiry').val(moment().add(1, 'd').format('YYYY-MM-DDTHH:mm'));
+    // Remove the clear button from the date input.
+    $('.expiry').attr('required', 'required');
 
     // Activate the cancel button.
     $('#cancel').click(function(){
diff --git a/services/identity/internal/util/certs.go b/services/identity/internal/util/certs.go
index 3c6e7b8..c6aeba6 100644
--- a/services/identity/internal/util/certs.go
+++ b/services/identity/internal/util/certs.go
@@ -26,7 +26,8 @@
 	generateCertFile := filepath.Join(strings.TrimSpace(string(output)), "generate_cert.go")
 	generateCertCmd := exec.Command("go", "run", generateCertFile, "--host", host, "--duration", duration.String())
 	generateCertCmd.Dir = tmpDir
-	if err := generateCertCmd.Run(); err != nil {
+	if output, err := generateCertCmd.CombinedOutput(); err != nil {
+		fmt.Fprintf(os.Stderr, "%v failed:\n%s\n", generateCertCmd.Args, output)
 		return "", "", fmt.Errorf("Could not generate key and cert: %v", err)
 	}
 	return filepath.Join(tmpDir, "cert.pem"), filepath.Join(tmpDir, "key.pem"), nil