Task Issue 960 issuetracker management Write Gating - sebiboga/testlink-upgraded GitHub Wiki
Task 960 — Issue Tracker write gating: issuetracker_management (create/edit/delete)
Issue: #960
Status: IMPLEMENTED & VERIFIED (2026-09-23) — branch task/issue-960
Legacy 1.9.20 clearly separates read from manage on Issue Trackers:
lib/issuetrackers/issueTrackerView.php:24 set $gui->canManage = hasRight('issuetracker_management') and the whole write UI
(issueTrackerView.tpl) — Create button (:95-100), the delete column + icon
(:45,78-84), the wrench/connection icons and the name link (:54-73) — is
rendered only for managers. A view-only user sees a bare table with
plain-text names. The write controller issueTrackerEdit.php also requires
issuetracker_management.
The modern screen exposed two privilege-escalation paths:
- API —
POST /,PUT /{id},DELETE /{id}andPOST /oauth/createaccepted requests from any user that passed the #959 read gate. Measured:itview(view-only) created + deleted a tracker (both HTTP 200). - UI — the toolbar (
+ Create Issue Tracker,GitHub) and the per-row Edit/Delete icons were rendered foritviewexactly as for admin.
Server-side fix (api/issuetracker/index.php)
A $canManage boolean (cast from hasRight(issuetracker_management) — the
method returns null, not false, for an absent right) plus a $writeDenied()
closure that trails audit_security_user_right_missing (activity EDIT,
object issuetrackers) into the Event Viewer before answering HTTP 403.
Each write route begins with if (!$canManage) { $writeDenied(); }:
| Route | Purpose | New guard |
|---|---|---|
POST / |
create tracker | 403 for non-manager |
PUT /{id} |
update tracker | 403 for non-manager |
DELETE /{id} |
delete tracker | 403 for non-manager |
POST /oauth/create |
GitHub tracker create + project link | 403 for non-manager |
GET / additionally returns "canManage" so the front-end can hide the write UI.
Client-side fix (gui/templates/issuetracker/issuetrackerView.html)
Same pattern as reqMgrSystemView.html / platformsView.html:
canManage = !!r.canManagecaptured from the BFF payload.#btnCreateand#btnGithubtoggled via$('#btnCreate').toggle(canManage).- Edit/Delete icons emitted only when managing; the
Actionscolumn (header + cell) is dropped for view-only users. - View-only users also get a bare plain-text table, exactly like legacy 1.9.20.
- Defense-in-depth guards inside
showCreateModal,editTracker,deleteTracker,githubOAuthStart,createGithubTrackerandcheckOAuthConnected.
No new i18n keys — every label reuses existing keys present in all 10 locale bundles.
Verification

Admin (manager): full UI, canManage:true, create/delete work (HTTP 200).

itview (view-only): the same tracker row renders as plain text — no
Actions column, no Edit/Delete icons, no Create/GitHub buttons.

itview blocked: POST/PUT/DELETE/oauth/create → HTTP 403
{"status":"error","message":"No permission"}.
- 4 AUDIT events logged for the denied writes (
activity=EDIT,object_type=issuetrackers). - Event Viewer clean:
log_level IN (1,2)→ 0 rows. - Test suite:
tmp/TLU_Test_Cases.md→ "Task — Issue #960" (all PASS).