diff --git a/docs/openedx_content/decisions/0005-serving-static-assets.rst b/docs/openedx_content/decisions/0005-serving-static-assets.rst index c2b01ff43..315f504f9 100644 --- a/docs/openedx_content/decisions/0005-serving-static-assets.rst +++ b/docs/openedx_content/decisions/0005-serving-static-assets.rst @@ -6,12 +6,12 @@ Status ------ -Accepted in principle. Pending implementation. +Accepted in principle. Partially implemented. Substantially revised in September 2026. Context -------- -Both Studio and the LMS need to serve course team authored static assets as part of the authoring and learning experiences. "Static assets" in the edx-platform context presently refers to: image files, audio files, text document files like PDFs, older video transcript files, and even JavaScript and Python files. It does NOT typically include video files, which are treated separately because of their large file size and complex workflows (processing for multiple resolutions, using third-party dictation services, etc.) +The Open edX platform needs to serve course team authored static assets as part of the authoring and learning experiences. "Static assets" in the openedx-platform context presently refers to: image files, audio files, text document files like PDFs, older video transcript files, and even JavaScript and Python files. It does NOT typically include video files, which are treated separately because of their large file size and complex workflows (processing for multiple resolutions, using third-party dictation services, attached subtitle files, etc.) This ADR is the synthesis of various ideas that were discussed across a handful of pull requests and issues. These links are provided for extra context, but they are not required to understand this ADR: @@ -19,20 +19,23 @@ This ADR is the synthesis of various ideas that were discussed across a handful * `File Uploads + media_server app #33 `_ * `Modeling Files and File Dependencies #70 `_ * `Serving static assets (disorganized thoughts) #108 `_ +* `Unrestricted image upload leads to stored XSS `_ Data Storage Implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The underlying data models live in the openedx-learning repo. The most relevant models are: +The underlying data models live in the ``openedx_content`` app. The most relevant models are: -* `Content in contents/models.py `_ -* `Component and ComponentVersion in components/models.py `_ +* `Media in media/models.py `_ +* `Component and ComponentVersion in components/models.py `_ Key takeaways about how this data is stored: -* Assets are associated and versioned with Components, where a Component is typically an XBlock. So you don't ask for "version 5 of /static/fig1.webp", you ask for "the /static/fig1.webp associated with version 5 of this block". -* This initial MVP would be to serve assets for v2 content libraries, where all static assets are associated with a particular component XBlock. Later on, we'll want to allow courses to port their existing files and uploads into this system in a backwards compatible way. We will probably do this by creating a non-XBlock, filesystem Component type that can treat the entire course's uploads as a Component. The specifics for how that is modeled on the backend are out of scope for this ADR, but this general approach is meant to work for both use cases. -* The actual raw asset data is stored in django-storages using its hash value as the file name. This makes it cheap to make many references to the same asset data under different names and versions, but it means that we cannot simply give direct links to the raw file data to the browser (see the next section for details). +Currently, all assets are associated and versioned with Components, where a Component is typically an XBlock. So you don't ask for "version 5 of /static/fig1.webp"; you ask for "the /static/fig1.webp associated with version 5 of this Component". + +:ref:`openedx-content-adr-0013` proposes a special type of component that only holds assets (an "AssetSet"), and no XBlock, so that a course's existing files and uploads can be referenced by multiple XBlocks or used independently (e.g. a PDF download). + +The actual raw asset data is stored in django-storages using its hash value as the file name. This makes it cheap to make many references to the same asset data under different names and versions, but it means that we cannot simply give direct links to the raw file data to the browser (see the next section for details). The Difficulty with Direct Links to Raw Data Files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -61,16 +64,23 @@ Application Requirements Security Requirements ~~~~~~~~~~~~~~~~~~~~~ -**Assets must enforce user+file read permissions at the Learning Context level.** - The MongoDB GridFS backed ContentStore currently supports course-level access checks that can be toggled on and off for individual assets. Uploaded assets are public by default, and can be downloaded by anyone who knows the URL, regardless of whether or not they are enrolled in the course. They can optionally be "locked", which will restrict downloads to students who are enrolled in the course. +**The ``openedx_content`` app only knows about LearningPackages and content, not Learning Contexts** + Permissions in the Open edX platform are (now) defined by the ``openedx-authz`` authorization framework, and depend on various factors like which learning context (course/library) hosts the asset and what roles the requesting user has. However, this ``openedx_content`` app is a low-level content management system, and does not know about ``openedx-authz`` nor have any way to directly check permissions/authorization. + +**Assets require fine-grained permissions.** + The Open edX platform today depends on various rules for determining who can access each asset file. The MongoDB GridFS backed ContentStore currently supports course-level access checks that can be toggled on and off for individual assets. Uploaded assets are public by default, but can optionally be "locked", which will restrict downloads to students who are enrolled in the course. Components in courses have complex authorization rules (release dates, cohorts, A/B testing, etc.), so any assets that are attached to Components should also respect those same rules. In other words, *permissions checking must be extensible*. The ``openedx_content`` app will implement the details of how to serve an asset, but it will not have the necessary models and logic to determine whether it is allowed to. + +**Uploaded assets must never execute scripts with the LMS or Studio origin.** + A user navigating to an asset URL, or a page framing it, must not result in author-supplied script running with the session cookies of the LMS or Studio. + + Any asset that a browser will render as a *document* (HTML, SVG, XML, and to a lesser extent PDF) can carry script. If such a file is served inline from the LMS or Studio origin, and a user navigates to it or a page embeds it in an ``