Modernize Requirements Bulk Assignment reqTcBulkAssign - sebiboga/testlink-upgraded GitHub Wiki
Refs #1595 — the requirements coverage grid of the Test Specification, now a Dashio popup with a PHP BFF.
The ledger's TODO section was empty, so the pick rule was "the smallest coherent
standalone legacy screen that has no modern twin". That is
lib/requirements/reqTcAssign.php in testsuite / bulk mode: the controller
rendered gui/templates/dashio/requirements/reqTcBulkAssignment.tpl, a
requirements grid reachable from the Test Specification tree through the
legacy listTestCases.php?feature=assignReqs action.
It is not a duplicate of the "Test Suite (Bulk)" tab of
gui/templates/requirements/assignReqs.html (Refs #1083). That tab is a
picker: two multi-selects that hand a set of requirements to one test case
version. This screen is a per-suite coverage grid:
- every requirement of the selected requirement spec is listed with its
doc_id, title, scope and a truthfuln/m test cases of this suite already linkedchip, - the whole set (or any subset) can be assigned to every test case of the suite subtree in one click,
- and — new, legacy only offered assign — the selected requirements can be unassigned from the suite's test cases again.
Legacy write path: requirement_mgr::bulkAssignLatestREQVTCV() (latest
requirement version × latest test case version, idempotent, and by design
without an audit trail). All of that behaviour is preserved 1:1.
| Route | Behaviour |
|---|---|
GET ?action=init&tproject_id=&tsuite_id=[&idSRS=] |
context card, get_testcases_deep() count, per-requirement linked_count, requirement-spec options, selected_req_spec (persisted in the session like the legacy idSRS) |
POST ?action=bulkassign |
bulkAssignLatestREQVTCV() + one aggregated audit_req_assigned_tc event |
POST ?action=unassign |
deletes exactly the open coverage rows the grid created (latest req version × latest TC version) + audit_req_assignment_removed_tc
|
- session auth (
doSessionStart) +bffSameOriginGuardfor the unsafe verbs, - right
req_tcase_link_management→ 403 with the legacy wording, - JSON contract: 401 anonymous, 403 rights / CSRF, 404 unknown suite, 400 no context / no test cases / nothing selected / unknown action, 405 wrong verb.
Hardening beyond legacy
- the test suite is proven to belong to the test project before anything is read
or written (the legacy controller trusted
idSRS+ the session project), - the submitted
req_ids are intersected with the requirements that really live on the submittedidSRS, so a forged id from another specification or project is rejected (rejected: n) instead of being linked — this closes the cross-spec hole tracked by #1085 for this route.
Dashio popup: teal header + dark toolbar (Refresh / Back to test
specification / Close), the legacy warning bar
(bulk_req_assign_msg with the test-case count and suite name, or
bulk_req_assign_no_test_cases), a context card (test project, test suite,
test cases deep, requirement-specification selector), the grid
(check/uncheck-all, Doc ID, Requirement + coverage chip, Scope), the
Assign to all test cases of the suite / Unassign from the suite test
cases actions with a Bootstrap confirm dialog, a feedback box
(bulk_assigment_done / bulk_unassing_done), the empty states and the
localized 401 / 403 / 404 / missing-context cards, and the TLi18n locale
switcher.
-
$actions->reqTcBulkAssigninlib/functions/common.php. - The Test Specification suite view gained a Requirements Bulk
Assignment button (
openReqBulkAssign()) — the modern equivalent of the legacy tree action — gated onrequirementsEnabled+req_tcase_link_management. -
lib/requirements/reqTcAssign.phpis now a session-guarded 302 shim (anonymous → login,?id=forwarded astsuite_id).
-
#1596 —
gui/templates/testcases/testSpec.htmlgated both requirement-linking buttons onctx.reqEnabled, a propertyinit()never sets, so the pre-existing per-test-case Assign Requirements button was never rendered either. Both now usectx.options.requirementsEnabled. - The repo vendors Bootstrap 3.4.1, so the confirm dialog loaded a
non-existent
bootstrap.bundle.min.jsand threwCannot read properties of undefined (reading 'Modal'). The screen uses the jQuerymodal('show'/'hide')API, and BS3's data-api needs an explicitdata-targetto dismiss a modal (header × and Cancel). - The audit events passed the requirements table as
$objectID, so the Event Viewer showed no object; they are now bound to the test suite (object_id = <suite>,object_type = testsuites) and are no longer written for a no-op. -
Stored XSS (code review) — the test-suite name was injected raw into the
warning bar and the confirm dialog. It is now escaped, and every placeholder
is filled with a replacer callback, so a name containing
$&,$`or$'is displayed literally and can never rewrite the sentence. -
IDOR on
idSRS(code review) — the spec id was only validated as> 0, so a spec of another test project could be read and its requirements linked to (or unlinked from) this project's test cases. The spec is now resolved against the current test project ininit(request id and the persisted session memory) and in both POST actions. -
Private-project rights bypass (code review) — the right was evaluated
without
getAccess, so a user holdingreq_tcase_link_managementthrough a global role could act on a private project they are not a member of.hasRight(..., null, true)(legacypageAccessCheck()semantics) now denies it, and the refusal is audited (SECURITY/testprojects). - Smaller review fixes:
tsuite_idmust be a real test-suite node (a legacyedit=testcasedeep link no longer resolves to the parent suite), the project must have requirements enabled, "nothing selected" is a 400 that the screen renders, the actions stay disabled on a suite without test cases, thelinked_countchip only counts the open linksunassignremoves, the footer uses the translatedfooters.reqTcBulkAssignkey, every row regained the legacy requirement-version editor link, the checkboxes have accessible titles, and responses carrycharset=utf-8+nosniff.
- Regression suite 1595: 63/63 PASS in
tmp/TLU_Test_Cases.md(16 BFF contract + 24 browser states + 3 entry point + 6 Event Viewer + 14 security regressions: XSS suite name in the warning bar and in both confirm dialogs, foreign-spec IDOR on init/assign/unassign,idSRSsent as a JSON array, unvalidated session spec, test-case node astsuite_id, requirements-disabled project, private-project global-right bypass, CSRF and response hardening). - Fixtures:
tmp/fixtures_1595.php(project BULK1595, 2 specs, suite with 3 deep test cases, empty suite, 1 seeded link) andtmp/norights_1595.php(user withoutreq_tcase_link_management). - Event Viewer: 0 ERROR/WARNING rows; the two new aggregated audit events render with their localized text.
| Screen state | Screenshot |
|---|---|
| Grid with coverage chips and the legacy warning | ![]() |
| Confirm dialog | ![]() |
Romanian locale (?locale=ro) |
![]() |
403 — no req_tcase_link_management
|
![]() |
| Entry button in the Test Specification suite view | ![]() |




