Repository navigation
Task Issue 970 codetracker management Write Gating
Task 970 — Implement codetracker_management write + UI-action gating in the Code Trackers screen (gap vs legacy)
Issue: #970
Status: IMPLEMENTED & VERIFIED (2026-09-25) — branch task/issue-970-codetracker-management-gate
Legacy delimited read from write capability on Code Trackers:
-
lib/codetrackers/codeTrackerView.php:24→$gui->canManage = hasRight('codetracker_management'). -
gui/templates/dashio/codetrackers/codeTrackerView.tpl: the Create button (:91-96), the wrench "check connection" link + connection icons (:51-61), the edit-link around the tracker name (:64-70) and the delete icon (:76, additionally only whenlink_count == 0) are ALL rendered only{if $gui->canManage != ""}. - The write controller
lib/codetrackers/codeTrackerEdit.php:181-184is reachable only viacheckRights()=codetracker_management.
The modern BFF api/codetracker/index.php (after the #969 read gate) allowed any viewer to write: POST / (create), PUT /{id} (update), DELETE /{id} and POST /test_github all executed for a user holding only codetracker_view — a privilege escalation.
Fixture: ctviewonly (role 99, only codetracker_view right id 52, password admin).
-
POST /→ 200, tracker INSERTed;PUT /1rename → 200;DELETE /1→ 200;POST /test_github→ 200. - GET list →
{status:ok, items:[], total:0}—canManagekey ABSENT. - UI:
+ Create Code Trackerbutton rendered for the view-only user.
api/codetracker/index.php:
$canManage = ($user->hasRight($db, 'codetracker_management') == 'yes');
function denyWrite($user, $userId, $action) {
logAuditEvent(TLS('audit_security_user_right_missing', $user->login,
'api/codetracker/index.php', $action),
'WRITE', $userId, 'codetrackers'); // trailed BEFORE the denial
http_response_code(403);
echo json_encode(['status' => 'error', 'message' => 'No permission']);
exit;
}Every write route starts with if (!$canManage) { denyWrite($user, $userId, '<op>'); }:
-
POST /(create) →create;PUT /{id}→update;DELETE /{id}→delete;POST /test_github→test_github.
GET / now returns "canManage" (mirrors legacy $gui->canManage) and each item gains "link_count" so the UI can mirror the legacy link_count == 0 delete condition.
Note: hasRight() returns 'yes'/null (roles.inc.php:254-274), so == 'yes' normalization is required for clean JSON true/false.
gui/templates/codetracker/codetrackerView.html:
-
canManage = !!r.canManagefrom the GET payload. -
$('#createBtn').toggle(canManage)— Create button management-only. - Edit icon only when
canManage; Delete icon only whencanManage && link_count === 0. -
table.column(4).visible(canManage)collapses the Actions column for view-only users (mirrors legacy<th>gating).
No new i18n keys required — all gated labels reuse existing keys; no bundle changes.


Call (as ctviewonly) |
Result |
|---|---|
POST / create |
403 No permission
|
PUT /2 update |
403 |
DELETE /2 |
403 |
POST /test_github |
403 |
GET / list |
200 canManage:false
|
Admin: GET / → canManage:true; modal create → 200 (id 2); rename PUT → 200; linked fixture (link_count=1) hides delete icon; delete flow (id 3) → 200.
Event Viewer: log_level IN (1,2) → 0 rows; 13 events all AUDIT (16). Test suite Suite 970 — Task — Issue #970 in tl/TLU_Test_Cases.md: 10/10 PASS.