Skip to content

Commit 0ad5f60

Browse files
authored
chore: prevent removing members from the default organization (coder#14094)
* chore: prevent removing members from the default organization Until multi-organizations is released outside an experiment, the experiment should be backwards compatible.
1 parent 173dc0e commit 0ad5f60

File tree

5 files changed

+127
-74
lines changed

5 files changed

+127
-74
lines changed

cli/organizationmembers_test.go

-46
Original file line numberDiff line numberDiff line change
@@ -34,49 +34,3 @@ func TestListOrganizationMembers(t *testing.T) {
3434
require.Contains(t, buf.String(), owner.UserID.String())
3535
})
3636
}
37-
38-
func TestRemoveOrganizationMembers(t *testing.T) {
39-
t.Parallel()
40-
41-
t.Run("OK", func(t *testing.T) {
42-
t.Parallel()
43-
44-
ownerClient := coderdtest.New(t, &coderdtest.Options{})
45-
owner := coderdtest.CreateFirstUser(t, ownerClient)
46-
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID))
47-
_, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
48-
49-
ctx := testutil.Context(t, testutil.WaitMedium)
50-
51-
inv, root := clitest.New(t, "organization", "members", "remove", "-O", owner.OrganizationID.String(), user.Username)
52-
clitest.SetupConfig(t, orgAdminClient, root)
53-
54-
buf := new(bytes.Buffer)
55-
inv.Stdout = buf
56-
err := inv.WithContext(ctx).Run()
57-
require.NoError(t, err)
58-
59-
members, err := orgAdminClient.OrganizationMembers(ctx, owner.OrganizationID)
60-
require.NoError(t, err)
61-
62-
require.Len(t, members, 2)
63-
})
64-
65-
t.Run("UserNotExists", func(t *testing.T) {
66-
t.Parallel()
67-
68-
ownerClient := coderdtest.New(t, &coderdtest.Options{})
69-
owner := coderdtest.CreateFirstUser(t, ownerClient)
70-
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID))
71-
72-
ctx := testutil.Context(t, testutil.WaitMedium)
73-
74-
inv, root := clitest.New(t, "organization", "members", "remove", "-O", owner.OrganizationID.String(), "random_name")
75-
clitest.SetupConfig(t, orgAdminClient, root)
76-
77-
buf := new(bytes.Buffer)
78-
inv.Stdout = buf
79-
err := inv.WithContext(ctx).Run()
80-
require.ErrorContains(t, err, "must be an existing uuid or username")
81-
})
82-
}

coderd/members.go

+13
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,19 @@ func (api *API) deleteOrganizationMember(rw http.ResponseWriter, r *http.Request
106106
aReq.Old = member.OrganizationMember.Auditable(member.Username)
107107
defer commitAudit()
108108

109+
if organization.IsDefault {
110+
// Multi-organizations is currently an experiment, which means it is feasible
111+
// for a deployment to enable, then disable this. To maintain backwards
112+
// compatibility, this safety is necessary.
113+
// TODO: Remove this check when multi-organizations is fully supported.
114+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
115+
Message: "Removing members from the default organization is not supported.",
116+
Detail: "Multi-organizations is currently an experiment, and until it is fully supported, the default org should be protected.",
117+
Validations: nil,
118+
})
119+
return
120+
}
121+
109122
if member.UserID == apiKey.UserID {
110123
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{Message: "cannot remove self from an organization"})
111124
return

coderd/members_test.go

+15-28
Original file line numberDiff line numberDiff line change
@@ -30,54 +30,41 @@ func TestAddMember(t *testing.T) {
3030
})
3131
}
3232

