From 0769b61c0bacdd587be836ee66dc96f5fad6cf43 Mon Sep 17 00:00:00 2001 From: MuPp3t33r <67326198+MuPp3t33r@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:01:57 +0200 Subject: [PATCH 1/8] feat: Allow admin to override the maximum file upload size Original configuration had a hard-coded limit of 20MB per file supplied to the platform. This change will retain the existing 20MB limit as default, but provide the admin with the ability to override the value via the MFE_CONFIG API (getConfig) The new function includes validation to check that the override value supplied is in fact a valid and positive integer and reverts to default 20MB in case of validation failure --- src/constants.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/constants.js b/src/constants.js index beb4a74d1f..c3920db7b2 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,3 +1,5 @@ +import { getConfig } from '@edx/frontend-platform'; + export const DATE_FORMAT = 'MM/dd/yyyy'; export const TIME_FORMAT = 'HH:mm'; export const DATE_TIME_FORMAT = 'YYYY-MM-DDTHH:mm:ss\\Z'; @@ -52,7 +54,16 @@ export const DECODED_ROUTES = { ], }; -export const UPLOAD_FILE_MAX_SIZE = 20 * 1024 * 1024; // 100mb +// FilesUpload page - Default max size: 20MB else use env override if exists and valid number +const DEFAULT_UPLOAD_FILE_MAX_SIZE = 20 * 1024 * 1024; // 20 MB + +export const getUploadFileMaxSize = () => { + const config = getConfig(); + const overrideMaxFileSizeMB = parseInt(config.OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB, 10); + return !Number.isNaN(overrideMaxFileSizeMB) && overrideMaxFileSizeMB > 0 + ? overrideMaxFileSizeMB * 1024 * 1024 + : DEFAULT_UPLOAD_FILE_MAX_SIZE; +}; export const COURSE_BLOCK_NAMES = ({ chapter: { id: 'chapter', name: 'Section' }, From 6706b676d5b0cfb684ce6683f8b9a2729677112a Mon Sep 17 00:00:00 2001 From: MuPp3t33r <67326198+MuPp3t33r@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:03:49 +0200 Subject: [PATCH 2/8] fix: use new getUploadFileMaxSize function added to constants Originally the constant 'maxFileSize' was hardcoded to 20MB and was not configured to use the pre-existing value defined in constants.js This fix removes the hardcoded value and calls the function getUploadFileMaxSize() to determine the value of maxFileSize --- src/files-and-videos/files-page/FilesPage.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/files-and-videos/files-page/FilesPage.jsx b/src/files-and-videos/files-page/FilesPage.jsx index 0976cc9bb6..df6c63fc3e 100644 --- a/src/files-and-videos/files-page/FilesPage.jsx +++ b/src/files-and-videos/files-page/FilesPage.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { CheckboxFilter, Container } from '@openedx/paragon'; +import { getUploadFileMaxSize } from '@src/constants'; import Placeholder from '../../editors/Placeholder'; import { RequestStatus } from '../../data/constants'; @@ -90,7 +91,7 @@ const FilesPage = ({ usageErrorMessages: errorMessages.usageMetrics, fileType: 'file', }; - const maxFileSize = 20 * 1048576; + const maxFileSize = getUploadFileMaxSize(); const activeColumn = { id: 'activeStatus', From 7c43ce876e6c5d6594638eb93d0c4c0439ce3b28 Mon Sep 17 00:00:00 2001 From: MuPp3t33r <67326198+MuPp3t33r@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:06:22 +0200 Subject: [PATCH 3/8] fix: use new getUploadFileMaxSize function added to constants This change allows the TextbookForm to fetch the maximum allowed size via a call to getUploadFileMaxSize() defined in constants. Additionally fixes the conversion factor (maxSize/(1024*1024) vs (1000*1000) for consistency with upstream code --- src/textbooks/textbook-form/TextbookForm.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/textbooks/textbook-form/TextbookForm.jsx b/src/textbooks/textbook-form/TextbookForm.jsx index dfc472ce4f..db92b6a8d4 100644 --- a/src/textbooks/textbook-form/TextbookForm.jsx +++ b/src/textbooks/textbook-form/TextbookForm.jsx @@ -17,11 +17,11 @@ import { useToggle, } from '@openedx/paragon'; +import { getUploadFileMaxSize } from '@src/constants'; import FormikControl from '../../generic/FormikControl'; import PromptIfDirty from '../../generic/prompt-if-dirty/PromptIfDirty'; import ModalDropzone from '../../generic/modal-dropzone/ModalDropzone'; import { useModel } from '../../generic/model-store'; -import { UPLOAD_FILE_MAX_SIZE } from '../../constants'; import textbookFormValidationSchema from './validations'; import messages from './messages'; @@ -171,7 +171,7 @@ const TextbookForm = ({ onSavingStatus={onSavingStatus} invalidFileSizeMore={intl.formatMessage( messages.uploadModalFileInvalidSizeText, - { maxSize: UPLOAD_FILE_MAX_SIZE / (1000 * 1000) }, + { maxSize: getUploadFileMaxSize() / (1024 * 1024) }, )} onSelectFile={setSelectedFile} previewComponent={( @@ -180,7 +180,7 @@ const TextbookForm = ({ {selectedFile} )} - maxSize={UPLOAD_FILE_MAX_SIZE} + maxSize={getUploadFileMaxSize()} /> From 02e4da3b8d15d2488ef73d3196dbd7d4fe47abfd Mon Sep 17 00:00:00 2001 From: MuPp3t33r <67326198+MuPp3t33r@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:09:40 +0200 Subject: [PATCH 4/8] fix: use new getUploadFileMaxSize function added to constants This change allows the ModalDropzone to fetch the maximum allowed size via a call to getUploadFileMaxSize() defined in constants. Additionally fixes the conversion factor (maxSize/(1024*1024) vs (1000*1000) for consistency with upstream code --- src/generic/modal-dropzone/ModalDropzone.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/generic/modal-dropzone/ModalDropzone.jsx b/src/generic/modal-dropzone/ModalDropzone.jsx index 8133910785..ea92358f75 100644 --- a/src/generic/modal-dropzone/ModalDropzone.jsx +++ b/src/generic/modal-dropzone/ModalDropzone.jsx @@ -13,9 +13,9 @@ import { } from '@openedx/paragon'; import { FileUpload as FileUploadIcon } from '@openedx/paragon/icons'; +import { getUploadFileMaxSize } from '@src/constants'; import useModalDropzone from './useModalDropzone'; import messages from './messages'; -import { UPLOAD_FILE_MAX_SIZE } from '../../constants'; const ModalDropzone = ({ fileTypes, @@ -30,7 +30,7 @@ const ModalDropzone = ({ onChange, onSavingStatus, onSelectFile, - maxSize = UPLOAD_FILE_MAX_SIZE, + maxSize, }) => { const { intl, @@ -48,7 +48,7 @@ const ModalDropzone = ({ const invalidSizeMore = invalidFileSizeMore || intl.formatMessage( messages.uploadImageDropzoneInvalidSizeMore, - { maxSize: maxSize / (1000 * 1000) }, + { maxSize: (maxSize || getUploadFileMaxSize()) / (1024 * 1024) }, ); const inputComponent = previewUrl ? ( @@ -129,7 +129,7 @@ ModalDropzone.defaultProps = { imageHelpText: '', previewComponent: null, imageDropzoneText: '', - maxSize: UPLOAD_FILE_MAX_SIZE, + maxSize: '', invalidFileSizeMore: '', onSelectFile: null, }; From f0e02527c6f3bdcecf718a685a6e717c49ac19ad Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Tue, 19 Aug 2025 13:50:44 -0700 Subject: [PATCH 5/8] fix: default value for maxSize had wrong type --- src/generic/modal-dropzone/ModalDropzone.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generic/modal-dropzone/ModalDropzone.jsx b/src/generic/modal-dropzone/ModalDropzone.jsx index ea92358f75..e8457fe288 100644 --- a/src/generic/modal-dropzone/ModalDropzone.jsx +++ b/src/generic/modal-dropzone/ModalDropzone.jsx @@ -129,7 +129,7 @@ ModalDropzone.defaultProps = { imageHelpText: '', previewComponent: null, imageDropzoneText: '', - maxSize: '', + maxSize: undefined, invalidFileSizeMore: '', onSelectFile: null, }; From a19d1aba18a0e6baca2ee2111c42139568fca3cc Mon Sep 17 00:00:00 2001 From: MuPp3t33r <67326198+MuPp3t33r@users.noreply.github.com> Date: Fri, 22 Aug 2025 12:36:29 +0200 Subject: [PATCH 6/8] docs: Create OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB.rst documentation of new config option and it's usage --- .../OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB.rst | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 docs/how_tos/OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB.rst diff --git a/docs/how_tos/OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB.rst b/docs/how_tos/OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB.rst new file mode 100644 index 0000000000..36b659b0b4 --- /dev/null +++ b/docs/how_tos/OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB.rst @@ -0,0 +1,65 @@ +#################### +OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB +#################### +This document provides information related to overriding the maxFileSize values allowed when uploading files into the Studio. +Currently the override affects the following areas of the platform: + +* ``Content -> Files``: This is the general location for files to be uploaded into Studio. +* ``Content -> Pages & Resources -> Textbooks``: This is the location specifically for uploading textbooks into a course. + +In addition to overriding the value in ``openedx-lms-production-settings`` it is also necessary to modify Caddy's ``max_size`` handling in the ``request_body`` otherwise Caddy will fail to process your file submission(s). + +The following Tutor plugin can be used as a template to configure the override value. +In the example provided, override_value = "1024" means 1024MB or equivalently 1GB. Replace this with your preferred value. + +.. code-block:: python + + from tutor import hooks + + # Instructions / info + # override_value is defined as a string so bad formats (incorrectly entered values) don't crash Python immediately + # User MUST enter only digits as a POSITIVE integer representing a value in MegaBytes (MB), e.g. "1024" for 1GB + # This adds the value to the MFE_Config API as well as the CaddyFile CMS block + + override_value = "1024" + + # --- Validation --- + try: + override_int = int(override_value) + if override_int <= 0: + raise ValueError + except ValueError: + raise ValueError( + f"Invalid override_value: {override_value}. " + "It must be a positive integer without units (e.g., 1048)." + ) + + # --- Config patches --- + hooks.Filters.ENV_PATCHES.add_items([ + ( + "openedx-lms-production-settings", + f""" + MFE_CONFIG["OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB"] = "{override_int}" + """ + ), + ]) + + hooks.Filters.ENV_PATCHES.add_item( + ( + "caddyfile-cms", + f""" + # Maximum asset upload size in CMS/Studio + handle /assets/* {{ + request_body {{ + max_size {override_int}MB + }} + }} + """ + ) + ) + +Assuming your plugin is named ``override_max_asset_upload_size.py``: + +* activate your plugin: ``tutor plugins enable override_max_asset_upload_size`` +* restart your server instance: ``tutor local stop && tutor local start -d`` +* validation: open the ``Files & Uploads`` page and confirm that your new override value is displayed instead of the default 20MB limit From 81892095d5fd5c30eac4f9817fc180953d7e95a7 Mon Sep 17 00:00:00 2001 From: MuPp3t33r <67326198+MuPp3t33r@users.noreply.github.com> Date: Fri, 22 Aug 2025 12:37:14 +0200 Subject: [PATCH 7/8] docs: Update README.rst add information about new config option affecting files uploads --- README.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 96fade1c60..e180fc6026 100644 --- a/README.rst +++ b/README.rst @@ -126,11 +126,15 @@ The following are requirements for this feature to function correctly: Configuration ------------- -In additional to the standard settings, the following local configuration items are required: +In addition to the standard settings, the following local configuration items are required: * ``LEARNING_BASE_URL``: points to Learning MFE; necessary so that the `View Live` button works * ``ENABLE_PROGRESS_GRAPH_SETTINGS``: allow enabling or disabling the learner progress graph course-wide +In addition to the standard settings, the following local configuration items are optional: + +* ``OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB``: specifies a positive (non-zero) integer representing a value in MegaBytes, used to override the maxSize attribute when uploading textbooks. For more information and examples of usage, please see `OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB `_ in the how_tos docs. + Feature Description ------------------- @@ -212,6 +216,14 @@ Feature: Files & Uploads In Studio, the "Files & Uploads" page for each enabled course will now be served by this frontend, instead of the UI built into edx-platform. This page allows managing static asset files like PDFs, images, etc. used for the course. +Configuration +------------- + +In addition to the standard settings, the following local configuration items are optional: + +* ``OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB``: specifies a positive (non-zero) integer representing a value in MegaBytes, used to override the maxSize attribute when uploading files. For more information and examples of usage, please see `OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB `_ in the how_tos docs. + + Feature: Course Updates ========================== From 574a34c253205396d0cf782c300592b159909c68 Mon Sep 17 00:00:00 2001 From: MuPp3t33r <67326198+MuPp3t33r@users.noreply.github.com> Date: Fri, 22 Aug 2025 12:38:03 +0200 Subject: [PATCH 8/8] test: Create constants.test.ts add test coverage for new config option --- src/constants.test.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/constants.test.ts diff --git a/src/constants.test.ts b/src/constants.test.ts new file mode 100644 index 0000000000..c54dd4a299 --- /dev/null +++ b/src/constants.test.ts @@ -0,0 +1,35 @@ +import { mergeConfig } from '@edx/frontend-platform'; +import { getUploadFileMaxSize } from '@src/constants'; + +const DEFAULT_MAX = 20 * 1024 * 1024; + +describe('getUploadFileMaxSize()', () => { + afterEach(() => { + // Reset config after each test to avoid leaks + mergeConfig({}); + }); + + it('returns the global default when no config value is set', () => { + expect(getUploadFileMaxSize()).toEqual(DEFAULT_MAX); + }); + + it('returns OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB when set to a valid positive integer', () => { + mergeConfig({ OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB: 7 }); + expect(getUploadFileMaxSize()).toEqual(7 * 1024 * 1024); + }); + + it('falls back to default when override is not a number', () => { + mergeConfig({ OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB: 'not-a-number' as any }); + expect(getUploadFileMaxSize()).toEqual(DEFAULT_MAX); + }); + + it('falls back to default when override is 0', () => { + mergeConfig({ OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB: 0 }); + expect(getUploadFileMaxSize()).toEqual(DEFAULT_MAX); + }); + + it('falls back to default when override is negative', () => { + mergeConfig({ OVERRIDE_UPLOAD_FILE_MAX_SIZE_IN_MB: -5 }); + expect(getUploadFileMaxSize()).toEqual(DEFAULT_MAX); + }); +});