Repository navigation
Bugfix Issue 1968 RTM Private TestPlan Rights Admin Exemption
Bugfix — Issue #1968: RTM ignored testplans.is_public, and the plan-scope guard locked the admin out
Issue: #1968 — RTM BFF ignores
testplans.is_public, letting project-level metrics users read private plans
Severity: major — authorization bypass on private test plans (closed), plus an admin lockout
introduced by the first half of the fix
Branch: fix/issue-1968 · Files changed: lib/functions/tlUser.class.php
(the RTM call-site change in api/rtm/index.php was already on the default branch in feebbe684)
api/rtm/index.php authorised with hasRight($db,'testplan_metrics',$tproject_id,$tplan_id)
(4 arguments). Only the 5th argument ($getAccess=true) makes tlUser::hasRight() load
testplans.is_public; without it a user holding testplan_metrics through a project-level role
could read the Requirements Traceability Matrix of a private plan in that project.
The fix commit feebbe684 already did two things at the call site:
- pass
trueas the 5th argument, and - move the
tproject_idmismatch assertion after the rights check, so an unauthorised caller cannot tell "cross-project plan" from "missing right".
Measured after that commit: the project-level-only user was denied (leak closed), but admin was
also denied 403 on the private plan — the issue's own acceptance criterion ("admin still 200")
failed. The same lockout existed on every metrics BFF that passes the 5th argument.
Fresh DB; fixture created by SQL:
| entity | value |
|---|---|
project 1 PrivProj
|
testprojects.is_public = 1 |
plan 2 Private Plan
|
testplans.is_public = 0 |
plan 3 Public Plan
|
testplans.is_public = 1 |
metricsuser (id 2) |
global role 5 guest; project role 6 senior tester only; no user_testplan_roles row |
admin (id 1) |
role 8; no project/plan role row |
curl -s -c cj_user -H 'Origin: http://localhost:8082' -H 'Content-Type: application/json' \
-d '{"login":"metricsuser","password":"metrics123"}' http://localhost:8082/api/auth/login
curl -s -c cj_admin -H 'Origin: http://localhost:8082' -H 'Content-Type: application/json' \
-d '{"login":"admin","password":"admin"}' http://localhost:8082/api/auth/login
curl -s -b cj_user 'http://localhost:8082/api/rtm/index.php?action=context&tplan_id=2'
curl -s -b cj_admin 'http://localhost:8082/api/rtm/index.php?action=context&tplan_id=2'Measured on feebbe684 (before this fix):
| caller | private plan 2 | public plan 3 |
|---|---|---|
metricsuser |
403 no_right (leak closed) |
200 |
admin |
403 no_right (regression) |
200 |
api/exechistoryreport/index.php?tplan_id=2 showed the same admin 403, proving the defect was in the
shared primitive, not in the RTM endpoint.
api/rtm/index.php:112-124 hasRight(..., $tplan_id, true) before the tproject assertion (correct)
lib/functions/tlUser.class.php:892-897 $getAccess => $accessPublic['tplan'] = testplan::getPublicAttr()
lib/functions/testplan.class.php:7377 private plan => 0
lib/functions/tlUser.class.php:966-996 no user_testplan_roles row =>
if (isset($accessPublic['tplan']) && $accessPublic['tplan'] == 0)
return false; <-- NO admin exemption
lib/functions/tlUser.class.php:938-945 sibling TEST PROJECT branch: ... && globalRoleID != TL_ROLES_ADMIN
lib/functions/roles.inc.php:407-416 legacy private-plan rule: ... && globalRoleID != TL_ROLES_ADMIN
lib/functions/tlUser.class.php:1143-1157 getAccessibleTestPlans(): same admin exemption
admin owns no user_testplan_roles row (it does not need one), so the plan-scope denial is
unconditional for it. Legacy explicitly exempted TL_ROLES_ADMIN; the primitive forgot that
exemption when the 5-argument form became reachable.
One condition in the primitive:
if(isset($accessPublic['tplan']) && $accessPublic['tplan'] == 0
&& $this->globalRoleID != TL_ROLES_ADMIN) {
return false;
}- Keeps the #1968 leak closed: a non-admin project-level role is still refused on a private plan.
- Restores legacy admin parity and the already-shipped project-branch behaviour.
- Fixes every 5-argument caller in one place: 18
api/*metrics call sites, of which 17 pass a plan id (strategyreport passesnull), plus the plan-scoped callers such aspageAccessCheck()andexec_delete.
Alternatives rejected: patching only api/rtm (leaves the other metrics BFFs broken for admin);
duplicating the rule per endpoint (unverifiable, more surface).
php -l lib/functions/tlUser.class.php → clean.
| request | before | after |
|---|---|---|
metricsuser → RTM private plan 2 |
403 | 403 no_right |
admin → RTM private plan 2 |
403 | 200 context |
metricsuser → RTM public plan 3 |
200 | 200 |
admin → RTM public plan 3 |
200 | 200 |
admin → api/exechistoryreport?tplan_id=2
|
403 | 200 |
metricsuser → api/exechistoryreport?tplan_id=2
|
403 | 403 |
admin → RTM private plan with tproject_id=999
|
— |
404 project_mismatch (rights first) |
metricsuser → same mismatch |
— |
403 no_right (no existence oracle) |
| 3-argument callers | — | unchanged; no Undefined array key "tplan" warnings |
Event Viewer: test-probe ERROR rows were deleted; events ends with the 2 audit-login INFO rows,
0 new ERROR/WARNING attributable to the change.
Regression suite: ## Regression — Issue #1968 in tmp/TLU_Test_Cases.md, parts A–G PASS.
Suite-file gate: TLU_REQUIRE_SUITE="Issue #1968" bash ai/verify_test_suites.sh → 34 PASS / 0 FAIL.
-
lib/functions/tlUser.class.php— admin exemption in the private-plan branch (the fix). -
api/rtm/index.php— already fixed on the default branch infeebbe684; unchanged here. -
CHANGELOG— 2.0.1 bugfix entry. -
tmp/TLU_Test_Cases.md— regression suite (git-ignored, appended, gate passed). -
docs/Bugfix-Issue-1968-RTM-Private-TestPlan-Rights-Admin-Exemption.md+ wiki mirror.