33-
func TestListMembers(t *testing.T) {
33+
func TestDeleteMember(t *testing.T) {
3434
t.Parallel()
3535

36-
t.Run("OK", func(t *testing.T) {
36+
t.Run("NotAllowed", func(t *testing.T) {
3737
t.Parallel()
3838
owner := coderdtest.New(t, nil)
3939
first := coderdtest.CreateFirstUser(t, owner)
40+
_, user := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID)
4041

41-
client, user := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID, rbac.ScopedRoleOrgAdmin(first.OrganizationID))
42-
43-
ctx := testutil.Context(t, testutil.WaitShort)
44-
members, err := client.OrganizationMembers(ctx, first.OrganizationID)
45-
require.NoError(t, err)
46-
require.Len(t, members, 2)
47-
require.ElementsMatch(t,
48-
[]uuid.UUID{first.UserID, user.ID},
49-
db2sdk.List(members, onlyIDs))
42+
ctx := testutil.Context(t, testutil.WaitMedium)
43+
// Deleting members from the default org is not allowed.
44+
// If this behavior changes, and we allow deleting members from the default org,
45+
// this test should be updated to check there is no error.
46+
// nolint:gocritic // must be an owner to see the user
47+
err := owner.DeleteOrganizationMember(ctx, first.OrganizationID, user.Username)
48+
require.ErrorContains(t, err, "Multi-organizations is currently an experiment")
5049
})
5150
}
5251

53-
func TestRemoveMember(t *testing.T) {
52+
func TestListMembers(t *testing.T) {
5453
t.Parallel()
5554

5655
t.Run("OK", func(t *testing.T) {
5756
t.Parallel()
5857
owner := coderdtest.New(t, nil)
5958
first := coderdtest.CreateFirstUser(t, owner)
60-
orgAdminClient, orgAdmin := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID, rbac.ScopedRoleOrgAdmin(first.OrganizationID))
61-
_, user := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID)
6259

63-
ctx := testutil.Context(t, testutil.WaitMedium)
64-
// Verify the org of 3 members
65-
members, err := orgAdminClient.OrganizationMembers(ctx, first.OrganizationID)
66-
require.NoError(t, err)
67-
require.Len(t, members, 3)
68-
require.ElementsMatch(t,
69-
[]uuid.UUID{first.UserID, user.ID, orgAdmin.ID},
70-
db2sdk.List(members, onlyIDs))
71-
72-
// Delete a member
73-
err = orgAdminClient.DeleteOrganizationMember(ctx, first.OrganizationID, user.Username)
74-
require.NoError(t, err)
60+
client, user := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID, rbac.ScopedRoleOrgAdmin(first.OrganizationID))
7561

76-
members, err = orgAdminClient.OrganizationMembers(ctx, first.OrganizationID)
62+
ctx := testutil.Context(t, testutil.WaitShort)
63+
members, err := client.OrganizationMembers(ctx, first.OrganizationID)
7764
require.NoError(t, err)
7865
require.Len(t, members, 2)
7966
require.ElementsMatch(t,
80-
[]uuid.UUID{first.UserID, orgAdmin.ID},
67+
[]uuid.UUID{first.UserID, user.ID},
8168
db2sdk.List(members, onlyIDs))
8269
})
8370
}

enterprise/cli/organizationmembers_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,64 @@ import (
1515
"github.com/coder/coder/v2/testutil"
1616
)
1717

