[perf] Move to Roles.

Only roles.Editor's are allowed to triage, create alerts, etc.

Also remove InternalOnly support as that can now be handled
by auth-proxy also.

Bug: b/249507110
Change-Id: Ib9a8a6efa22aaf753e47c3b580fc9562f0d01e1c
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/586702
Reviewed-by: Ravi Mistry <rmistry@google.com>
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
diff --git a/perf/go/config/config.go b/perf/go/config/config.go
index 6bd5d9b..2ac0e37 100644
--- a/perf/go/config/config.go
+++ b/perf/go/config/config.go
@@ -243,7 +243,7 @@
 
 // FrontendFlags are the command-line flags for the web UI.
 type FrontendFlags struct {
-	AuthBypassList                 string
+	AuthBypassList                 string // TODO(jcgregorio) Remove after migrating to Roles.
 	ConfigFilename                 string
 	ConnectionString               string
 	CommitRangeURL                 string
@@ -252,7 +252,7 @@
 	NoEmail                        bool
 	EventDrivenRegressionDetection bool
 	Interesting                    float64
-	InternalOnly                   bool
+	InternalOnly                   bool // TODO(jcgregorio) Remove after migrating to Roles.
 	KeyOrder                       string
 	Local                          bool
 	NumContinuous                  int
@@ -266,7 +266,7 @@
 	Radius                         int
 	StepUpOnly                     bool
 	DisplayGroupBy                 bool
-	ProxyLogin                     bool
+	ProxyLogin                     bool // TODO(jcgregorio) Remove after migrating to Roles.
 }
 
 // AsCliFlags returns a slice of cli.Flag.
diff --git a/perf/go/frontend/BUILD.bazel b/perf/go/frontend/BUILD.bazel
index 6f3cd7d..81035f4 100644
--- a/perf/go/frontend/BUILD.bazel
+++ b/perf/go/frontend/BUILD.bazel
@@ -8,7 +8,6 @@
     deps = [
         "//go/alogin",
         "//go/alogin/proxylogin",
-        "//go/alogin/sklogin",
         "//go/auditlog",
         "//go/calc",
         "//go/email",
@@ -16,6 +15,7 @@
         "//go/metrics2",
         "//go/paramtools",
         "//go/query",
+        "//go/roles",
         "//go/skerr",
         "//go/sklog",
         "//go/sklog/sklogimpl",
diff --git a/perf/go/frontend/frontend.go b/perf/go/frontend/frontend.go
index b25a005..ae602f6 100644
--- a/perf/go/frontend/frontend.go
+++ b/perf/go/frontend/frontend.go
@@ -25,7 +25,6 @@
 	"go.opencensus.io/trace"
 	"go.skia.org/infra/go/alogin"
 	"go.skia.org/infra/go/alogin/proxylogin"
-	"go.skia.org/infra/go/alogin/sklogin"
 	"go.skia.org/infra/go/auditlog"
 	"go.skia.org/infra/go/calc"
 	"go.skia.org/infra/go/email"
@@ -33,6 +32,7 @@
 	"go.skia.org/infra/go/metrics2"
 	"go.skia.org/infra/go/paramtools"
 	"go.skia.org/infra/go/query"
+	"go.skia.org/infra/go/roles"
 	"go.skia.org/infra/go/skerr"
 	"go.skia.org/infra/go/sklog"
 	"go.skia.org/infra/go/sklog/sklogimpl"
@@ -289,20 +289,13 @@
 	cfg := config.Config
 
 	// Configure login.
-	if f.flags.ProxyLogin {
-		f.loginProvider, err = proxylogin.New(
-			cfg.AuthConfig.HeaderName,
-			cfg.AuthConfig.EmailRegex,
-			cfg.AuthConfig.LoginURL,
-			cfg.AuthConfig.LogoutURL)
-		if err != nil {
-			sklog.Fatalf("Failed to initialize login: %s", err)
-		}
-	} else {
-		f.loginProvider, err = sklogin.New(f.flags.Port, f.flags.Local, f.flags.AuthBypassList)
-		if err != nil {
-			sklog.Fatalf("Failed to initialize the login system: %s", err)
-		}
+	f.loginProvider, err = proxylogin.New(
+		cfg.AuthConfig.HeaderName,
+		cfg.AuthConfig.EmailRegex,
+		cfg.AuthConfig.LoginURL,
+		cfg.AuthConfig.LogoutURL)
+	if err != nil {
+		sklog.Fatalf("Failed to initialize login: %s", err)
 	}
 
 	ctx := context.Background()
@@ -820,6 +813,16 @@
 	}
 }
 
