Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

spindle: legacy casbin rekey via tap resync nudge

Lewis: May this revision serve well! <lewis@tangled.org>

author
Lewis
committer
Tangled
date (May 12, 2026, 9:03 PM +0300) commit 21212008 parent 2b00f542 change-id wznwmtno
+493 -17
+8
rbac/rbac.go
··· 208 208 return err 209 209 } 210 210 211 + func (e *Enforcer) WipeRepoPolicies(domain, repo string) error { 212 + if err := checkRepoFormat(repo); err != nil { 213 + return err 214 + } 215 + _, err := e.E.RemoveFilteredPolicy(1, domain, repo) 216 + return err 217 + } 218 + 211 219 func (e *Enforcer) GetUserByRole(role, domain string) ([]string, error) { 212 220 var membersWithoutRoles []string 213 221
+87
spindle/casbin_copy.go
··· 1 + package spindle 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "log/slog" 7 + 8 + "github.com/bluesky-social/indigo/atproto/syntax" 9 + "tangled.org/core/rbac" 10 + "tangled.org/core/spindle/db" 11 + ) 12 + 13 + func migrateLegacyRepoCasbin(ctx context.Context, d *db.DB, e *rbac.Enforcer, logger *slog.Logger, owner syntax.DID, name string, rkey syntax.RecordKey, repoDid syntax.DID) { 14 + candidates := legacyKeyCandidates(owner, name, rkey) 15 + if siblings, err := d.SiblingRkeysForRepoDid(owner, repoDid, rkey); err == nil { 16 + var fold func(rest []string, acc []string) []string 17 + fold = func(rest []string, acc []string) []string { 18 + if len(rest) == 0 { 19 + return acc 20 + } 21 + return fold(rest[1:], append(acc, owner.String()+"/"+rest[0])) 22 + } 23 + candidates = fold(siblings, candidates) 24 + } else { 25 + logger.Warn("legacy casbin rekey: sibling lookup failed", "err", err) 26 + } 27 + if len(candidates) == 0 { 28 + return 29 + } 30 + flag := "legacy-casbin-rekey:" + repoDid.String() + ":" + rkey.String() 31 + var exists bool 32 + if err := d.QueryRowContext(ctx, `select exists (select 1 from migrations where name = ?)`, flag).Scan(&exists); err != nil { 33 + logger.Warn("legacy casbin rekey: check migration flag", "err", err) 34 + return 35 + } 36 + if exists { 37 + return 38 + } 39 + 40 + if err := e.AddRepo(owner.String(), rbac.ThisServer, repoDid.String()); err != nil { 41 + logger.Warn("legacy casbin rekey: owner add new key failed", "err", err) 42 + return 43 + } 44 + 45 + collabs, err := d.ListCollaboratorsByRepoDid(repoDid) 46 + if err != nil { 47 + logger.Warn("legacy casbin rekey: list collaborators failed", "err", err) 48 + return 49 + } 50 + 51 + var addCollabs func(remaining []db.RepoCollaborator) error 52 + addCollabs = func(remaining []db.RepoCollaborator) error { 53 + if len(remaining) == 0 { 54 + return nil 55 + } 56 + c := remaining[0] 57 + if err := e.AddCollaborator(c.Subject.String(), rbac.ThisServer, repoDid.String()); err != nil { 58 + return fmt.Errorf("AddCollaborator %s -> %s: %w", c.Subject, repoDid, err) 59 + } 60 + return addCollabs(remaining[1:]) 61 + } 62 + if err := addCollabs(collabs); err != nil { 63 + logger.Warn("legacy casbin rekey: collaborator add failed", "err", err) 64 + return 65 + } 66 + 67 + var wipeCandidates func(remaining []string) error 68 + wipeCandidates = func(remaining []string) error { 69 + if len(remaining) == 0 { 70 + return nil 71 + } 72 + if err := e.WipeRepoPolicies(rbac.ThisServer, remaining[0]); err != nil { 73 + return fmt.Errorf("WipeRepoPolicies %s: %w", remaining[0], err) 74 + } 75 + return wipeCandidates(remaining[1:]) 76 + } 77 + if err := wipeCandidates(candidates); err != nil { 78 + logger.Warn("legacy casbin rekey: wipe failed", "err", err) 79 + return 80 + } 81 + 82 + if _, err := d.ExecContext(ctx, `insert or ignore into migrations (name) values (?)`, flag); err != nil { 83 + logger.Warn("legacy casbin rekey: mark flag failed", "err", err) 84 + return 85 + } 86 + logger.Info("legacy casbin rekeyed", "owner", owner, "name", name, "rkey", rkey, "repoDid", repoDid, "candidates", candidates, "collabs", len(collabs)) 87 + }
+27
spindle/db/repos.go
··· 93 93 }, nil 94 94 } 95 95 96 + func (d *DB) SiblingRkeysForRepoDid(owner, repoDid syntax.DID, excludeRkey syntax.RecordKey) ([]string, error) { 97 + rows, err := d.Query( 98 + `select rkey from repos 99 + where owner = ? 100 + and coalesce(repo_did, '') = ? 101 + and rkey <> ?`, 102 + owner.String(), repoDid.String(), excludeRkey.String(), 103 + ) 104 + if err != nil { 105 + return nil, err 106 + } 107 + defer rows.Close() 108 + 109 + var collect func(acc []string) ([]string, error) 110 + collect = func(acc []string) ([]string, error) { 111 + if !rows.Next() { 112 + return acc, rows.Err() 113 + } 114 + var r string 115 + if err := rows.Scan(&r); err != nil { 116 + return acc, err 117 + } 118 + return collect(append(acc, r)) 119 + } 120 + return collect(nil) 121 + } 122 + 96 123 func (d *DB) GetRepoByDid(repoDid syntax.DID) (*Repo, error) { 97 124 return scanRepo(d.QueryRow( 98 125 `select knot, owner, rkey, coalesce(repo_did, '') from repos where repo_did = ?`,
+370 -17
spindle/startup_migrations_test.go
··· 3 3 import ( 4 4 "context" 5 5 "database/sql" 6 + "fmt" 6 7 "io" 7 8 "log/slog" 8 9 "path/filepath" ··· 11 12 12 13 "github.com/bluesky-social/indigo/atproto/syntax" 13 14 15 + "tangled.org/core/rbac" 14 16 "tangled.org/core/spindle/db" 15 17 "tangled.org/core/spindle/secrets" 16 18 ) ··· 74 76 return n 75 77 } 76 78 77 - func newTestSpindleDB(t *testing.T) *db.DB { 79 + func newTestSpindleDB(t *testing.T) (*db.DB, *rbac.Enforcer) { 78 80 t.Helper() 79 - d, err := db.Make(context.Background(), filepath.Join(t.TempDir(), "spindle.db")) 81 + p := filepath.Join(t.TempDir(), "spindle.db") 82 + d, err := db.Make(context.Background(), p) 80 83 if err != nil { 81 84 t.Fatalf("db.Make: %v", err) 82 85 } 83 86 t.Cleanup(func() { d.Close() }) 84 - return d 87 + e, err := rbac.NewEnforcer(p) 88 + if err != nil { 89 + t.Fatalf("rbac.NewEnforcer: %v", err) 90 + } 91 + e.E.EnableAutoSave(true) 92 + return d, e 85 93 } 86 94 87 95 func newTestVault(t *testing.T) *secrets.SqliteManager { ··· 107 115 } 108 116 } 109 117 118 + func mustAddCollab(t *testing.T, d *db.DB, owner, rkey, subject, repoDid string) { 119 + t.Helper() 120 + if err := d.AddRepoCollaborator(db.RepoCollaborator{ 121 + OwnerDid: syntax.DID(owner), 122 + Rkey: syntax.RecordKey(rkey), 123 + Subject: syntax.DID(subject), 124 + RepoDid: syntax.DID(repoDid), 125 + }); err != nil { 126 + t.Fatalf("AddRepoCollaborator(%s): %v", rkey, err) 127 + } 128 + } 129 + 110 130 func TestMigrateLegacyRepoSecrets_NameCandidate(t *testing.T) { 111 131 ctx := context.Background() 112 132 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 113 - d := newTestSpindleDB(t) 133 + d, _ := newTestSpindleDB(t) 114 134 vault := newTestVault(t) 115 135 116 136 owner := syntax.DID("did:plc:akshay") ··· 190 210 func TestMigrateLegacyRepoSecrets_RkeyCandidate(t *testing.T) { 191 211 ctx := context.Background() 192 212 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 193 - d := newTestSpindleDB(t) 213 + d, _ := newTestSpindleDB(t) 194 214 vault := newTestVault(t) 195 215 196 216 owner := syntax.DID("did:plc:akshay") ··· 218 238 func TestMigrateLegacyRepoSecrets_BothCandidates(t *testing.T) { 219 239 ctx := context.Background() 220 240 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 221 - d := newTestSpindleDB(t) 241 + d, _ := newTestSpindleDB(t) 222 242 vault := newTestVault(t) 223 243 224 244 owner := syntax.DID("did:plc:akshay") ··· 257 277 func TestMigrateLegacyRepoSecrets_PreExistingTakesPriority(t *testing.T) { 258 278 ctx := context.Background() 259 279 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 260 - d := newTestSpindleDB(t) 280 + d, _ := newTestSpindleDB(t) 261 281 vault := newTestVault(t) 262 282 263 283 owner := syntax.DID("did:plc:akshay") ··· 294 314 func TestMigrateLegacyRepoSecrets_EmptyName(t *testing.T) { 295 315 ctx := context.Background() 296 316 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 297 - d := newTestSpindleDB(t) 317 + d, _ := newTestSpindleDB(t) 298 318 vault := newTestVault(t) 299 319 300 320 owner := syntax.DID("did:plc:akshay") ··· 319 339 func TestMigrateLegacyRepoSecrets_BothEmpty(t *testing.T) { 320 340 ctx := context.Background() 321 341 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 322 - d := newTestSpindleDB(t) 342 + d, _ := newTestSpindleDB(t) 323 343 vault := newTestVault(t) 324 344 325 345 owner := syntax.DID("did:plc:akshay") ··· 347 367 } 348 368 } 349 369 370 + func TestMigrateLegacyRepoCasbin_NameCandidate(t *testing.T) { 371 + ctx := context.Background() 372 + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 373 + d, e := newTestSpindleDB(t) 374 + 375 + if err := e.AddSpindle(rbacDomain); err != nil { 376 + t.Fatalf("AddSpindle: %v", err) 377 + } 378 + 379 + owner := "did:plc:akshay" 380 + repoDid := "did:plc:boltless" 381 + displayName := "myrepo" 382 + rkey := "3kspindlerkey00a" 383 + collab := "did:plc:limpet" 384 + oldNameKey := owner + "/" + displayName 385 + oldRkeyKey := owner + "/" + rkey 386 + 387 + mustAddCollab(t, d, owner, "3kcollabrkey0001", collab, repoDid) 388 + 389 + if err := e.AddRepo(owner, rbacDomain, oldNameKey); err != nil { 390 + t.Fatalf("seed AddRepo at Name key: %v", err) 391 + } 392 + if err := e.AddCollaborator(collab, rbacDomain, oldNameKey); err != nil { 393 + t.Fatalf("seed AddCollaborator at Name key: %v", err) 394 + } 395 + 396 + migrateLegacyRepoCasbin(ctx, d, e, logger, syntax.DID(owner), displayName, syntax.RecordKey(rkey), syntax.DID(repoDid)) 397 + 398 + if got, err := e.IsSettingsAllowed(owner, rbacDomain, repoDid); err != nil || !got { 399 + t.Errorf("owner should have settings at new repoDid key, allowed=%v err=%v", got, err) 400 + } 401 + if got, err := e.IsSettingsAllowed(collab, rbacDomain, repoDid); err != nil || !got { 402 + t.Errorf("collab should have settings at new repoDid key, allowed=%v err=%v", got, err) 403 + } 404 + if got, err := e.IsSettingsAllowed(owner, rbacDomain, oldNameKey); err != nil || got { 405 + t.Errorf("owner Name-keyed policy should be removed, allowed=%v err=%v", got, err) 406 + } 407 + if got, err := e.IsSettingsAllowed(collab, rbacDomain, oldNameKey); err != nil || got { 408 + t.Errorf("collab Name-keyed policy should be removed, allowed=%v err=%v", got, err) 409 + } 410 + if got, err := e.IsSettingsAllowed(owner, rbacDomain, oldRkeyKey); err != nil || got { 411 + t.Errorf("owner rkey-keyed policy should be absent (never added), allowed=%v err=%v", got, err) 412 + } 413 + 414 + migrateLegacyRepoCasbin(ctx, d, e, logger, syntax.DID(owner), displayName, syntax.RecordKey(rkey), syntax.DID(repoDid)) 415 + 416 + if got, err := e.IsSettingsAllowed(collab, rbacDomain, repoDid); err != nil || !got { 417 + t.Errorf("collab settings still expected after idempotent re-run, allowed=%v err=%v", got, err) 418 + } 419 + 420 + var marked int 421 + if err := d.QueryRow( 422 + `select count(*) from migrations where name = ?`, 423 + "legacy-casbin-rekey:"+repoDid+":"+rkey, 424 + ).Scan(&marked); err != nil { 425 + t.Fatalf("query migrations: %v", err) 426 + } 427 + if marked != 1 { 428 + t.Errorf("expected per-repo flag recorded exactly once, got %d", marked) 429 + } 430 + } 431 + 432 + func TestMigrateLegacyRepoCasbin_RkeyCandidate(t *testing.T) { 433 + ctx := context.Background() 434 + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 435 + d, e := newTestSpindleDB(t) 436 + 437 + if err := e.AddSpindle(rbacDomain); err != nil { 438 + t.Fatalf("AddSpindle: %v", err) 439 + } 440 + 441 + owner := "did:plc:akshay" 442 + repoDid := "did:plc:boltless" 443 + displayName := "myrepo" 444 + rkey := "3kspindlerkey00a" 445 + collab := "did:plc:limpet" 446 + oldRkeyKey := owner + "/" + rkey 447 + 448 + mustAddCollab(t, d, owner, "3kcollabrkey0001", collab, repoDid) 449 + 450 + if err := e.AddRepo(owner, rbacDomain, oldRkeyKey); err != nil { 451 + t.Fatalf("seed AddRepo at rkey: %v", err) 452 + } 453 + if err := e.AddCollaborator(collab, rbacDomain, oldRkeyKey); err != nil { 454 + t.Fatalf("seed AddCollaborator at rkey: %v", err) 455 + } 456 + 457 + migrateLegacyRepoCasbin(ctx, d, e, logger, syntax.DID(owner), displayName, syntax.RecordKey(rkey), syntax.DID(repoDid)) 458 + 459 + if got, err := e.IsSettingsAllowed(owner, rbacDomain, repoDid); err != nil || !got { 460 + t.Errorf("owner should have settings at new repoDid key, allowed=%v err=%v", got, err) 461 + } 462 + if got, err := e.IsSettingsAllowed(collab, rbacDomain, repoDid); err != nil || !got { 463 + t.Errorf("collab should have settings at new repoDid key, allowed=%v err=%v", got, err) 464 + } 465 + if got, err := e.IsSettingsAllowed(owner, rbacDomain, oldRkeyKey); err != nil || got { 466 + t.Errorf("owner rkey-keyed policy should be removed, allowed=%v err=%v", got, err) 467 + } 468 + if got, err := e.IsSettingsAllowed(collab, rbacDomain, oldRkeyKey); err != nil || got { 469 + t.Errorf("collab rkey-keyed policy should be removed, allowed=%v err=%v", got, err) 470 + } 471 + } 472 + 473 + func TestMigrateLegacyRepoCasbin_BothCandidates(t *testing.T) { 474 + ctx := context.Background() 475 + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 476 + d, e := newTestSpindleDB(t) 477 + 478 + if err := e.AddSpindle(rbacDomain); err != nil { 479 + t.Fatalf("AddSpindle: %v", err) 480 + } 481 + 482 + owner := "did:plc:akshay" 483 + repoDid := "did:plc:boltless" 484 + displayName := "myrepo" 485 + rkey := "3kspindlerkey00a" 486 + collab := "did:plc:limpet" 487 + oldNameKey := owner + "/" + displayName 488 + oldRkeyKey := owner + "/" + rkey 489 + 490 + mustAddCollab(t, d, owner, "3kcollabrkey0001", collab, repoDid) 491 + 492 + if err := e.AddRepo(owner, rbacDomain, oldNameKey); err != nil { 493 + t.Fatalf("seed AddRepo at Name key: %v", err) 494 + } 495 + if err := e.AddRepo(owner, rbacDomain, oldRkeyKey); err != nil { 496 + t.Fatalf("seed AddRepo at rkey: %v", err) 497 + } 498 + if err := e.AddCollaborator(collab, rbacDomain, oldNameKey); err != nil { 499 + t.Fatalf("seed AddCollaborator at Name key: %v", err) 500 + } 501 + if err := e.AddCollaborator(collab, rbacDomain, oldRkeyKey); err != nil { 502 + t.Fatalf("seed AddCollaborator at rkey: %v", err) 503 + } 504 + 505 + migrateLegacyRepoCasbin(ctx, d, e, logger, syntax.DID(owner), displayName, syntax.RecordKey(rkey), syntax.DID(repoDid)) 506 + 507 + if got, err := e.IsSettingsAllowed(owner, rbacDomain, oldNameKey); err != nil || got { 508 + t.Errorf("owner Name-keyed policy should be removed, allowed=%v err=%v", got, err) 509 + } 510 + if got, err := e.IsSettingsAllowed(owner, rbacDomain, oldRkeyKey); err != nil || got { 511 + t.Errorf("owner rkey-keyed policy should be removed, allowed=%v err=%v", got, err) 512 + } 513 + if got, err := e.IsSettingsAllowed(collab, rbacDomain, oldNameKey); err != nil || got { 514 + t.Errorf("collab Name-keyed policy should be removed, allowed=%v err=%v", got, err) 515 + } 516 + if got, err := e.IsSettingsAllowed(collab, rbacDomain, oldRkeyKey); err != nil || got { 517 + t.Errorf("collab rkey-keyed policy should be removed, allowed=%v err=%v", got, err) 518 + } 519 + } 520 + 521 + func TestMigrateLegacyRepoCasbin_BothEmpty(t *testing.T) { 522 + ctx := context.Background() 523 + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 524 + d, e := newTestSpindleDB(t) 525 + 526 + if err := e.AddSpindle(rbacDomain); err != nil { 527 + t.Fatalf("AddSpindle: %v", err) 528 + } 529 + 530 + owner := syntax.DID("did:plc:akshay") 531 + repoDid := syntax.DID("did:plc:boltless") 532 + 533 + migrateLegacyRepoCasbin(ctx, d, e, logger, owner, "", "", repoDid) 534 + 535 + var marked int 536 + if err := d.QueryRow( 537 + `select count(*) from migrations where name like ?`, 538 + "legacy-casbin-rekey:"+repoDid.String()+":%", 539 + ).Scan(&marked); err != nil { 540 + t.Fatalf("query migrations: %v", err) 541 + } 542 + if marked != 0 { 543 + t.Errorf("empty inputs should not record flag, got %d", marked) 544 + } 545 + } 546 + 350 547 func TestNudgeTapForResync(t *testing.T) { 351 548 ctx := context.Background() 352 549 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 353 - d := newTestSpindleDB(t) 550 + d, _ := newTestSpindleDB(t) 354 551 355 552 tapPath := filepath.Join(t.TempDir(), "tap.db") 356 553 seedTapDB(t, tapPath) ··· 422 619 func TestNudgeTapForResync_MissingDB(t *testing.T) { 423 620 ctx := context.Background() 424 621 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 425 - d := newTestSpindleDB(t) 622 + d, _ := newTestSpindleDB(t) 426 623 427 624 missing := filepath.Join(t.TempDir(), "absent.db") 428 625 ··· 445 642 func TestNudgeTapForResync_EmptyPath(t *testing.T) { 446 643 ctx := context.Background() 447 644 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 448 - d := newTestSpindleDB(t) 645 + d, _ := newTestSpindleDB(t) 449 646 450 647 if err := nudgeTapForResync(ctx, d, "", logger); err == nil { 451 648 t.Errorf("expected error for empty tap db path") ··· 466 663 func TestRunStartupMigrations_NonEmbedSkipsTapNudge(t *testing.T) { 467 664 ctx := context.Background() 468 665 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 469 - d := newTestSpindleDB(t) 666 + d, _ := newTestSpindleDB(t) 470 667 471 668 if err := runStartupMigrations(ctx, d, false, "", logger); err != nil { 472 669 t.Fatalf("non-embed should not error on empty path: %v", err) ··· 487 684 func TestCleanupOrphanRepos_DeletesWhenSiblingExists(t *testing.T) { 488 685 ctx := context.Background() 489 686 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 490 - d := newTestSpindleDB(t) 687 + d, _ := newTestSpindleDB(t) 491 688 492 689 owner := "did:plc:akshay" 493 690 if _, err := d.Exec(`insert into repos (knot, owner, rkey, repo_did, created_at) values ··· 521 718 func TestCleanupOrphanRepos_KeepsWhenAlone(t *testing.T) { 522 719 ctx := context.Background() 523 720 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 524 - d := newTestSpindleDB(t) 721 + d, _ := newTestSpindleDB(t) 525 722 526 723 owner := "did:plc:akshay" 527 724 if _, err := d.Exec(`insert into repos (knot, owner, rkey, repo_did, created_at) values ··· 545 742 func TestCleanupOrphanRepos_PerOwnerScope(t *testing.T) { 546 743 ctx := context.Background() 547 744 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 548 - d := newTestSpindleDB(t) 745 + d, _ := newTestSpindleDB(t) 549 746 550 747 ownerA := "did:plc:akshay" 551 748 ownerB := "did:plc:limpet" ··· 579 776 func TestCleanupOrphanRepos_EmptyStringRepoDid(t *testing.T) { 580 777 ctx := context.Background() 581 778 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 582 - d := newTestSpindleDB(t) 779 + d, _ := newTestSpindleDB(t) 583 780 584 781 owner := "did:plc:akshay" 585 782 if _, err := d.Exec(`insert into repos (knot, owner, rkey, repo_did, created_at) values ··· 601 798 t.Errorf("empty-string repo_did orphan should be deleted when sibling exists, got %d remaining", emptyCount) 602 799 } 603 800 } 801 + 802 + func TestMigrateLegacyRepoCasbin_MultipleCollabsAllRekeyed(t *testing.T) { 803 + ctx := context.Background() 804 + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 805 + d, e := newTestSpindleDB(t) 806 + 807 + if err := e.AddSpindle(rbacDomain); err != nil { 808 + t.Fatalf("AddSpindle: %v", err) 809 + } 810 + 811 + owner := "did:plc:akshay" 812 + repoDid := "did:plc:boltless" 813 + displayName := "myrepo" 814 + rkey := "3kspindlerkey00a" 815 + oldNameKey := owner + "/" + displayName 816 + collabs := []string{"did:plc:limpet", "did:plc:nautilus", "did:plc:whelk", "did:plc:cuttle"} 817 + 818 + var addCollabRows func(rest []string, idx int) 819 + addCollabRows = func(rest []string, idx int) { 820 + if len(rest) == 0 { 821 + return 822 + } 823 + mustAddCollab(t, d, owner, fmt.Sprintf("3kcollabrkey%04d", idx), rest[0], repoDid) 824 + addCollabRows(rest[1:], idx+1) 825 + } 826 + addCollabRows(collabs, 0) 827 + 828 + if err := e.AddRepo(owner, rbacDomain, oldNameKey); err != nil { 829 + t.Fatalf("seed owner: %v", err) 830 + } 831 + var seedAll func(rest []string) error 832 + seedAll = func(rest []string) error { 833 + if len(rest) == 0 { 834 + return nil 835 + } 836 + if err := e.AddCollaborator(rest[0], rbacDomain, oldNameKey); err != nil { 837 + return err 838 + } 839 + return seedAll(rest[1:]) 840 + } 841 + if err := seedAll(collabs); err != nil { 842 + t.Fatalf("seed collab policies: %v", err) 843 + } 844 + 845 + migrateLegacyRepoCasbin(ctx, d, e, logger, syntax.DID(owner), displayName, syntax.RecordKey(rkey), syntax.DID(repoDid)) 846 + 847 + var assertEach func(rest []string) 848 + assertEach = func(rest []string) { 849 + if len(rest) == 0 { 850 + return 851 + } 852 + c := rest[0] 853 + if got, err := e.IsSettingsAllowed(c, rbacDomain, repoDid); err != nil || !got { 854 + t.Errorf("collab %s should have settings at repoDid, allowed=%v err=%v", c, got, err) 855 + } 856 + if got, err := e.IsPushAllowed(c, rbacDomain, repoDid); err != nil || !got { 857 + t.Errorf("collab %s should have push at repoDid, allowed=%v err=%v", c, got, err) 858 + } 859 + if got, err := e.IsSettingsAllowed(c, rbacDomain, oldNameKey); err != nil || got { 860 + t.Errorf("collab %s old policy should be wiped, allowed=%v err=%v", c, got, err) 861 + } 862 + assertEach(rest[1:]) 863 + } 864 + assertEach(collabs) 865 + } 866 + 867 + func TestMigrateLegacyRepoCasbin_RenameSiblingsEachWiped(t *testing.T) { 868 + ctx := context.Background() 869 + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 870 + d, e := newTestSpindleDB(t) 871 + 872 + if err := e.AddSpindle(rbacDomain); err != nil { 873 + t.Fatalf("AddSpindle: %v", err) 874 + } 875 + 876 + owner := syntax.DID("did:plc:akshay") 877 + repoDid := syntax.DID("did:plc:di4gol2smljyj6gjnjdu5qrg") 878 + siblings := []string{"pre-rename-life", "i-renamed-this", "post-rename-rename", "post-rename-renamed-again"} 879 + 880 + var seedAll func(rest []string) error 881 + seedAll = func(rest []string) error { 882 + if len(rest) == 0 { 883 + return nil 884 + } 885 + if err := e.AddRepo(owner.String(), rbacDomain, owner.String()+"/"+rest[0]); err != nil { 886 + return err 887 + } 888 + return seedAll(rest[1:]) 889 + } 890 + if err := seedAll(siblings); err != nil { 891 + t.Fatalf("seed siblings: %v", err) 892 + } 893 + 894 + var run func(rest []string) 895 + run = func(rest []string) { 896 + if len(rest) == 0 { 897 + return 898 + } 899 + migrateLegacyRepoCasbin(ctx, d, e, logger, owner, "", syntax.RecordKey(rest[0]), repoDid) 900 + run(rest[1:]) 901 + } 902 + run(siblings) 903 + 904 + var assertWiped func(rest []string) 905 + assertWiped = func(rest []string) { 906 + if len(rest) == 0 { 907 + return 908 + } 909 + key := owner.String() + "/" + rest[0] 910 + if got, err := e.IsSettingsAllowed(owner.String(), rbacDomain, key); err != nil || got { 911 + t.Errorf("rename sibling %s should be wiped, allowed=%v err=%v", rest[0], got, err) 912 + } 913 + assertWiped(rest[1:]) 914 + } 915 + assertWiped(siblings) 916 + 917 + if got, err := e.IsSettingsAllowed(owner.String(), rbacDomain, repoDid.String()); err != nil || !got { 918 + t.Errorf("owner should retain settings at repoDid, allowed=%v err=%v", got, err) 919 + } 920 + } 921 + 922 + func TestMigrateLegacyRepoCasbin_StrandedCollabWiped(t *testing.T) { 923 + ctx := context.Background() 924 + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 925 + d, e := newTestSpindleDB(t) 926 + 927 + if err := e.AddSpindle(rbacDomain); err != nil { 928 + t.Fatalf("AddSpindle: %v", err) 929 + } 930 + 931 + owner := "did:plc:akshay" 932 + repoDid := "did:plc:boltless" 933 + displayName := "myrepo" 934 + rkey := "3kspindlerkey00a" 935 + strandedCollab := "did:plc:nautilus" 936 + oldNameKey := owner + "/" + displayName 937 + 938 + if err := e.AddRepo(owner, rbacDomain, oldNameKey); err != nil { 939 + t.Fatalf("seed AddRepo at Name key: %v", err) 940 + } 941 + if err := e.AddCollaborator(strandedCollab, rbacDomain, oldNameKey); err != nil { 942 + t.Fatalf("seed stranded collab at Name key: %v", err) 943 + } 944 + 945 + migrateLegacyRepoCasbin(ctx, d, e, logger, syntax.DID(owner), displayName, syntax.RecordKey(rkey), syntax.DID(repoDid)) 946 + 947 + if got, err := e.IsSettingsAllowed(strandedCollab, rbacDomain, oldNameKey); err != nil || got { 948 + t.Errorf("stranded collab should be wiped from old key, allowed=%v err=%v", got, err) 949 + } 950 + if got, err := e.IsSettingsAllowed(owner, rbacDomain, oldNameKey); err != nil || got { 951 + t.Errorf("owner old policy should be wiped, allowed=%v err=%v", got, err) 952 + } 953 + if got, err := e.IsSettingsAllowed(owner, rbacDomain, repoDid); err != nil || !got { 954 + t.Errorf("owner should have settings at new repoDid key, allowed=%v err=%v", got, err) 955 + } 956 + }
+1
spindle/tapclient.go
··· 138 138 legacyName = *record.Name 139 139 } 140 140 migrateLegacyRepoSecrets(ctx, t.spindle.db, t.spindle.vault, l, ownerDid, legacyName, rkey, repoDid) 141 + migrateLegacyRepoCasbin(ctx, t.spindle.db, t.spindle.e, l, ownerDid, legacyName, rkey, repoDid) 141 142 142 143 if removed, err := t.spindle.db.CollapseRepoSiblings(ownerDid, repoDid); err != nil { 143 144 l.Warn("collapse rename siblings failed", "err", err)