Skip to content

Commit 833eaf8

Browse files
fix(coderd): prevent user-admin from resetting owner password (#25709) (#26191)
Backport of #25709 to `release/2.29`. Cherry-picked with `git cherry-pick -x` (`76bf462bbf`); the commit body references the original PR. _Generated by Coder Agents on behalf of @jdomeracki-coder._ Co-authored-by: Garrett Delfosse <garrett@coder.com>
1 parent ed7e924 commit 833eaf8

2 files changed

Lines changed: 69 additions & 0 deletions

File tree

coderd/users.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,24 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
11801180
return
11811181
}
11821182

1183+
// Only owners can change the password of another owner.
1184+
if apiKey.UserID != user.ID && slices.Contains(user.RBACRoles, rbac.RoleOwner().String()) {
1185+
actingUser, err := api.Database.GetUserByID(ctx, apiKey.UserID)
1186+
if err != nil {
1187+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
1188+
Message: "Internal error fetching acting user.",
1189+
Detail: err.Error(),
1190+
})
1191+
return
1192+
}
1193+
if !slices.Contains(actingUser.RBACRoles, rbac.RoleOwner().String()) {
1194+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
1195+
Message: "Only owners can change the password of an owner.",
1196+
})
1197+
return
1198+
}
1199+
}
1200+
11831201
if !httpapi.Read(ctx, rw, r, &params) {
11841202
return
11851203
}

coderd/users_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,57 @@ func TestUpdateUserPassword(t *testing.T) {
14141414
require.Equal(t, http.StatusNotFound, cerr.StatusCode())
14151415
})
14161416

1417+
t.Run("UserAdminCannotResetOwnerPassword", func(t *testing.T) {
1418+
t.Parallel()
1419+
client := coderdtest.New(t, nil)
1420+
owner := coderdtest.CreateFirstUser(t, client)
1421+
userAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleUserAdmin())
1422+
1423+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1424+
defer cancel()
1425+
1426+
err := userAdmin.UpdateUserPassword(ctx, owner.UserID.String(), codersdk.UpdateUserPasswordRequest{
1427+
Password: "SomeNewStrongPassword!",
1428+
})
1429+
require.Error(t, err, "user-admin should not be able to reset owner password")
1430+
var apiErr *codersdk.Error
1431+
require.ErrorAs(t, err, &apiErr)
1432+
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
1433+
require.Contains(t, apiErr.Message, "Only owners can change the password of an owner")
1434+
})
1435+
1436+
t.Run("OwnerCanResetOwnerPassword", func(t *testing.T) {
1437+
t.Parallel()
1438+
client := coderdtest.New(t, nil)
1439+
owner := coderdtest.CreateFirstUser(t, client)
1440+
1441+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1442+
defer cancel()
1443+
1444+
anotherOwner, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
1445+
Email: "another-owner@coder.com",
1446+
Username: "another-owner",
1447+
Password: "SomeStrongPassword!",
1448+
OrganizationIDs: []uuid.UUID{owner.OrganizationID},
1449+
})
1450+
require.NoError(t, err)
1451+
_, err = client.UpdateUserRoles(ctx, anotherOwner.ID.String(), codersdk.UpdateRoles{
1452+
Roles: []string{rbac.RoleOwner().String()},
1453+
})
1454+
require.NoError(t, err)
1455+
1456+
err = client.UpdateUserPassword(ctx, anotherOwner.ID.String(), codersdk.UpdateUserPasswordRequest{
1457+
Password: "SomeNewStrongPassword!",
1458+
})
1459+
require.NoError(t, err, "owner should be able to reset another owner's password")
1460+
1461+
_, err = client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
1462+
Email: "another-owner@coder.com",
1463+
Password: "SomeNewStrongPassword!",
1464+
})
1465+
require.NoError(t, err, "other owner should login with the new password")
1466+
})
1467+
14171468
t.Run("PasswordsMustDiffer", func(t *testing.T) {
14181469
t.Parallel()
14191470

0 commit comments

Comments
 (0)