+func (f *Frontend) isEditor(w http.ResponseWriter, r *http.Request, action string, body interface{}) bool {
+	user := f.loginProvider.LoggedInAs(r)
+	if f.loginProvider.HasRole(r, roles.Editor) {
+		httputils.ReportError(w, fmt.Errorf("Not logged in."), "You must be logged in to complete this action.", http.StatusInternalServerError)
+		return false
+	}
+	auditlog.LogWithUser(r, user.String(), "triage", body)
+	return true
+}
+
 // TriageRequest is used in triageHandler.
 type TriageRequest struct {
 	Cid         types.CommitNumber      `json:"cid"`
@@ -840,17 +843,14 @@
 func (f *Frontend) triageHandler(w http.ResponseWriter, r *http.Request) {
 	ctx := r.Context()
 	w.Header().Set("Content-Type", "application/json")
-	user := f.loginProvider.LoggedInAs(r)
-	if user == alogin.NotLoggedIn {
-		httputils.ReportError(w, fmt.Errorf("Not logged in."), "You must be logged in to triage.", http.StatusInternalServerError)
-		return
-	}
 	tr := &TriageRequest{}
 	if err := json.NewDecoder(r.Body).Decode(tr); err != nil {
 		httputils.ReportError(w, err, "Failed to decode JSON.", http.StatusInternalServerError)
 		return
 	}
-	auditlog.LogWithUser(r, user.String(), "triage", tr)
+	if !f.isEditor(w, r, "triage", tr) {
+		return
+	}
 	detail, err := f.perfGit.CommitFromCommitNumber(ctx, tr.Cid)
 	if err != nil {
 		httputils.ReportError(w, err, "Failed to find CommitID.", http.StatusInternalServerError)
@@ -1294,18 +1294,17 @@
 
 func (f *Frontend) alertUpdateHandler(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
-	user := f.loginProvider.LoggedInAs(r)
-	if user == alogin.NotLoggedIn {
-		httputils.ReportError(w, fmt.Errorf("Not logged in."), "You must be logged in to edit alerts.", http.StatusInternalServerError)
-		return
-	}
 
 	cfg := &alerts.Alert{}
 	if err := json.NewDecoder(r.Body).Decode(cfg); err != nil {
 		httputils.ReportError(w, err, "Failed to decode JSON.", http.StatusInternalServerError)
 		return
 	}
-	auditlog.LogWithUser(r, user.String(), "alert-update", cfg)
+
+	if !f.isEditor(w, r, "alert-update", cfg) {
+		return
+	}
+
 	if err := f.alertStore.Save(r.Context(), cfg); err != nil {
 		httputils.ReportError(w, err, "Failed to save alerts.Config.", http.StatusInternalServerError)
 	}
@@ -1319,18 +1318,17 @@
 
 func (f *Frontend) alertDeleteHandler(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
-	user := f.loginProvider.LoggedInAs(r)
-	if user == alogin.NotLoggedIn {
-		httputils.ReportError(w, fmt.Errorf("Not logged in."), "You must be logged in to delete alerts.", http.StatusInternalServerError)
-		return
-	}
 
 	sid := mux.Vars(r)["id"]
 	id, err := strconv.ParseInt(sid, 10, 64)
 	if err != nil {
 		httputils.ReportError(w, err, "Failed to parse alert id.", http.StatusInternalServerError)
 	}
-	auditlog.LogWithUser(r, user.String(), "alert-delete", sid)
+
+	if !f.isEditor(w, r, "alert-delete", sid) {
+		return
+	}
+
 	if err := f.alertStore.Delete(r.Context(), int(id)); err != nil {
 		httputils.ReportError(w, err, "Failed to delete the alerts.Config.", http.StatusInternalServerError)
 		return
@@ -1349,18 +1347,17 @@
 
 func (f *Frontend) alertBugTryHandler(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
-	user := f.loginProvider.LoggedInAs(r)
-	if user == alogin.NotLoggedIn {
-		httputils.ReportError(w, fmt.Errorf("Not logged in."), "You must be logged in to test alerts.", http.StatusInternalServerError)
-		return
-	}
 
 	req := &TryBugRequest{}
 	if err := json.NewDecoder(r.Body).Decode(req); err != nil {
 		httputils.ReportError(w, err, "Failed to decode JSON.", http.StatusInternalServerError)
 		return
 	}
-	auditlog.LogWithUser(r, user.String(), "alert-bug-try", req)
+
+	if !f.isEditor(w, r, "alert-bug-try", req) {
+		return
+	}
+
 	resp := &TryBugResponse{
 		URL: bug.ExampleExpand(req.BugURITemplate),
 	}
@@ -1371,18 +1368,17 @@
 
 func (f *Frontend) alertNotifyTryHandler(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
-	user := f.loginProvider.LoggedInAs(r)
-	if user == alogin.NotLoggedIn {
-		httputils.ReportError(w, fmt.Errorf("Not logged in."), "You must be logged in to try alerts.", http.StatusInternalServerError)
-		return
-	}
 
 	req := &alerts.Alert{}
 	if err := json.NewDecoder(r.Body).Decode(req); err != nil {
 		httputils.ReportError(w, err, "Failed to decode JSON.", http.StatusInternalServerError)
 		return
 	}
-	auditlog.LogWithUser(r, user.String(), "alert-notify-try", req)
+
+	if !f.isEditor(w, r, "alert-notify-try", req) {
+		return
+	}
+
 	if err := f.notifier.ExampleSend(r.Context(), req); err != nil {
 		httputils.ReportError(w, err, fmt.Sprintf("Failed to send email: %s", err), http.StatusInternalServerError)
 	}
@@ -1416,24 +1412,6 @@
 	http.Redirect(w, r, "/t/", http.StatusMovedPermanently)
 }
 
-var internalOnlyExceptions = []string{
-	"/oauth2callback/",
-	"/_/reg/count",
-}
-
-// internalOnlyHandler wraps the handler with a handler that only allows
-// authenticated access, with the exception of the endpoints listed in
-// internalOnlyExceptions.
-func (f *Frontend) internalOnlyHandler(h http.Handler) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		if util.In(r.URL.Path, internalOnlyExceptions) || f.loginProvider.LoggedInAs(r) != alogin.NotLoggedIn {
-			h.ServeHTTP(w, r)
-		} else {
-			f.loginProvider.NeedsAuthentication(w, r)
-		}
-	})
-}
-
 // Serve content on the configured endpoints.Serve.
 //
 // This method does not return.
@@ -1510,9 +1488,6 @@
 	router.HandleFunc("/_/login/status", f.loginStatus).Methods("GET")
 
 	var h http.Handler = router
-	if f.flags.InternalOnly {
-		h = f.internalOnlyHandler(h)
-	}
 	h = httputils.LoggingGzipRequestResponse(h)
 	if !f.flags.Local {
 		h = httputils.HealthzAndHTTPS(h)