Skip to content

Commit fb9fe63

Browse files
fix(coderd): prevent user-admin from resetting owner password (#25709) (#26183)
Backport of #25709 to `release/2.33`. 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 335df24 commit fb9fe63

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
@@ -1385,6 +1385,24 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
13851385
return
13861386
}
13871387

1388+
// Only owners can change the password of another owner.
1389+
if apiKey.UserID != user.ID && slices.Contains(user.RBACRoles, rbac.RoleOwner().String()) {
1390+
actingUser, err := api.Database.GetUserByID(ctx, apiKey.UserID)
1391+
if err != nil {
1392+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
1393+
Message: "Internal error fetching acting user.",
1394+
Detail: err.Error(),
1395+
})
1396+
return
1397+
}
1398+
if !slices.Contains(actingUser.RBACRoles, rbac.RoleOwner().String()) {
1399+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
1400+
Message: "Only owners can change the password of an owner.",
1401+
})
1402+
return
1403+
}
1404+
}
1405+
13881406
if !httpapi.Read(ctx, rw, r, &params) {
13891407
return
13901408
}

coderd/users_test.go

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

1521+
t.Run("UserAdminCannotResetOwnerPassword", func(t *testing.T) {
1522+
t.Parallel()
1523+
client := coderdtest.New(t, nil)
1524+
owner := coderdtest.CreateFirstUser(t, client)
1525+
userAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleUserAdmin())
1526+
1527+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1528+
defer cancel()
1529+
1530+
err := userAdmin.UpdateUserPassword(ctx, owner.UserID.String(), codersdk.UpdateUserPasswordRequest{
1531+
Password: "SomeNewStrongPassword!",
1532+
})
1533+
require.Error(t, err, "user-admin should not be able to reset owner password")
1534+
var apiErr *codersdk.Error
1535+
require.ErrorAs(t, err, &apiErr)
1536+
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
1537+
require.Contains(t, apiErr.Message, "Only owners can change the password of an owner")
1538+
})
1539+
1540+
t.Run("OwnerCanResetOwnerPassword", func(t *testing.T) {
1541+
t.Parallel()
1542+
client := coderdtest.New(t, nil)
1543+
owner := coderdtest.CreateFirstUser(t, client)
1544+
1545+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1546+
defer cancel()
1547+
1548+
anotherOwner, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{
1549+
Email: "another-owner@coder.com",
1550+
Username: "another-owner",
1551+
Password: "SomeStrongPassword!",
1552+
OrganizationIDs: []uuid.UUID{owner.OrganizationID},
1553+
})
1554+
require.NoError(t, err)
1555+
_, err = client.UpdateUserRoles(ctx, anotherOwner.ID.String(), codersdk.UpdateRoles{
1556+
Roles: []string{rbac.RoleOwner().String()},
1557+
})
1558+
require.NoError(t, err)
1559+
1560+
err = client.UpdateUserPassword(ctx, anotherOwner.ID.String(), codersdk.UpdateUserPasswordRequest{
1561+
Password: "SomeNewStrongPassword!",
1562+
})
1563+
require.NoError(t, err, "owner should be able to reset another owner's password")
1564+
1565+
_, err = client.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{
1566+
Email: "another-owner@coder.com",
1567+
Password: "SomeNewStrongPassword!",
1568+
})
1569+
require.NoError(t, err, "other owner should login with the new password")
1570+
})
1571+
15211572
t.Run("PasswordsMustDiffer", func(t *testing.T) {
15221573
t.Parallel()
15231574

0 commit comments

Comments
 (0)