diff --git a/appview/ingester_repo.go b/appview/ingester_repo.go index 6597a4f4..c4374da0 100644 --- a/appview/ingester_repo.go +++ b/appview/ingester_repo.go @@ -58,12 +58,15 @@ func (i *Ingester) ingestRepoCreate(ctx context.Context, e *jmodels.Event) error return nil } - _, err = db.GetRepo(i.Db, + existing, err := db.GetRepo(i.Db, orm.FilterEq("did", e.Did), orm.FilterEq("rkey", e.Commit.RKey), ) if err == nil { l.Info("repo row already exists, skipping create", "did", e.Did, "rkey", e.Commit.RKey) + if err := i.ensureRepoOwnerPermissions(e.Did, existing.Knot, existing.RepoIdentifier()); err != nil { + return fmt.Errorf("failed to ensure repo owner permissions: %w", err) + } return nil } if !errors.Is(err, sql.ErrNoRows) { @@ -121,6 +124,9 @@ func (i *Ingester) ingestRepoCreate(ctx context.Context, e *jmodels.Event) error l.Warn("failed to fetch repo after rename for notification", "err", err) return nil } + if err := i.ensureRepoOwnerPermissions(e.Did, newRepo.Knot, newRepo.RepoIdentifier()); err != nil { + return fmt.Errorf("failed to ensure repo owner permissions: %w", err) + } i.Notifier.RenameRepo(ctx, syntax.DID(e.Did), &oldRepo, newRepo) return nil } @@ -158,10 +164,24 @@ func (i *Ingester) ingestRepoCreate(ctx context.Context, e *jmodels.Event) error return fmt.Errorf("failed to commit insert tx: %w", err) } + if err := i.ensureRepoOwnerPermissions(e.Did, repo.Knot, repo.RepoIdentifier()); err != nil { + return fmt.Errorf("failed to ensure repo owner permissions: %w", err) + } + i.Notifier.NewRepo(ctx, repo) return nil } +func (i *Ingester) ensureRepoOwnerPermissions(ownerDid, knot, repo string) error { + if i.Enforcer == nil { + return fmt.Errorf("ingester has no RBAC enforcer configured") + } + if err := i.Enforcer.AddRepo(ownerDid, knot, repo); err != nil { + return err + } + return i.Enforcer.E.SavePolicy() +} + func (i *Ingester) ingestRepoUpdate(ctx context.Context, e *jmodels.Event) error { l := i.Logger.With("handler", "ingestRepoUpdate", "did", e.Did, "rkey", e.Commit.RKey) diff --git a/appview/ingester_repo_test.go b/appview/ingester_repo_test.go index 6b5edf6f..7322222c 100644 --- a/appview/ingester_repo_test.go +++ b/appview/ingester_repo_test.go @@ -19,6 +19,7 @@ import ( "tangled.org/core/appview/notify" "tangled.org/core/appview/repoverify" "tangled.org/core/orm" + "tangled.org/core/rbac" ) func mustKnotURL(t *testing.T, raw string) *url.URL { @@ -69,10 +70,15 @@ func newTestIngester(t *testing.T) (*Ingester, *spyNotifier) { t.Fatalf("db.Make: %v", err) } t.Cleanup(func() { d.Close() }) + enforcer, err := rbac.NewEnforcer(path) + if err != nil { + t.Fatalf("rbac.NewEnforcer: %v", err) + } spy := &spyNotifier{} ing := &Ingester{ Db: d, + Enforcer: enforcer, Logger: slog.New(slog.NewTextHandler(io.Discard, nil)), Notifier: spy, } @@ -156,6 +162,19 @@ func loadRepo(t *testing.T, ing *Ingester, did, rkey string) *models.Repo { return r } +func assertRepoOwnerPermissions(t *testing.T, ing *Ingester, owner, knot, repo string) { + t.Helper() + for _, perm := range []string{"repo:settings", "repo:push", "repo:owner"} { + ok, err := ing.Enforcer.E.Enforce(owner, knot, repo, perm) + if err != nil { + t.Fatalf("Enforce(%q): %v", perm, err) + } + if !ok { + t.Fatalf("owner missing %s permission for %s", perm, repo) + } + } +} + func TestIngestRepo_CreateInsertsNewRow(t *testing.T) { ing, spy := newTestIngester(t) @@ -183,6 +202,7 @@ func TestIngestRepo_CreateInsertsNewRow(t *testing.T) { if spy.creates != 1 { t.Errorf("NewRepo called %d times, want 1", spy.creates) } + assertRepoOwnerPermissions(t, ing, "did:plc:akshay", "knot.example", "did:plc:repo1") } func TestIngestRepo_CreateSkipsIfRowExists(t *testing.T) { @@ -201,6 +221,7 @@ func TestIngestRepo_CreateSkipsIfRowExists(t *testing.T) { if spy.creates != 0 { t.Errorf("row already exists, NewRepo should not be called but was called %d times", spy.creates) } + assertRepoOwnerPermissions(t, ing, "did:plc:akshay", "knot.example", "did:plc:repo1") } func TestIngestRepo_CreateCascadesRename(t *testing.T) {