Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Add instance-wide roles and refine access UI

5b814e9958090cfd1fa826efb2d375bf613547c1 · Keenan Nemetz · 14d ago · parent 9579c1f9

Add instance-wide roles and refine access UI

Instance roles now live on accounts, apply to public repositories, and the Admin role is the unified instance-admin permission with access to every repository. Repository ACLs can raise an instance role but not lower it.

Adds role assignment to the users UI and CLI, open-registration role config, a reusable role picker, and separates adding a repository account from saving access rules.

forge/identity/identity_test.go +56

@@ -9,6 +9,7 @@ import (
9 9 "sync"
10 10 "testing"
11 11
12 "github.com/rodent-software/vole/forge/access"
12 13 "golang.org/x/crypto/ssh"
13 14 )
14 15
@@ -616,6 +617,61 @@ func TestInstanceAdminsAreExplicitAndAudited(t *testing.T) {
616 617 }
617 618 }
618 619
620 func TestInstanceRolesAreAccountStanding(t *testing.T) {
621 t.Parallel()
622 s := newStore(t)
623 line, _ := pubLine(t)
624 alice, err := s.Create("alice", line, "operator")
625 if err != nil {
626 t.Fatal(err)
627 }
628 if alice.Role != access.None || s.InstanceRole(alice.Name) != access.None {
629 t.Fatalf("new account role = %v", alice.Role)
630 }
631 if _, err := s.SetInstanceRole(alice.Name, access.Contribute, "operator"); err != nil {
632 t.Fatal(err)
633 }
634 a, err := s.Find(alice.Name)
635 if err != nil {
636 t.Fatal(err)
637 }
638 if a.Role != access.Contribute || a.Admin || s.IsAdmin(alice.Name) {
639 t.Fatalf("instance role = %v admin=%t", a.Role, a.Admin)
640 }
641 if s.InstanceRole(alice.Name) != access.Contribute {
642 t.Fatalf("InstanceRole = %v", s.InstanceRole(alice.Name))
643 }
644 bob, inv, err := s.CreateWithInviteRole("bob", access.Maintain, "operator", 0)
645 if err != nil || inv == nil {
646 t.Fatal(err)
647 }
648 if bob.Role != access.Maintain {
649 t.Fatalf("invited role = %v", bob.Role)
650 }
651 if _, err := s.SetInstanceRole(alice.Name, access.Admin, "operator"); err != nil {
652 t.Fatal(err)
653 }
654 if !s.IsAdmin(alice.Name) {
655 t.Fatal("Admin role did not grant instance admin")
656 }
657 if _, err := s.SetInstanceRole(alice.Name, access.None, "operator"); err == nil {
658 t.Fatal("removed the last instance admin")
659 }
660 if _, err := s.Retire(bob.Name, "operator"); err != nil {
661 t.Fatal(err)
662 }
663 if got := s.InstanceRole(bob.Name); got != access.None {
664 t.Fatalf("retired account role = %v, want none", got)
665 }
666 log, err := os.ReadFile(s.AuditPath)
667 if err != nil {
668 t.Fatal(err)
669 }
670 if !strings.Contains(string(log), "identity.set-role\toperator\talice\trole=contribute") {
671 t.Fatalf("role change was not audited:\n%s", log)
672 }
673 }
674
619 675 func TestLastInstanceAdminCannotBeRemoved(t *testing.T) {
620 676 t.Parallel()
621 677 s := newStore(t)