Skip to content

Commit f15a934

Browse files
fix(coderd): prevent user-admin from resetting owner password (#25709) (#26178)
Backport of #25709 to `release/2.34`. 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 9595e6c commit f15a934

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
@@ -1596,6 +1596,24 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
15961596
return
15971597
}
15981598

1599+
// Only owners can change the password of another owner.
1600+
if apiKey.UserID != user.ID && slices.Contains(user.RBACRoles, rbac.RoleOwner().String()) {
1601+
actingUser, err := api.Database.GetUserByID(ctx, apiKey.UserID)
1602+
if err != nil {
1603+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
1604+
Message: "Internal error fetching acting user.",
1605+
Detail: err.Error(),
1606+
})
1607+
return
1608+
}
1609+
if !slices.Contains(actingUser.RBACRoles, rbac.RoleOwner().String()) {
1610+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
1611+
Message: "Only owners can change the password of an owner.",
1612+
})
1613+
return
1614+
}
1615+
}
1616+
15991617
if !httpapi.Read(ctx, rw, r, &params) {
16001618
return
16011619
}

coderd/users_test.go

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

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

0 commit comments

Comments
 (0)