internal/gaby: check if comment author is allowed to bisect

Change-Id: I2880436969d46b4c23eaf06975c3caf32b495a27
Reviewed-on: https://go-review.googlesource.com/c/oscar/+/641895
Reviewed-by: Jonathan Amsterdam <jba@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/internal/gaby/github_event.go b/internal/gaby/github_event.go
index c6dbb22..3d76bf1 100644
--- a/internal/gaby/github_event.go
+++ b/internal/gaby/github_event.go
@@ -11,6 +11,7 @@
 	"net/http"
 	"slices"
 
+	"cloud.google.com/go/firestore"
 	"golang.org/x/oscar/internal/actions"
 	"golang.org/x/oscar/internal/docs"
 	"golang.org/x/oscar/internal/github"
@@ -177,7 +178,6 @@
 // spawnBisectionTask checks if event is encoding a bisection task and,
 // if so, it spawns the corresponding task.
 func (g *Gaby) spawnBisectionTask(ctx context.Context, event *github.WebhookIssueCommentEvent) error {
-	// TODO: check the author via secrets?
 	// TODO: access comment through db instead
 	// of directly through the event?
 	breq, err := parseBisectTrigger(event)
@@ -189,9 +189,42 @@
 		g.slog.Info("bisect.Request no trigger", "body", event.Comment.Body)
 		return nil
 	}
+
+	// Check the user only after we established that
+	// the comment encodes a bisection request, to
+	// save time on pinging firestore.
+	user := event.Comment.User.Login
+	if ok, err := userAllowedBisection(ctx, user); err != nil {
+		g.slog.Error("bisect.Request permission check", "err", err)
+		return err
+	} else if !ok {
+		g.slog.Info("bisect.Request permission denied", "user", user)
+		return nil
+	}
+
 	if err := g.bisect.BisectAsync(ctx, breq); err != nil {
 		return err
 	}
 	g.slog.Info("bisect.Request trigger success", "req", fmt.Sprintf("%+v", breq))
 	return nil
 }
+
+// userAllowedBisection checks if the author of the
+// comment event is allowed to spawn a bisection.
+func userAllowedBisection(ctx context.Context, user string) (bool, error) {
+	fc, err := firestore.NewClient(ctx, flags.project)
+	if err != nil {
+		return false, err
+	}
+	doc := fc.Collection("auth").Doc("bisect-github-users")
+	sn, err := doc.Get(ctx)
+	if err != nil {
+		return false, err
+	}
+	var users map[string]bool
+	if err := sn.DataTo(&users); err != nil {
+		return false, err
+	}
+
+	return users[user], nil
+}
diff --git a/internal/github/webhook.go b/internal/github/webhook.go
index 795005a..557e383 100644
--- a/internal/github/webhook.go
+++ b/internal/github/webhook.go
@@ -184,6 +184,7 @@
 type Comment struct {
 	URL  string `json:"url"`
 	Body string `json:"body"`
+	User User   `json:"user"`
 }
 
 // toWebhookEvent converts data into a WebhookEvent with a Payload of the