internal/access,internal/relui: use IAPFields instead of one-of context key

For gRPC, we have a nice IAPFields type and IAPFromContext helper. Use
that for HTTP as well rather than a one-off undocumented context key.

Change-Id: I6a6a636c1a48f7bf194a7e15fcbfaec77808646a
Reviewed-on: https://go-review.googlesource.com/c/build/+/666497
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/internal/access/access.go b/internal/access/access.go
index ac560d9..9dc434c 100644
--- a/internal/access/access.go
+++ b/internal/access/access.go
@@ -73,8 +73,7 @@
 			log.Printf("JWT validation error: %v", err)
 			return
 		}
-		ctx := context.WithValue(r.Context(), "subject", iap.ID)
-		ctx = context.WithValue(ctx, "email", iap.Email)
+		ctx := ContextWithIAP(r.Context(), iap)
 		h.ServeHTTP(w, r.WithContext(ctx))
 	})
 }
diff --git a/internal/relui/web.go b/internal/relui/web.go
index d47409c..c97532c 100644
--- a/internal/relui/web.go
+++ b/internal/relui/web.go
@@ -25,6 +25,7 @@
 	"github.com/google/uuid"
 	"github.com/jackc/pgx/v4"
 	"github.com/julienschmidt/httprouter"
+	"golang.org/x/build/internal/access"
 	"golang.org/x/build/internal/criadb"
 	"golang.org/x/build/internal/metrics"
 	"golang.org/x/build/internal/relui/db"
@@ -676,16 +677,16 @@
 		return true
 	}
 
-	email := ctx.Value("email")
-	if email == nil {
-		log.Printf("request context did not contain expected 'email' value from IAP JWT")
+	iap, err := access.IAPFromContext(ctx)
+	if err != nil {
+		log.Printf("Error getting IAP fields from context: %v", err)
 		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
 		return false
 	}
 
-	isMember, err := s.cria.IsMemberOfAny(ctx, fmt.Sprintf("user:%s", email), authorizedGroups)
+	isMember, err := s.cria.IsMemberOfAny(ctx, fmt.Sprintf("user:%s", iap.Email), authorizedGroups)
 	if err != nil {
-		log.Printf("cria.IsMemberOfAny(user:%s) failed: %s", email, err)
+		log.Printf("cria.IsMemberOfAny(user:%s) failed: %s", iap.Email, err)
 		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
 		return false
 	}
diff --git a/internal/relui/web_test.go b/internal/relui/web_test.go
index 4ac63fc..293608a 100644
--- a/internal/relui/web_test.go
+++ b/internal/relui/web_test.go
@@ -29,6 +29,7 @@
 	"github.com/jackc/pgx/v4"
 	"github.com/jackc/pgx/v4/pgxpool"
 	"github.com/julienschmidt/httprouter"
+	"golang.org/x/build/internal/access"
 	"golang.org/x/build/internal/criadb"
 	"golang.org/x/build/internal/releasetargets"
 	"golang.org/x/build/internal/relui/db"
@@ -844,6 +845,11 @@
 	}
 	s := NewServer(p, worker, nil, SiteHeader{}, nil, criadb.NewTestDatabase(memberships))
 
+	iap := access.IAPFields{
+		Email: "test@google.com",
+		ID:    "testid",
+	}
+
 	hourAgo := time.Now().Add(-1 * time.Hour)
 	q := db.New(p)
 
@@ -854,7 +860,7 @@
 			"workflow.schedule":    []string{string(ScheduleImmediate)},
 		}.Encode()))
 		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-		req = req.WithContext(context.WithValue(req.Context(), "email", "test@google.com"))
+		req = req.WithContext(access.ContextWithIAP(req.Context(), iap))
 		rec := httptest.NewRecorder()
 
 		s.createWorkflowHandler(rec, req)
@@ -892,7 +898,7 @@
 		params := httprouter.Params{{Key: "id", Value: wfID.String()}, {Key: "name", Value: "beep"}}
 		req := httptest.NewRequest(http.MethodPost, path.Join("/workflows/", wfID.String(), "tasks", "beep", "retry"), nil)
 		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-		req = req.WithContext(context.WithValue(req.Context(), "email", "test@google.com"))
+		req = req.WithContext(access.ContextWithIAP(req.Context(), iap))
 		rec := httptest.NewRecorder()
 
 		s.retryTaskHandler(rec, req, params)
@@ -933,7 +939,7 @@
 		params := httprouter.Params{{Key: "id", Value: wfID.String()}, {Key: "name", Value: "approve"}}
 		req := httptest.NewRequest(http.MethodPost, path.Join("/workflows/", wfID.String(), "tasks", "approve", "approve"), nil)
 		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-		req = req.WithContext(context.WithValue(req.Context(), "email", "test@google.com"))
+		req = req.WithContext(access.ContextWithIAP(req.Context(), iap))
 		rec := httptest.NewRecorder()
 
 		s.approveTaskHandler(rec, req, params)
@@ -965,7 +971,7 @@
 		params := httprouter.Params{{Key: "id", Value: wfID.String()}}
 		req := httptest.NewRequest(http.MethodPost, path.Join("/workflows/", wfID.String(), "stop"), nil)
 		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-		req = req.WithContext(context.WithValue(req.Context(), "email", "test@google.com"))
+		req = req.WithContext(access.ContextWithIAP(req.Context(), iap))
 		rec := httptest.NewRecorder()
 
 		s.stopWorkflowHandler(rec, req, params)
@@ -989,7 +995,7 @@
 		params := httprouter.Params{{Key: "id", Value: strconv.Itoa(int(sched.ID))}}
 		req := httptest.NewRequest(http.MethodPost, path.Join("/schedules/", strconv.Itoa(int(sched.ID)), "delete"), nil)
 		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
-		req = req.WithContext(context.WithValue(req.Context(), "email", "test@google.com"))
+		req = req.WithContext(access.ContextWithIAP(req.Context(), iap))
 		rec := httptest.NewRecorder()
 
 		s.deleteScheduleHandler(rec, req, params)