18+
func TestRemoveOrganizationMembers(t *testing.T) {
19+
t.Parallel()
20+
21+
t.Run("OK", func(t *testing.T) {
22+
t.Parallel()
23+
dv := coderdtest.DeploymentValues(t)
24+
dv.Experiments = []string{string(codersdk.ExperimentMultiOrganization)}
25+
26+
ownerClient, _ := coderdenttest.New(t, &coderdenttest.Options{
27+
Options: &coderdtest.Options{
28+
DeploymentValues: dv,
29+
},
30+
LicenseOptions: &coderdenttest.LicenseOptions{
31+
Features: license.Features{
32+
codersdk.FeatureMultipleOrganizations: 1,
33+
},
34+
},
35+
})
36+
37+
secondOrganization := coderdenttest.CreateOrganization(t, ownerClient, coderdenttest.CreateOrganizationOptions{})
38+
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, secondOrganization.ID, rbac.ScopedRoleOrgAdmin(secondOrganization.ID))
39+
_, user := coderdtest.CreateAnotherUser(t, ownerClient, secondOrganization.ID)
40+
41+
ctx := testutil.Context(t, testutil.WaitMedium)
42+
43+
inv, root := clitest.New(t, "organization", "members", "remove", "-O", secondOrganization.ID.String(), user.Username)
44+
clitest.SetupConfig(t, orgAdminClient, root)
45+
46+
buf := new(bytes.Buffer)
47+
inv.Stdout = buf
48+
err := inv.WithContext(ctx).Run()
49+
require.NoError(t, err)
50+
51+
members, err := orgAdminClient.OrganizationMembers(ctx, secondOrganization.ID)
52+
require.NoError(t, err)
53+
54+
require.Len(t, members, 2)
55+
})
56+
57+
t.Run("UserNotExists", func(t *testing.T) {
58+
t.Parallel()
59+
60+
ownerClient := coderdtest.New(t, &coderdtest.Options{})
61+
owner := coderdtest.CreateFirstUser(t, ownerClient)
62+
orgAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID))
63+
64+
ctx := testutil.Context(t, testutil.WaitMedium)
65+
66+
inv, root := clitest.New(t, "organization", "members", "remove", "-O", owner.OrganizationID.String(), "random_name")
67+
clitest.SetupConfig(t, orgAdminClient, root)
68+
69+
buf := new(bytes.Buffer)
70+
inv.Stdout = buf
71+
err := inv.WithContext(ctx).Run()
72+
require.ErrorContains(t, err, "must be an existing uuid or username")
73+
})
74+
}
75+
1876
func TestEnterpriseListOrganizationMembers(t *testing.T) {
1977
t.Parallel()
2078

enterprise/members_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,47 @@ import (
1818
func TestEnterpriseMembers(t *testing.T) {
1919
t.Parallel()
2020

21+
t.Run("Remove", func(t *testing.T) {
22+
t.Parallel()
23+
dv := coderdtest.DeploymentValues(t)
24+
dv.Experiments = []string{string(codersdk.ExperimentMultiOrganization)}
25+
owner, first := coderdenttest.New(t, &coderdenttest.Options{
26+
Options: &coderdtest.Options{
27+
DeploymentValues: dv,
28+
},
29+
LicenseOptions: &coderdenttest.LicenseOptions{
30+
Features: license.Features{
31+
codersdk.FeatureMultipleOrganizations: 1,
32+
},
33+
},
34+
})
35+
36+
secondOrg := coderdenttest.CreateOrganization(t, owner, coderdenttest.CreateOrganizationOptions{})
37+
38+
orgAdminClient, orgAdmin := coderdtest.CreateAnotherUser(t, owner, secondOrg.ID, rbac.ScopedRoleOrgAdmin(secondOrg.ID))
39+
_, user := coderdtest.CreateAnotherUser(t, owner, secondOrg.ID)
40+
41+
ctx := testutil.Context(t, testutil.WaitMedium)
42+
// Verify the org of 3 members
43+
members, err := orgAdminClient.OrganizationMembers(ctx, secondOrg.ID)
44+
require.NoError(t, err)
45+
require.Len(t, members, 3)
46+
require.ElementsMatch(t,
47+
[]uuid.UUID{first.UserID, user.ID, orgAdmin.ID},
48+
db2sdk.List(members, onlyIDs))
49+
50+
// Delete a member
51+
err = orgAdminClient.DeleteOrganizationMember(ctx, secondOrg.ID, user.Username)
52+
require.NoError(t, err)
53+
54+
members, err = orgAdminClient.OrganizationMembers(ctx, secondOrg.ID)
55+
require.NoError(t, err)
56+
require.Len(t, members, 2)
57+
require.ElementsMatch(t,
58+
[]uuid.UUID{first.UserID, orgAdmin.ID},
59+
db2sdk.List(members, onlyIDs))
60+
})
61+
2162
t.Run("PostUser", func(t *testing.T) {
2263
t.Parallel()
2364

0 commit comments

Comments
 (0)