fix: prevent user permission escalation

This commit is contained in:
loveuer
2026-09-09 21:00:41 -07:00
parent d3016b94db
commit fa4357a3f2
4 changed files with 103 additions and 8 deletions
+49
View File
@@ -305,6 +305,55 @@ func TestDeleteUserRemovesOwnedFilesAndProtectsIDOne(t *testing.T) {
}
}
func TestUserManagerCannotGrantOrTakeOverPermissionsTheyDoNotHave(t *testing.T) {
app, _ := testApp(t)
passwordHash, _ := bcrypt.GenerateFromPassword([]byte("password-123"), bcrypt.DefaultCost)
manager := User{Username: "manager", PasswordHash: string(passwordHash), CanManageUsers: true, CanUpload: true}
higherUser := User{Username: "higher", PasswordHash: string(passwordHash), CanAdvancedUpload: true}
basicUser := User{Username: "editable", PasswordHash: string(passwordHash), CanUpload: true}
for _, user := range []*User{&manager, &higherUser, &basicUser} {
if err := app.db.Create(user).Error; err != nil {
t.Fatal(err)
}
}
router := app.router()
cookie := loginAs(t, router, manager.Username, "password-123")
createBody, _ := json.Marshal(map[string]any{"username": "escalated", "canAdvancedUpload": true})
created := request(t, router, http.MethodPost, "/api/users", bytes.NewReader(createBody), "application/json", cookie)
if created.Code != http.StatusForbidden {
t.Fatalf("create advanced user status = %d, body = %s", created.Code, created.Body.String())
}
updateBody, _ := json.Marshal(map[string]any{"username": basicUser.Username, "canUpload": true, "canAdvancedUpload": true})
updated := request(t, router, http.MethodPut, "/api/users/"+jsonNumber(basicUser.ID), bytes.NewReader(updateBody), "application/json", cookie)
if updated.Code != http.StatusForbidden {
t.Fatalf("grant advanced permission status = %d, body = %s", updated.Code, updated.Body.String())
}
reset := request(t, router, http.MethodPost, "/api/users/"+jsonNumber(higherUser.ID)+"/reset-password", nil, "", cookie)
if reset.Code != http.StatusForbidden {
t.Fatalf("reset higher user status = %d, body = %s", reset.Code, reset.Body.String())
}
downgradeBody, _ := json.Marshal(map[string]any{"username": higherUser.Username, "canUpload": true})
downgraded := request(t, router, http.MethodPut, "/api/users/"+jsonNumber(higherUser.ID), bytes.NewReader(downgradeBody), "application/json", cookie)
if downgraded.Code != http.StatusForbidden {
t.Fatalf("edit higher user status = %d, body = %s", downgraded.Code, downgraded.Body.String())
}
deleted := request(t, router, http.MethodDelete, "/api/users/"+jsonNumber(higherUser.ID), nil, "", cookie)
if deleted.Code != http.StatusForbidden {
t.Fatalf("delete higher user status = %d, body = %s", deleted.Code, deleted.Body.String())
}
createBody, _ = json.Marshal(map[string]any{"username": "allowed", "canUpload": true})
created = request(t, router, http.MethodPost, "/api/users", bytes.NewReader(createBody), "application/json", cookie)
if created.Code != http.StatusCreated {
t.Fatalf("create user with owned permission status = %d, body = %s", created.Code, created.Body.String())
}
}
func jsonNumber(id uint) string {
data, _ := json.Marshal(id)
return string(data)