Skip to content

Fix: orphaned documents/folders when SPA loses ?cid= in URL — non-admin teachers can't upload#8662

Closed
addow wants to merge 1 commit into
chamilo:masterfrom
webstone:fix/document-upload-cid-injection
Closed

Fix: orphaned documents/folders when SPA loses ?cid= in URL — non-admin teachers can't upload#8662
addow wants to merge 1 commit into
chamilo:masterfrom
webstone:fix/document-upload-cid-injection

Conversation

@addow

@addow addow commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Folders and documents created via the SPA can end up orphaned — created in the database but invisible in the course documents list — when the URL is missing ?cid=…. This happens because:

  • The shared axios request interceptor in assets/vue/config/api.js reads getRawCourseContext() from window.location.search and injects cid/sid/gid into config.params. When the SPA navigates inside a course without preserving ?cid=… in the URL (e.g. after entering a sub-folder), that read returns nulls, no params are injected, and the POST goes out as plain /api/documents with no query.
  • Server-side, CidReqListener sees an empty cid and calls cleanSessionHandler(), wiping session.course.
  • CreateDocumentFileAction then runs buildResourceLinkListFromContext() which finds no course in session and no cid in the query, so the resource_link is built without cid.
  • The new document is created but has no resource_link binding it to the course, so it disappears from the documents list right after creation.

The security check still passes because admins inherit ROLE_CURRENT_COURSE_TEACHER via Symfony's role_hierarchy (config/packages/security.yaml), which masks the bug for everyone with ROLE_ADMIN. Teachers without admin role hit it.

Affected: every /api/documents POST going through makeService("documents").createWithFormData — i.e. folder creation and any flow using store.dispatch("documents/createWithFormData", …). The chunked uploader at DocumentsUpload.vue:609 was already constructing its own URL with explicit cid/sid/gid and is unaffected.

Fix

Surgical override in assets/vue/services/documents.js:

  • Override createWithFormData to construct its own POST request.
  • Read cid/sid/gid from the Pinia cidReq store first (authoritative — maintained by router guards, survives URL changes) and fall back to getCourseContext() (URL-based) only when the store is not yet active (early bootstrap / tests).
  • Append them as query parameters on /api/documents.

No backend changes; no impact on other modules.

Test plan

  • Log in as a non-admin teacher of a course
  • Navigate to the course documents tool via the SPA navigation (no ?cid= in URL after a few clicks)
  • Create a new folder → folder appears in the list and is bound to the course
  • Upload a single document via the standard (non-chunked) path → document appears in the list
  • Verify the resulting resource_link row has the expected course_id populated
  • Regression: log in as global admin and repeat — behaviour unchanged (this path was already silently broken but masked by ROLE_CURRENT_COURSE_TEACHER inheritance)

@addow addow changed the title fix(vue/documents): force cid/sid/gid on POST /api/documents Fix: orphaned documents/folders when SPA loses ?cid= in URL — non-admin teachers can't upload Jun 25, 2026
The shared axios request interceptor in assets/vue/config/api.js reads
getRawCourseContext() from window.location.search and injects cid/sid/gid
into config.params. When the SPA navigates inside a course without
preserving ?cid=… in the URL, that read returns nulls, no params are
injected, and the POST goes out as plain /api/documents (no query).

On the server side, CidReqListener sees an empty $request->get('cid'),
treats the request as out-of-course, and calls cleanSessionHandler()
which removes session.course. CreateDocumentFileAction then runs
buildResourceLinkListFromContext() which finds no course in session and
no cid in the query, so the resource_link entry is built without cid.

The new document is created but is orphaned — no resource_link binding it
to the course — so it disappears from the documents list immediately
after creation. The security check still passes because admins inherit
ROLE_CURRENT_COURSE_TEACHER via Symfony's role_hierarchy (security.yaml),
masking the bug for everyone with ROLE_ADMIN.

Affected: every /api/documents POST that goes through the legacy
makeService("documents").createWithFormData path — i.e. folder creation
and any flow using store.dispatch("documents/createWithFormData", …).
The chunked uploader at DocumentsUpload.vue:609 was already constructing
its own URL with explicit cid/sid/gid and is unaffected.

Surgical fix in services/documents.js:
- Override createWithFormData to construct its own POST request.
- Read cid/sid/gid from the Pinia cidReq store first (authoritative,
  maintained by router guards, survives URL changes) and fall back to
  getCourseContext() (URL-based) only when the store is not yet active
  (early bootstrap / tests).
- Append them as query parameters on /api/documents.

Confirmed: the same code path exists on v2.0.2 and on chamilo/chamilo-lms
upstream master, so this is a community-wide bug. Forwarding it upstream
as a separate PR is on the to-do.
@addow addow force-pushed the fix/document-upload-cid-injection branch from 4bcfffd to 4975d85 Compare June 30, 2026 08:05
@AngelFQC

AngelFQC commented Jul 6, 2026

Copy link
Copy Markdown
Member

Thanks for the detailed analysis — the root-cause chain is accurate. However, this is now superseded: course-tool routes require cid at the router level (requiresCourseContext guard in assets/vue/router/index.js), so the documents tool can no longer be reached with a cid-less URL and the axios interceptor always finds the context. The scenario this patches is unreachable.

Also, reading the Pinia store in a single service would fork the context-resolution pattern (everything else goes through the shared interceptor), which we'd rather avoid.

Two follow-ups worth pursuing instead, if you're up for it:

  1. Identify which internal navigation drops ?cid= — it now bounces users to Home instead of orphaning documents, but it's still a bug.
  2. Backend hardening: POST /api/documents without resolvable course context should be rejected instead of persisting an orphaned document (currently still possible for admins via direct API calls).

4739271
6a85b19

Closing in favour of the above.

@AngelFQC AngelFQC closed this Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants