Task Issue 924 Assign TProject Roles Rights Filtering - sebiboga/testlink-upgraded GitHub Wiki
Task 924 β Assign Test Project Roles: legacy checkRights() + effective-role project filtering (gap vs legacy)
Issue: #924
Status: IMPLEMENTED (2026-09-17) β branch task/issue-924-assign-tproject-rights
api/roles/index.php gated every /api/roles/* route with a single
role_management check (api/roles/index.php:42-50, added by c7439e57c for
#897). The legacy Assign Test Project Roles screen used a union check
(lib/usermanagement/usersAssign.php:201-240 checkRights()): a user is allowed
in when they hold any of
-
role_management, -
testplan_user_role_assignment(test project / test plan context), -
user_role_assignment(global), -
testproject_user_role_assignmenton the target test project.
Consequences of the modern behaviour:
-
False denial β a user whose only assign right is
user_role_assignment(e.g. roleleader) received403 no_permissions_for_actionfrom the BFF although the legacy page loaded for them. -
No project filtering β the Test Project combo was built from
get_accessible_for_user(...,'map_of_map')(name/active, noeffective_role), so it listed projects whose effective role cannot assign roles. LegacygetTestProjectEffectiveRoles()(:273-336) lists only projects whose effective role holdsuser_role_assignmentORtestproject_user_role_assignment; when none remain it showstestproject_roles_assign_disabled.
-
lib/usermanagement/usersAssign.php:23βtestlinkInitPage($db,false,false,"checkRights") -
lib/usermanagement/usersAssign.php:201-240βcheckRights()union -
lib/usermanagement/usersAssign.php:246-266βcheckRightsForUpdate() -
lib/usermanagement/usersAssign.php:273-336βgetTestProjectEffectiveRoles() -
lib/functions/common.php:1010-1048βcheckUserRightsFor()(audit + redirect) -
lib/functions/roles.inc.php:191-205βg_rights_users_global(propagable rights) -
lib/functions/testproject.class.php:490-683βget_accessible_for_user()(map_of_map_fullcarrieseffective_role)
- Removed the global
role_managementgate; enforcement is now route-aware:- role-catalog routes keep
role_management(denyRoleManagement()); -
GET /meta/tproject-roles,GET /meta/tplan-roles,PUT /tproject-roles,PUT /tplan-rolesuse the new helpers:-
userCanAssignRoles()β legacycheckRights()union (reads the caller's project/plan roles before evaluatinghasRight()); -
userCanUpdateAssignments()β legacycheckRightsForUpdate(); -
denyAssignRights()β403+logAuditEvent(audit_security_user_right_missing).
-
- role-catalog routes keep
-
getAssignableProjects()β portsgetTestProjectEffectiveRoles(): usesget_accessible_for_user($userId, ['output'=>'map_of_map_full'])and keeps only projects whoseeffective_role(tlRole::hasRight()) hasuser_role_assignmentortestproject_user_role_assignment. -
GET /meta/tproject-rolesnow returns that filtered combo.
- New
#disabledMsgblock renders the legacytestproject_roles_assign_disabledfeedback when the combo is empty or the API answers403; the table is hidden and the project select disabled. - Safe selection: a URL
tproject_idnot present in the filtered combo falls back to the first assignable project (legacy:307-316) instead of loading a non-assignable project. - i18n key
assign.rolesDisabledadded to all 10 locale bundles.
-
leader(global role 9, project roletesteron Alpha): BFF returnsprojects=[Beta]; the legacy page renders the identical combo["2:Beta Project"]. -
frank(role 3<no rights>): BFF returns403and logsaudit_security_user_right_missing; screen shows the disabled message. - admin: both projects listed, assignments table loads.
-
php -lclean; all 10 i18n bundles valid JSON. Full matrix:tmp/TLU_Test_Cases.mdsuite 1523 (15/15 PASS).
Discovered while building the rights/filtering work above; filed as
#1523 and fixed in
commit 7464701df (branch fix/issue-1523-empty-userids).
Root cause β PUT /tproject-roles / PUT /tplan-roles with
assignments:{} passed array_keys([]) = [] to deleteUserRoles(); the
!is_null($users) branch appended AND user_id IN() (empty implode()) β
MariaDB 1064 ... near ')' β uncaught DB exception β HTTP 500 and a new
events log_level=1 row. The legacy controller already treated an empty map
as a no-op (lib/usermanagement/usersAssign.php:560-562, [] == null).
Fix
-
lib/functions/testproject.class.php/lib/functions/testplan.class.phpdeleteUserRoles(): an empty array returnstl::OKbefore any SQL is built;nullkeeps its delete-all meaning. -
api/roles/index.php: both PUT handlers short-circuit an empty/absentassignmentsmap to{"status":"ok"}before any manager call or audit event; a non-arrayassignmentsis rejected with HTTP400.
Verified β both endpoints answer 200 {"status":"ok"}, events gains no
new ERROR; assignment ({uid:role}), un-assignment ({uid:0}) and the legacy
null delete-all paths (project/plan delete) are unaffected. Regression suite
in tmp/TLU_Test_Cases.md (βRegression β Issue #1523β, 11/11 PASS).
leader (only user_role_assignment) β combo shows only Beta Project:

frank (role 3 <no rights>) β legacy testproject_roles_assign_disabled feedback:
