Skip to content

Commit 931d4fa

Browse files
fix(coderd): prevent user-admin from resetting owner password (#25709) (#26188)
Backport of #25709 to `release/2.32`. 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 0fbee8f commit 931d4fa

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
@@ -1316,6 +1316,24 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
13161316
return
13171317
}
13181318

1319+
// Only owners can change the password of another owner.
1320+
if apiKey.UserID != user.ID && slices.Contains(user.RBACRoles, rbac.RoleOwner().String()) {
1321+
actingUser, err := api.Database.GetUserByID(ctx, apiKey.UserID)
1322+
if err != nil {
1323+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
1324+
Message: "Internal error fetching acting user.",
1325+
Detail: err.Error(),
1326+
})
1327+
return
1328+
}
1329+
if !slices.Contains(actingUser.RBACRoles, rbac.RoleOwner().String()) {
1330+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
1331+
Message: "Only owners can change the password of an owner.",
1332+
})
1333+
return
1334+
}
1335+
}
1336+
13191337
if !httpapi.Read(ctx, rw, r, &params) {
13201338
return
13211339
}

coderd/users_test.go

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

1575+
t.Run("UserAdminCannotResetOwnerPassword", func(t *testing.T) {
1576+
t.Parallel()
1577+
client := coderdtest.New(t, nil)
1578+
owner := coderdtest.CreateFirstUser(t, client)
1579+
userAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleUserAdmin())
1580+
1581+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1582+
defer cancel()
1583+
1584+
err := userAdmin.UpdateUserPassword(ctx, owner.UserID.String(), codersdk.UpdateUserPasswordRequest{
1585+
Password: "SomeNewStrongPassword!",
1586+
})
1587+
require.Error(t, err, "user-admin should not be able to reset owner password")
1588+
var apiErr *codersdk.Error
1589+
require.ErrorAs(t, err, &apiErr)
1590+
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
1591+
require.Contains(t, apiErr.Message, "Only owners can change the password of an owner")
1592+
})
1593+
1594+
t.Run("OwnerCanResetOwnerPassword", func(t *testing.T) {
1595+
t.Parallel()
1596+
client := coderdtest.New(t, nil)
1597+
owner := coderdtest.CreateFirstUser(t, client)
1598+
1599+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1600+
defer cancel()
1601+
1602+
anotherOwner, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
1603+
Email: "another-owner@coder.com",
1604+
Username: "another-owner",
1605+
Password: "SomeStrongPassword!",
1606+
OrganizationIDs: []uuid.UUID{owner.OrganizationID},
1607+
})
1608+
require.NoError(t, err)
1609+
_, err = client.UpdateUserRoles(ctx, anotherOwner.ID.String(), codersdk.UpdateRoles{
1610+
Roles: []string{rbac.RoleOwner().String()},
1611+
})
1612+
require.NoError(t, err)
1613+
1614+
err = client.UpdateUserPassword(ctx, anotherOwner.ID.String(), codersdk.UpdateUserPasswordRequest{
1615+
Password: "SomeNewStrongPassword!",
1616+
})
1617+
require.NoError(t, err, "owner should be able to reset another owner's password")
1618+
1619+
_, err = client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
1620+
Email: "another-owner@coder.com",
1621+
Password: "SomeNewStrongPassword!",
1622+
})
1623+
require.NoError(t, err, "other owner should login with the new password")
1624+
})
1625+
15751626
t.Run("PasswordsMustDiffer", func(t *testing.T) {
15761627
t.Parallel()
15771628

0 commit comments

Comments
 (0)