Skip to content

Commit aa9a1c3

Browse files
authored
fix: Prevent suspending owners (coder#3757)
1 parent e6802f0 commit aa9a1c3

File tree

2 files changed

+34
-14
lines changed

2 files changed

+34
-14
lines changed

coderd/users.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ import (
2121
"golang.org/x/xerrors"
2222

2323
"cdr.dev/slog"
24-
2524
"github.com/coder/coder/coderd/database"
2625
"github.com/coder/coder/coderd/gitsshkey"
2726
"github.com/coder/coder/coderd/httpapi"
2827
"github.com/coder/coder/coderd/httpmw"
2928
"github.com/coder/coder/coderd/rbac"
3029
"github.com/coder/coder/coderd/telemetry"
3130
"github.com/coder/coder/coderd/userpassword"
31+
"github.com/coder/coder/coderd/util/slice"
3232
"github.com/coder/coder/codersdk"
3333
"github.com/coder/coder/cryptorand"
3434
"github.com/coder/coder/examples"
@@ -425,11 +425,24 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW
425425
return
426426
}
427427

428-
if status == database.UserStatusSuspended && user.ID == apiKey.UserID {
429-
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
430-
Message: "You cannot suspend yourself.",
431-
})
432-
return
428+
if status == database.UserStatusSuspended {
429+
// There are some manual protections when suspending a user to
430+
// prevent certain situations.
431+
switch {
432+
case user.ID == apiKey.UserID:
433+
// Suspending yourself is not allowed, as you can lock yourself
434+
// out of the system.
435+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
436+
Message: "You cannot suspend yourself.",
437+
})
438+
return
439+
case slice.Contains(user.RBACRoles, rbac.RoleOwner()):
440+
// You may not suspend an owner
441+
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
442+
Message: fmt.Sprintf("You cannot suspend a user with the %q role. You must remove the role first.", rbac.RoleOwner()),
443+
})
444+
return
445+
}
433446
}
434447

435448
suspendedUser, err := api.Database.UpdateUserStatus(r.Context(), database.UpdateUserStatusParams{

coderd/users_test.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -737,21 +737,28 @@ func TestInitialRoles(t *testing.T) {
737737
func TestPutUserSuspend(t *testing.T) {
738738
t.Parallel()
739739

740+
t.Run("SuspendAnOwner", func(t *testing.T) {
741+
t.Parallel()
742+
client := coderdtest.New(t, nil)
743+
me := coderdtest.CreateFirstUser(t, client)
744+
_, user := coderdtest.CreateAnotherUserWithUser(t, client, me.OrganizationID, rbac.RoleOwner())
745+
746+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
747+
defer cancel()
748+
749+
_, err := client.UpdateUserStatus(ctx, user.Username, codersdk.UserStatusSuspended)
750+
require.Error(t, err, "cannot suspend owners")
751+
})
752+
740753
t.Run("SuspendAnotherUser", func(t *testing.T) {
741754
t.Parallel()
742755
client := coderdtest.New(t, nil)
743756
me := coderdtest.CreateFirstUser(t, client)
757+
_, user := coderdtest.CreateAnotherUserWithUser(t, client, me.OrganizationID)
744758

745759
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
746760
defer cancel()
747761

748-
client.User(ctx, codersdk.Me)
749-
user, _ := client.CreateUser(ctx, codersdk.CreateUserRequest{
750-
Email: "bruno@coder.com",
751-
Username: "bruno",
752-
Password: "password",
753-
OrganizationID: me.OrganizationID,
754-
})
755762
user, err := client.UpdateUserStatus(ctx, user.Username, codersdk.UserStatusSuspended)
756763
require.NoError(t, err)
757764
require.Equal(t, user.Status, codersdk.UserStatusSuspended)
@@ -841,7 +848,7 @@ func TestUsersFilter(t *testing.T) {
841848
for i := 0; i < 15; i++ {
842849
roles := []string{}
843850
if i%2 == 0 {
844-
roles = append(roles, rbac.RoleOwner())
851+
roles = append(roles, rbac.RoleTemplateAdmin(), rbac.RoleUserAdmin())
845852
}
846853
if i%3 == 0 {
847854
roles = append(roles, "auditor")

0 commit comments

Comments
 (0)