Skip to content

Commit 12dfd05

Browse files
committed
feat: remove user from groups on org membership delete
Groups inherently provide authz access to certain resources. If a user is removed from an organization, they should be removed from all their groups in said organization.
1 parent 370f0b9 commit 12dfd05

File tree

5 files changed

+99
-1
lines changed

5 files changed

+99
-1
lines changed

coderd/database/dbmem/dbmem.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1922,7 +1922,7 @@ func (q *FakeQuerier) DeleteOrganization(_ context.Context, id uuid.UUID) error
19221922
return sql.ErrNoRows
19231923
}
19241924

1925-
func (q *FakeQuerier) DeleteOrganizationMember(_ context.Context, arg database.DeleteOrganizationMemberParams) error {
1925+
func (q *FakeQuerier) DeleteOrganizationMember(ctx context.Context, arg database.DeleteOrganizationMemberParams) error {
19261926
err := validateDatabaseType(arg)
19271927
if err != nil {
19281928
return err
@@ -1937,6 +1937,16 @@ func (q *FakeQuerier) DeleteOrganizationMember(_ context.Context, arg database.D
19371937
if len(deleted) == 0 {
19381938
return sql.ErrNoRows
19391939
}
1940+
1941+
// Delete group member trigger
1942+
q.groupMembers = slices.DeleteFunc(q.groupMembers, func(member database.GroupMemberTable) bool {
1943+
if member.UserID != arg.UserID {
1944+
return false
1945+
}
1946+
g, _ := q.getGroupByIDNoLock(ctx, member.GroupID)
1947+
return g.OrganizationID == arg.OrganizationID
1948+
})
1949+
19401950
return nil
19411951
}
19421952

coderd/database/dump.sql

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
DROP TRIGGER IF EXISTS trigger_delete_group_members_on_org_member_delete ON organization_members;
2+
DROP FUNCTION IF EXISTS delete_group_members_on_org_member_delete;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
CREATE FUNCTION delete_group_members_on_org_member_delete() RETURNS TRIGGER
2+
LANGUAGE plpgsql
3+
AS $$
4+
DECLARE
5+
BEGIN
6+
-- Remove the user from all groups associated with the same
7+
-- organization as the organization_member being deleted.
8+
DELETE FROM group_members
9+
WHERE
10+
user_id = OLD.user_id
11+
AND group_id IN (
12+
SELECT id
13+
FROM groups
14+
WHERE organization_id = OLD.organization_id
15+
);
16+
RETURN OLD;
17+
END;
18+
$$;
19+
20+
CREATE TRIGGER trigger_delete_group_members_on_org_member_delete
21+
BEFORE DELETE ON organization_members
22+
FOR EACH ROW
23+
EXECUTE PROCEDURE delete_group_members_on_org_member_delete();

enterprise/members_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func TestEnterpriseMembers(t *testing.T) {
2929
LicenseOptions: &coderdenttest.LicenseOptions{
3030
Features: license.Features{
3131
codersdk.FeatureMultipleOrganizations: 1,
32+
codersdk.FeatureTemplateRBAC: 1,
3233
},
3334
},
3435
})
@@ -39,6 +40,21 @@ func TestEnterpriseMembers(t *testing.T) {
3940
_, user := coderdtest.CreateAnotherUser(t, owner, secondOrg.ID)
4041

4142
ctx := testutil.Context(t, testutil.WaitMedium)
43+
44+
// Groups exist to ensure a user removed from the org loses their
45+
// group access.
46+
g1, err := orgAdminClient.CreateGroup(ctx, secondOrg.ID, codersdk.CreateGroupRequest{
47+
Name: "foo",
48+
DisplayName: "Foo",
49+
})
50+
require.NoError(t, err)
51+
52+
g2, err := orgAdminClient.CreateGroup(ctx, secondOrg.ID, codersdk.CreateGroupRequest{
53+
Name: "bar",
54+
DisplayName: "Bar",
55+
})
56+
require.NoError(t, err)
57+
4258
// Verify the org of 3 members
4359
members, err := orgAdminClient.OrganizationMembers(ctx, secondOrg.ID)
4460
require.NoError(t, err)
@@ -47,6 +63,25 @@ func TestEnterpriseMembers(t *testing.T) {
4763
[]uuid.UUID{first.UserID, user.ID, orgAdmin.ID},
4864
db2sdk.List(members, onlyIDs))
4965

66+
// Add the member to some groups
67+
_, err = orgAdminClient.PatchGroup(ctx, g1.ID, codersdk.PatchGroupRequest{
68+
AddUsers: []string{user.ID.String()},
69+
})
70+
require.NoError(t, err)
71+
72+
_, err = orgAdminClient.PatchGroup(ctx, g2.ID, codersdk.PatchGroupRequest{
73+
AddUsers: []string{user.ID.String()},
74+
})
75+
require.NoError(t, err)
76+
77+
// Verify group membership
78+
userGroups, err := orgAdminClient.Groups(ctx, codersdk.GroupArguments{
79+
HasMember: user.ID.String(),
80+
})
81+
require.NoError(t, err)
82+
// Everyone group + 2 groups
83+
require.Len(t, userGroups, 3)
84+
5085
// Delete a member
5186
err = orgAdminClient.DeleteOrganizationMember(ctx, secondOrg.ID, user.Username)
5287
require.NoError(t, err)
@@ -57,6 +92,13 @@ func TestEnterpriseMembers(t *testing.T) {
5792
require.ElementsMatch(t,
5893
[]uuid.UUID{first.UserID, orgAdmin.ID},
5994
db2sdk.List(members, onlyIDs))
95+
96+
// User should now belong to 0 groups
97+
userGroups, err = orgAdminClient.Groups(ctx, codersdk.GroupArguments{
98+
HasMember: user.ID.String(),
99+
})
100+
require.NoError(t, err)
101+
require.Len(t, userGroups, 0)
60102
})
61103

62104
t.Run("PostUser", func(t *testing.T) {

0 commit comments

Comments
 (0)