From 92e48cc86422b0382d2370df65d8725924c722ee Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 3 Jun 2026 16:06:45 +0000 Subject: [PATCH] test: include per-org default roles in rbac user subjects Aligns coderdtest.AuthzUserSubjectWithDB with GetAuthorizationUserRoles so tests built via this helper see the per-org default_org_member_roles union, not just organization-member. Also moves the single-use MemberSubject helper into its sole consumer and unexports it. Refs #25936. --- coderd/coderdtest/coderdtest.go | 10 +++++++ coderd/coderdtest/subjects.go | 31 --------------------- coderd/workspaceconnwatcher/watcher_test.go | 30 +++++++++++++++++++- testutil/websocket.go | 2 +- 4 files changed, 40 insertions(+), 33 deletions(-) delete mode 100644 coderd/coderdtest/subjects.go diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 5b46450c3cd..0a34b5fcb21 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -908,6 +908,16 @@ func AuthzUserSubjectWithDB(ctx context.Context, t testing.TB, db database.Store require.NoError(t, err) for _, org := range orgs { roles = append(roles, rbac.ScopedRoleOrgMember(org.ID)) + // The implicit role set (organization-member plus the org's + // default_org_member_roles) is unioned at request time by + // GetAuthorizationUserRoles. Subjects built directly here bypass + // that SQL union, so mirror it explicitly. + for _, name := range org.DefaultOrgMemberRoles { + roles = append(roles, rbac.RoleIdentifier{ + Name: name, + OrganizationID: org.ID, + }) + } } //nolint:gocritic // We need to expand DB-backed/system roles. The caller diff --git a/coderd/coderdtest/subjects.go b/coderd/coderdtest/subjects.go deleted file mode 100644 index 97d61af42be..00000000000 --- a/coderd/coderdtest/subjects.go +++ /dev/null @@ -1,31 +0,0 @@ -package coderdtest - -import ( - "github.com/google/uuid" - - "github.com/coder/coder/v2/coderd/rbac" - "github.com/coder/coder/v2/coderd/rbac/rolestore" -) - -func MemberSubject(userID, orgID uuid.UUID) rbac.Subject { - memberRole, err := rbac.RoleByName(rbac.RoleMember()) - if err != nil { - panic(err) - } - orgMember, err := rolestore.TestingGetSystemRole( - rbac.RoleOrgMember(), - orgID, - rbac.OrgSettings{ShareableWorkspaceOwners: rbac.ShareableWorkspaceOwnersNone}, - ) - if err != nil { - panic(err) - } - return rbac.Subject{ - FriendlyName: "coderdtest-member", - Email: "member@coderd.test", - Type: rbac.SubjectTypeUser, - ID: userID.String(), - Roles: rbac.Roles{memberRole, orgMember}, - Scope: rbac.ScopeAll, - }.WithCachedASTValue() -} diff --git a/coderd/workspaceconnwatcher/watcher_test.go b/coderd/workspaceconnwatcher/watcher_test.go index beeb6c2594c..9c0434bc347 100644 --- a/coderd/workspaceconnwatcher/watcher_test.go +++ b/coderd/workspaceconnwatcher/watcher_test.go @@ -19,6 +19,8 @@ import ( "github.com/coder/coder/v2/coderd/database/dbmock" "github.com/coder/coder/v2/coderd/database/pubsub" "github.com/coder/coder/v2/coderd/httpmw" + "github.com/coder/coder/v2/coderd/rbac" + "github.com/coder/coder/v2/coderd/rbac/rolestore" "github.com/coder/coder/v2/coderd/workspaceconnwatcher" "github.com/coder/coder/v2/coderd/wspubsub" "github.com/coder/coder/v2/codersdk" @@ -72,7 +74,7 @@ func (h *harness) Dial(ctx context.Context, url string) (*wsjson.Decoder[workspa Handler: http.HandlerFunc(h.watcher.WorkspaceAgentConnectionWatch), CtxMutator: func(ctx context.Context) context.Context { ctx = httpmw.WithWorkspaceParam(ctx, h.workspace) - ctx = dbauthz.As(ctx, coderdtest.MemberSubject(userID, orgID)) + ctx = dbauthz.As(ctx, memberSubject(userID, orgID)) return ctx }, Logger: h.logger.Named("roundtripper"), @@ -470,3 +472,29 @@ func TestWatcher_ClosedAfterDial(t *testing.T) { } testutil.TryReceive(ctx, t, closed) } + +// memberSubject builds an RBAC subject scoped as a basic org member, used to +// drive the watcher handler through dbauthz checks. Kept local to this test +// because no other package needs it. +func memberSubject(userID, orgID uuid.UUID) rbac.Subject { + memberRole, err := rbac.RoleByName(rbac.RoleMember()) + if err != nil { + panic(err) + } + orgMember, err := rolestore.TestingGetSystemRole( + rbac.RoleOrgMember(), + orgID, + rbac.OrgSettings{ShareableWorkspaceOwners: rbac.ShareableWorkspaceOwnersNone}, + ) + if err != nil { + panic(err) + } + return rbac.Subject{ + FriendlyName: "coderdtest-member", + Email: "member@coderd.test", + Type: rbac.SubjectTypeUser, + ID: userID.String(), + Roles: rbac.Roles{memberRole, orgMember}, + Scope: rbac.ScopeAll, + }.WithCachedASTValue() +} diff --git a/testutil/websocket.go b/testutil/websocket.go index 026f1c3590f..7f570dfb25d 100644 --- a/testutil/websocket.go +++ b/testutil/websocket.go @@ -19,7 +19,7 @@ import ( // Handler: MyHandler, // CtxMutator: func(ctx context.Context) context.Context { // ctx = httpmw.WithWorkspaceParam(ctx, ws) -// ctx = dbauthz.As(ctx, coderdtest.MemberSubject(userID, orgID)) +// ctx = dbauthz.As(ctx, mySubject(userID, orgID)) // return ctx // }, // Logger: logger.Named("roundtripper"),