Add access control for department goal operations in departmentGoalsRoute

- Restrict goal listing and retrieval based on user permissions and department access.
- Enforce department-level access control for goal creation, updates, and deletions.
- Validate user actions against `superuser` status and department memberships.
This commit is contained in:
Jeppe Bundgaard
2026-01-26 17:22:17 +01:00
parent 4a0e86164c
commit b4b72d7537
@@ -20,6 +20,21 @@ class departmentGoalsRoute
global /** @var response $response */ $response;
$this->requirePermission('goals_department_list');
// Current user and access helpers
$user = (new authentication())->get_user();
if (!$user) {
$response->error('Unauthorized', 401);
}
$isSuperuser = method_exists($user, 'hasPermission') && $user->hasPermission('superuser');
$userDepartments = method_exists($user, 'getGroup') ? (array)$user->getGroup()->getDepartments() : [];
$hasAllDepartments = function (array $goalDepartments) use ($userDepartments): bool {
// Ensure ints
$goalDepartments = array_map('intval', $goalDepartments);
$userDepartments = array_map('intval', $userDepartments);
return count(array_diff($goalDepartments, $userDepartments)) === 0;
};
$goals = new department_goals_o();
// Single by id
@@ -32,17 +47,27 @@ class departmentGoalsRoute
if (!$goal->exists()) {
$response->error('Goal not found', 404);
}
// Access control: must have all departments unless superuser
if (!$isSuperuser && !$hasAllDepartments((array)$goal->departments->value())) {
$response->error('You are not allowed to access this goal', 403);
}
$response->success($goal->asArray());
}
// List with pagination if set (page, limit, search, filters, order supported by db_object_t)
$response->success(
$goals->listObjectsWithPaginationIfSet(
function ($row) {
return (new department_goals_o())->select((int)$row['id'])->asArray();
}
)
$list = $goals->listObjectsWithPaginationIfSet(
function ($row) {
return (new department_goals_o())->select((int)$row['id'])->asArray();
}
);
// Filter unauthorized results unless superuser
if (!$isSuperuser && is_array($list)) {
$list = array_values(array_filter($list, function ($item) use ($hasAllDepartments) {
$deps = isset($item['departments']) && is_array($item['departments']) ? $item['departments'] : [];
return $hasAllDepartments($deps);
}));
}
$response->success($list);
}, [
'goals_department_list' => 'List department goals or retrieve a single goal when id is provided'
]);
@@ -57,6 +82,8 @@ class departmentGoalsRoute
if (!$user) {
$response->error('Unauthorized', 401);
}
$isSuperuser = method_exists($user, 'hasPermission') && $user->hasPermission('superuser');
$userDepartments = method_exists($user, 'getGroup') ? (array)$user->getGroup()->getDepartments() : [];
// Validate input
$this->requireParameters(['departments', 'criteria']);
@@ -78,6 +105,15 @@ class departmentGoalsRoute
$departments[] = $dept;
}
// Access control: creator must have all departments unless superuser
if (!$isSuperuser) {
$goalDepartments = array_map(fn($d) => (int)$d->id, $departments);
$missing = array_diff($goalDepartments, array_map('intval', $userDepartments));
if (count($missing) > 0) {
$response->error('You are not allowed to create goals for one or more selected departments', 403);
}
}
// Parse criteria
$criteria = goals_criteria::fromJson(json_encode($criteriaInput));
@@ -104,6 +140,25 @@ class departmentGoalsRoute
$response->error('Goal not found', 404);
}
// Current user and access helpers
$user = (new authentication())->get_user();
if (!$user) {
$response->error('Unauthorized', 401);
}
$isSuperuser = method_exists($user, 'hasPermission') && $user->hasPermission('superuser');
$isCreator = ((int)$goal->created_by->value()) === (int)$user->id;
$userDepartments = method_exists($user, 'getGroup') ? (array)$user->getGroup()->getDepartments() : [];
$hasAllDepartments = function (array $goalDepartments, array $userDepartments) {
$goalDepartments = array_map('intval', $goalDepartments);
$userDepartments = array_map('intval', $userDepartments);
return count(array_diff($goalDepartments, $userDepartments)) === 0;
};
// If not superuser or creator, require access to existing goal departments
if (!$isSuperuser && !$isCreator && !$hasAllDepartments((array)$goal->departments->value(), $userDepartments)) {
$response->error('You are not allowed to update this goal', 403);
}
$dataToUpdate = [];
// Optional: departments
@@ -115,6 +170,13 @@ class departmentGoalsRoute
$response->error('All department ids must be numeric', 400);
}
}
// If not superuser or creator, ensure new departments are within user departments
if (!$isSuperuser && !$isCreator) {
$missing = array_diff(array_map('intval', $departmentsInput), array_map('intval', $userDepartments));
if (count($missing) > 0) {
$response->error('You are not allowed to assign one or more selected departments to this goal', 403);
}
}
// store as ids array in column
$dataToUpdate['departments'] = array_map('intval', $departmentsInput);
}
@@ -152,6 +214,21 @@ class departmentGoalsRoute
if (!$goal->exists()) {
$response->error('Goal not found', 404);
}
// Current user and access helpers
$user = (new authentication())->get_user();
if (!$user) {
$response->error('Unauthorized', 401);
}
$isSuperuser = method_exists($user, 'hasPermission') && $user->hasPermission('superuser');
$isCreator = ((int)$goal->created_by->value()) === (int)$user->id;
$userDepartments = method_exists($user, 'getGroup') ? (array)$user->getGroup()->getDepartments() : [];
$goalDepartments = (array)$goal->departments->value();
$hasAll = count(array_diff(array_map('intval', $goalDepartments), array_map('intval', $userDepartments))) === 0;
if (!$isSuperuser && !$isCreator && !$hasAll) {
$response->error('You are not allowed to delete this goal', 403);
}
$goal->delete();
$response->success(['message' => 'Deleted']);
}, [