From 96de6d143e5aea30ba1071ab0d85aee516dd6cf6 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Thu, 25 Sep 2025 11:14:41 +0100 Subject: [PATCH] appview/validator: limit label ops to collaborators Change-Id: ttyzwxqtkpslvlwxltovwsrnxrlqmtyu Signed-off-by: oppiliappan --- appview/ingester.go | 2 +- appview/labels/labels.go | 12 +++++++++++- appview/state/router.go | 2 +- appview/state/state.go | 2 +- appview/validator/label.go | 16 +++++++++++++++- appview/validator/validator.go | 5 ++++- 6 files changed, 33 insertions(+), 6 deletions(-) diff --git a/appview/ingester.go b/appview/ingester.go index dbde661c..cead268f 100644 --- a/appview/ingester.go +++ b/appview/ingester.go @@ -1008,7 +1008,7 @@ func (i *Ingester) ingestLabelOp(e *jmodels.Event) error { if !ok { return fmt.Errorf("failed to find label def for key: %s, expected: %q", o.OperandKey, slices.Collect(maps.Keys(actx.Defs))) } - if err := i.Validator.ValidateLabelOp(def, &o); err != nil { + if err := i.Validator.ValidateLabelOp(def, repo, &o); err != nil { return fmt.Errorf("failed to validate labelop: %w", err) } } diff --git a/appview/labels/labels.go b/appview/labels/labels.go index ecd304b4..aab675ba 100644 --- a/appview/labels/labels.go +++ b/appview/labels/labels.go @@ -23,6 +23,7 @@ import ( "tangled.org/core/appview/validator" "tangled.org/core/appview/xrpcclient" "tangled.org/core/log" + "tangled.org/core/rbac" "tangled.org/core/tid" ) @@ -32,6 +33,7 @@ type Labels struct { db *db.DB logger *slog.Logger validator *validator.Validator + enforcer *rbac.Enforcer } func New( @@ -39,6 +41,7 @@ func New( pages *pages.Pages, db *db.DB, validator *validator.Validator, + enforcer *rbac.Enforcer, ) *Labels { logger := log.New("labels") @@ -48,6 +51,7 @@ func New( db: db, logger: logger, validator: validator, + enforcer: enforcer, } } @@ -86,6 +90,12 @@ func (l *Labels) PerformLabelOp(w http.ResponseWriter, r *http.Request) { repoAt := r.Form.Get("repo") subjectUri := r.Form.Get("subject") + repo, err := db.GetRepo(l.db, db.FilterEq("at_uri", repoAt)) + if err != nil { + fail("Failed to get repository.", err) + return + } + // find all the labels that this repo subscribes to repoLabels, err := db.GetRepoLabels(l.db, db.FilterEq("repo_at", repoAt)) if err != nil { @@ -157,7 +167,7 @@ func (l *Labels) PerformLabelOp(w http.ResponseWriter, r *http.Request) { for i := range labelOps { def := actx.Defs[labelOps[i].OperandKey] - if err := l.validator.ValidateLabelOp(def, &labelOps[i]); err != nil { + if err := l.validator.ValidateLabelOp(def, repo, &labelOps[i]); err != nil { fail(fmt.Sprintf("Invalid form data: %s", err), err) return } diff --git a/appview/state/router.go b/appview/state/router.go index 756547c2..166498de 100644 --- a/appview/state/router.go +++ b/appview/state/router.go @@ -274,7 +274,7 @@ func (s *State) PipelinesRouter(mw *middleware.Middleware) http.Handler { } func (s *State) LabelsRouter(mw *middleware.Middleware) http.Handler { - ls := labels.New(s.oauth, s.pages, s.db, s.validator) + ls := labels.New(s.oauth, s.pages, s.db, s.validator, s.enforcer) return ls.Router(mw) } diff --git a/appview/state/state.go b/appview/state/state.go index ba5fcc58..3658bfb7 100644 --- a/appview/state/state.go +++ b/appview/state/state.go @@ -79,7 +79,7 @@ func Make(ctx context.Context, config *config.Config) (*State, error) { cache := cache.New(config.Redis.Addr) sess := session.New(cache) oauth := oauth.NewOAuth(config, sess) - validator := validator.New(d, res) + validator := validator.New(d, res, enforcer) posthog, err := posthog.NewWithConfig(config.Posthog.ApiKey, posthog.Config{Endpoint: config.Posthog.Endpoint}) if err != nil { diff --git a/appview/validator/label.go b/appview/validator/label.go index 2b33709c..44423949 100644 --- a/appview/validator/label.go +++ b/appview/validator/label.go @@ -95,14 +95,28 @@ func (v *Validator) ValidateLabelDefinition(label *models.LabelDefinition) error return nil } -func (v *Validator) ValidateLabelOp(labelDef *models.LabelDefinition, labelOp *models.LabelOp) error { +func (v *Validator) ValidateLabelOp(labelDef *models.LabelDefinition, repo *models.Repo, labelOp *models.LabelOp) error { if labelDef == nil { return fmt.Errorf("label definition is required") } + if repo == nil { + return fmt.Errorf("repo is required") + } if labelOp == nil { return fmt.Errorf("label operation is required") } + // validate permissions: only collaborators can apply labels currently + // + // TODO: introduce a repo:triage permission + ok, err := v.enforcer.IsPushAllowed(labelOp.Did, repo.Knot, repo.DidSlashRepo()) + if err != nil { + return fmt.Errorf("failed to enforce permissions: %w", err) + } + if !ok { + return fmt.Errorf("unauhtorized label operation") + } + expectedKey := labelDef.AtUri().String() if labelOp.OperandKey != expectedKey { return fmt.Errorf("operand key %q does not match label definition URI %q", labelOp.OperandKey, expectedKey) diff --git a/appview/validator/validator.go b/appview/validator/validator.go index ad565671..1fcf5888 100644 --- a/appview/validator/validator.go +++ b/appview/validator/validator.go @@ -4,18 +4,21 @@ import ( "tangled.org/core/appview/db" "tangled.org/core/appview/pages/markup" "tangled.org/core/idresolver" + "tangled.org/core/rbac" ) type Validator struct { db *db.DB sanitizer markup.Sanitizer resolver *idresolver.Resolver + enforcer *rbac.Enforcer } -func New(db *db.DB, res *idresolver.Resolver) *Validator { +func New(db *db.DB, res *idresolver.Resolver, enforcer *rbac.Enforcer) *Validator { return &Validator{ db: db, sanitizer: markup.NewSanitizer(), resolver: res, + enforcer: enforcer, } } -- 2.43.0