diff --git a/CHANGELOG-nightly.md b/CHANGELOG-nightly.md index 665d4306c81..a1d895a7a4b 100644 --- a/CHANGELOG-nightly.md +++ b/CHANGELOG-nightly.md @@ -2,6 +2,11 @@ Note: Breaking changes between versions are indicated by "💥". +- [Improvement] Get rid of many Django deprecation warnings that were printed in stdout for every LMS/CMS command. +- [Bugfix] Fix authentication to LMS when HTTPS is disabled. +- [Bugfix] Make it possible for plugins to implement the "caddyfile" patch without relying on the "port" local variable. +- 💥[Improvement] Move the Open edX forum to a [dedicated plugin](https://github.com/overhangio/tutor-forum/) (#450). +- [Bugfix] Fix frontend failure during login to the LMS. - 💥[Improvement] Drop Python 3.5 compatibility. - [Bugfix] Fix docker-compose project name in development on nightly branch. - 💥[Bugfix] No longer track the Tutor version number in resource labels (and label selectors, which breaks the update of Deployment resources), but instead do so in resource annotations. diff --git a/docs/configuration.rst b/docs/configuration.rst index abbd2353910..ea289234e85 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -77,7 +77,7 @@ You may want to pull/push images from/to a custom docker registry. For instance, Open edX customisation ~~~~~~~~~~~~~~~~~~~~~~ -- ``OPENEDX_COMMON_VERSION`` (default: ``"open-release/lilac.2"``) +- ``OPENEDX_COMMON_VERSION`` (default: ``"open-release/maple.beta1"``) This defines the default version that will be pulled from all Open edX git repositories. @@ -279,16 +279,16 @@ Note that your edx-platform version must be a fork of the latest release **tag** If you don't create your fork from this tag, you *will* have important compatibility issues with other services. In particular: -- Do not try to run a fork from an older (pre-Lilac) version of edx-platform: this will simply not work. +- Do not try to run a fork from an older (pre-Maple) version of edx-platform: this will simply not work. - Do not try to run a fork from the edx-platform master branch: there is a 99% probability that it will fail. -- Do not try to run a fork from the open-release/lilac.master branch: Tutor will attempt to apply security and bug fix patches that might already be included in the open-release/lilac.master but which were not yet applied to the latest release tag. Patch application will thus fail if you base your fork from the open-release/lilac.master branch. +- Do not try to run a fork from the open-release/maple.master branch: Tutor will attempt to apply security and bug fix patches that might already be included in the open-release/maple.master but which were not yet applied to the latest release tag. Patch application will thus fail if you base your fork from the open-release/maple.master branch. .. _i18n: Adding custom translations ~~~~~~~~~~~~~~~~~~~~~~~~~~ -If you are not running Open edX in English, chances are that some strings will not be properly translated. In most cases, this is because not enough contributors have helped translate Open edX in your language. It happens! With Tutor, available translated languages include those that come bundled with `edx-platform `__ as well as those from `openedx-i18n `__. +If you are not running Open edX in English, chances are that some strings will not be properly translated. In most cases, this is because not enough contributors have helped translate Open edX in your language. It happens! With Tutor, available translated languages include those that come bundled with `edx-platform `__ as well as those from `openedx-i18n `__. Tutor offers a relatively simple mechanism to add custom translations to the openedx Docker image. You should create a folder that corresponds to your language code in the "build/openedx/locale" folder of the Tutor environment. This folder should contain a "LC_MESSAGES" folder. For instance:: @@ -309,9 +309,9 @@ Then, add a "django.po" file there that will contain your custom translations:: .. warning:: Don't forget to specify the file ``Content-Type`` when adding message strings with non-ASCII characters; otherwise a ``UnicodeDecodeError`` will be raised during compilation. -The "String to translate" part should match *exactly* the string that you would like to translate. You cannot make it up! The best way to find this string is to copy-paste it from the `upstream django.po file for the English language `__. +The "String to translate" part should match *exactly* the string that you would like to translate. You cannot make it up! The best way to find this string is to copy-paste it from the `upstream django.po file for the English language `__. -If you cannot find the string to translate in this file, then it means that you are trying to translate a string that is used in some piece of javascript code. Those strings are stored in a different file named "djangojs.po". You can check it out `in the edx-platform repo as well `__. Your custom javascript strings should also be stored in a "djangojs.po" file that should be placed in the same directory. +If you cannot find the string to translate in this file, then it means that you are trying to translate a string that is used in some piece of javascript code. Those strings are stored in a different file named "djangojs.po". You can check it out `in the edx-platform repo as well `__. Your custom javascript strings should also be stored in a "djangojs.po" file that should be placed in the same directory. To recap, here is an example. To translate a few strings in French, both from django.po and djangojs.po, we would have the following file hierarchy:: diff --git a/docs/dev.rst b/docs/dev.rst index 9dcb3a1fcaa..4f2ed633325 100644 --- a/docs/dev.rst +++ b/docs/dev.rst @@ -25,7 +25,7 @@ This ``openedx-dev`` development image differs from the ``openedx`` production i - The user that runs inside the container has the same UID as the user on the host, in order to avoid permission problems inside mounted volumes (and in particular in the edx-platform repository). - Additional python and system requirements are installed for convenient debugging: `ipython `__, `ipdb `__, vim, telnet. -- The edx-platform `development requirements `__ are installed. +- The edx-platform `development requirements `__ are installed. Since the ``openedx-dev`` is based upon the ``openedx`` docker image, it should be re-built every time the ``openedx`` docker image is modified. @@ -137,7 +137,7 @@ Following the instructions :ref:`above ` on how to bind-mount direc If you choose any but the first solution above, you will have to make sure that your fork works with Tutor. -First of all, you should make sure that you are working off the ``open-release/lilac.2`` tag. See the :ref:`fork edx-platform section ` for more information. +First of all, you should make sure that you are working off the ``open-release/maple.beta1`` tag. See the :ref:`fork edx-platform section ` for more information. Then, you should run the following commands:: diff --git a/docs/faq.rst b/docs/faq.rst index 5ed78d86dbf..6f2f60c744c 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -38,7 +38,7 @@ The `devstack `_ is meant for development only, Is Tutor officially supported by edX? ------------------------------------- -As of the Open edX Lilac release (June 9th 2021), Tutor is one of the two officially supported installation methods for Open edX: see the `official installation instructions `__. We expect that by Maple (December 9th 2021) the native installation will be deprecated and Tutor will become the only officially recommended installation method, unless major issues are discovered. However, Tutor remains developed independently from edX, both by its parent company Overhang.IO and the :ref:`project maintainers `. +Yes: as of the Open edX Maple release (December 9th 2021), Tutor is the only officially supported installation methods for Open edX: see the `official installation instructions `__. What features are missing from Tutor? ------------------------------------- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index d985001487e..da637799945 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -22,8 +22,8 @@ Yes :) This is what happens when you run ``tutor local quickstart``: 2. Configuration files are generated from templates. 3. Docker images are downloaded. 4. Docker containers are provisioned. -5. A full, production-ready Open edX platform (`Lilac `__ release) is run with docker-compose. +5. A full, production-ready Open edX platform (`Maple `__ release) is run with docker-compose. The whole procedure should require less than 10 minutes, on a server with a good bandwidth. Note that your host environment will not be affected in any way, since everything runs inside docker containers. Root access is not even necessary. -There's a lot more to Tutor than that! To learn more about what you can do with Tutor and Open edX, check out the :ref:`whatnext` section. If the quickstart installation method above somehow didn't work for you, check out the :ref:`troubleshooting` guide. \ No newline at end of file +There's a lot more to Tutor than that! To learn more about what you can do with Tutor and Open edX, check out the :ref:`whatnext` section. If the quickstart installation method above somehow didn't work for you, check out the :ref:`troubleshooting` guide. diff --git a/tutor/__about__.py b/tutor/__about__.py index d3b32cc06f6..3abafe09f42 100644 --- a/tutor/__about__.py +++ b/tutor/__about__.py @@ -2,7 +2,7 @@ # Increment this version number to trigger a new release. See # docs/tutor.html#versioning for information on the versioning scheme. -__version__ = "12.1.7" +__version__ = "13.0.0" # The version suffix will be appended to the actual version, separated by a # dash. Use this suffix to differentiate between the actual released version and diff --git a/tutor/env.py b/tutor/env.py index 4cd8c485dd1..6d706d3aa7d 100644 --- a/tutor/env.py +++ b/tutor/env.py @@ -331,9 +331,13 @@ def current_release(root: str) -> str: """ Return the name of the current Open edX release. """ - return {"0": "ironwood", "3": "ironwood", "10": "juniper", "11": "koa"}[ - current_version(root).split(".")[0] - ] + return { + "0": "ironwood", + "3": "ironwood", + "10": "juniper", + "11": "koa", + "12": "lilac", + }[current_version(root).split(".")[0]] def current_version(root: str) -> str: diff --git a/tutor/templates/apps/openedx/config/cms.env.json b/tutor/templates/apps/openedx/config/cms.env.json index 0df5b3290b2..68aca49f139 100644 --- a/tutor/templates/apps/openedx/config/cms.env.json +++ b/tutor/templates/apps/openedx/config/cms.env.json @@ -37,7 +37,7 @@ "EMAIL_USE_TLS": {{ "true" if SMTP_USE_TLS else "false" }}, "HTTPS": "{{ "on" if ENABLE_HTTPS else "off" }}", "LANGUAGE_CODE": "{{ LANGUAGE_CODE }}", - "SESSION_COOKIE_DOMAIN": ".{{ LMS_HOST|common_domain(CMS_HOST) }}", + "SESSION_COOKIE_DOMAIN": "{{ CMS_HOST }}", {{ patch("cms-env", separator=",\n", suffix=",")|indent(2) }} "CACHES": { "default": { diff --git a/tutor/templates/apps/openedx/config/lms.env.json b/tutor/templates/apps/openedx/config/lms.env.json index e39533a2a12..b3188f94f5f 100644 --- a/tutor/templates/apps/openedx/config/lms.env.json +++ b/tutor/templates/apps/openedx/config/lms.env.json @@ -45,7 +45,7 @@ "ACE_ROUTING_KEY": "edx.lms.core.default", "HTTPS": "{{ "on" if ENABLE_HTTPS else "off" }}", "LANGUAGE_CODE": "{{ LANGUAGE_CODE }}", - "SESSION_COOKIE_DOMAIN": ".{{ LMS_HOST|common_domain(CMS_HOST) }}", + "SESSION_COOKIE_DOMAIN": "{{ LMS_HOST }}", {{ patch("lms-env", separator=",\n", suffix=",")|indent(2) }} "CACHES": { "default": { diff --git a/tutor/templates/apps/openedx/settings/cms/development.py b/tutor/templates/apps/openedx/settings/cms/development.py index da6824cf484..5ba365da67a 100644 --- a/tutor/templates/apps/openedx/settings/cms/development.py +++ b/tutor/templates/apps/openedx/settings/cms/development.py @@ -4,6 +4,11 @@ LMS_BASE = "{{ LMS_HOST }}:8000" LMS_ROOT_URL = "http://" + LMS_BASE + +# Authentication +SOCIAL_AUTH_EDX_OAUTH2_KEY = "{{ CMS_OAUTH2_KEY_SSO_DEV }}" +SOCIAL_AUTH_EDX_OAUTH2_PUBLIC_URL_ROOT = LMS_ROOT_URL + FEATURES["PREVIEW_LMS_BASE"] = "{{ PREVIEW_LMS_HOST }}:8000" {% include "apps/openedx/settings/partials/common_cms.py" %} diff --git a/tutor/templates/apps/openedx/settings/cms/production.py b/tutor/templates/apps/openedx/settings/cms/production.py index 6b04a882cc3..d09456e34db 100644 --- a/tutor/templates/apps/openedx/settings/cms/production.py +++ b/tutor/templates/apps/openedx/settings/cms/production.py @@ -9,4 +9,8 @@ "cms", ] +# Authentication +SOCIAL_AUTH_EDX_OAUTH2_KEY = "{{ CMS_OAUTH2_KEY_SSO }}" +SOCIAL_AUTH_EDX_OAUTH2_PUBLIC_URL_ROOT = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}" + {{ patch("openedx-cms-production-settings") }} diff --git a/tutor/templates/apps/openedx/settings/lms/development.py b/tutor/templates/apps/openedx/settings/lms/development.py index 10388901b84..2e86fb3ccd3 100644 --- a/tutor/templates/apps/openedx/settings/lms/development.py +++ b/tutor/templates/apps/openedx/settings/lms/development.py @@ -17,6 +17,9 @@ CMS_ROOT_URL = "http://{}".format(CMS_BASE) LOGIN_REDIRECT_WHITELIST.append(CMS_BASE) +# CMS authentication +IDA_LOGOUT_URI_LIST.append("http://{{ CMS_HOST }}:8001/complete/logout") + FEATURES['ENABLE_COURSEWARE_MICROFRONTEND'] = False LOGGING["loggers"]["oauth2_provider"] = { diff --git a/tutor/templates/apps/openedx/settings/lms/production.py b/tutor/templates/apps/openedx/settings/lms/production.py index fad463ce1a2..6ec8c3c0807 100644 --- a/tutor/templates/apps/openedx/settings/lms/production.py +++ b/tutor/templates/apps/openedx/settings/lms/production.py @@ -15,14 +15,17 @@ # Chrome to support samesite=none cookies. SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True -DCS_SESSION_COOKIE_SAMESITE = "None" +SESSION_COOKIE_SAMESITE = "None" {% else %} # When we cannot provide secure session/csrf cookies, we must disable samesite=none SESSION_COOKIE_SECURE = False CSRF_COOKIE_SECURE = False -DCS_SESSION_COOKIE_SAMESITE = "Lax" +SESSION_COOKIE_SAMESITE = "Lax" {% endif %} +# CMS authentication +IDA_LOGOUT_URI_LIST.append("{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ CMS_HOST }}/complete/logout") + # Required to display all courses on start page SEARCH_SKIP_ENROLLMENT_START_DATE_FILTERING = True diff --git a/tutor/templates/apps/openedx/settings/partials/common_all.py b/tutor/templates/apps/openedx/settings/partials/common_all.py index ac63b81aa40..e2874910c4d 100644 --- a/tutor/templates/apps/openedx/settings/partials/common_all.py +++ b/tutor/templates/apps/openedx/settings/partials/common_all.py @@ -25,7 +25,8 @@ } # Load module store settings from config files update_module_store_settings(MODULESTORE, doc_store_settings=DOC_STORE_CONFIG) -DATA_DIR = "/openedx/data/" +DATA_DIR = "/openedx/data/modulestore" + for store in MODULESTORE["default"]["OPTIONS"]["stores"]: store["OPTIONS"]["fs_root"] = DATA_DIR @@ -96,8 +97,11 @@ } LOGGING["loggers"]["tracking"]["handlers"] = ["console", "local", "tracking"] # Silence some loggers (note: we must attempt to get rid of these when upgrading from one release to the next) + import warnings -warnings.filterwarnings("ignore", category=DeprecationWarning, module="newrelic.console") +from django.utils.deprecation import RemovedInDjango40Warning, RemovedInDjango41Warning +warnings.filterwarnings("ignore", category=RemovedInDjango40Warning) +warnings.filterwarnings("ignore", category=RemovedInDjango41Warning) warnings.filterwarnings("ignore", category=DeprecationWarning, module="lms.djangoapps.course_wiki.plugins.markdownedx.wiki_plugin") warnings.filterwarnings("ignore", category=DeprecationWarning, module="wiki.plugins.links.wiki_plugin") @@ -165,11 +169,5 @@ "user": None, } -# Custom features -# LTI 1.3 will be enabled by default after lilac, and it's going to be a big -# deal, so we enable it early. We should remove this once the feature flag is -# deprecated. -FEATURES["LTI_1P3_ENABLED"] = True - {{ patch("openedx-common-settings") }} ######## End of settings common to LMS and CMS diff --git a/tutor/templates/apps/openedx/settings/partials/common_cms.py b/tutor/templates/apps/openedx/settings/partials/common_cms.py index 7cfeb07c65e..fe4a8285cd7 100644 --- a/tutor/templates/apps/openedx/settings/partials/common_cms.py +++ b/tutor/templates/apps/openedx/settings/partials/common_cms.py @@ -3,6 +3,12 @@ ######## Common CMS settings STUDIO_NAME = u"{{ PLATFORM_NAME }} - Studio" + +# Authentication +SOCIAL_AUTH_EDX_OAUTH2_SECRET = "{{ CMS_OAUTH2_SECRET }}" +SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT = "http://lms:8000" +SOCIAL_AUTH_REDIRECT_IS_HTTPS = False # scheme is correctly included in redirect_uri + MAX_ASSET_UPLOAD_FILE_SIZE_IN_MB = 100 FRONTEND_LOGIN_URL = LMS_ROOT_URL + '/login' @@ -16,4 +22,4 @@ {{ patch("openedx-cms-common-settings") }} -######## End of common CMS settings \ No newline at end of file +######## End of common CMS settings diff --git a/tutor/templates/build/openedx/Dockerfile b/tutor/templates/build/openedx/Dockerfile index 0c52da5867a..688b3f610b6 100644 --- a/tutor/templates/build/openedx/Dockerfile +++ b/tutor/templates/build/openedx/Dockerfile @@ -14,9 +14,9 @@ RUN apt update && \ apt install -y libssl-dev zlib1g-dev libbz2-dev \ libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \ xz-utils tk-dev libffi-dev liblzma-dev python-openssl git -ARG PYTHON_VERSION=3.8.6 +ARG PYTHON_VERSION=3.8.12 ENV PYENV_ROOT /opt/pyenv -RUN git clone https://github.com/pyenv/pyenv $PYENV_ROOT --branch v1.2.21 --depth 1 +RUN git clone https://github.com/pyenv/pyenv $PYENV_ROOT --branch v2.1.0 --depth 1 RUN $PYENV_ROOT/bin/pyenv install $PYTHON_VERSION RUN $PYENV_ROOT/versions/$PYTHON_VERSION/bin/python -m venv /openedx/venv @@ -44,6 +44,13 @@ RUN git config --global user.email "tutor@overhang.io" \ {{ patch("openedx-dockerfile-git-patches-default") }} {% else %} # Patch edx-platform +# Fix lms login failure +# https://github.com/edx/edx-platform/pull/29192/ +RUN git fetch https://github.com/regisb/edx-platform 29ae263a382dde472ab14bef93fe74813f83f271 && git cherry-pick 29ae263a382dde472ab14bef93fe74813f83f271 +# Add themed asset url +# https://github.com/edx/edx-platform/pull/29503 +RUN git fetch https://github.com/regisb/edx-platform 467ff94a82b020769e25fa0bdcf9c23d347f8fb1 && git cherry-pick 467ff94a82b020769e25fa0bdcf9c23d347f8fb1 + {% endif %} {# Example: RUN git fetch --depth=2 https://github.com/edx/edx-platform && git cherry-pick #} @@ -82,7 +89,7 @@ RUN pip install -r ./requirements/edx/base.txt RUN pip install django-redis==5.0.0 # Install uwsgi -RUN pip install uwsgi==2.0.19.1 +RUN pip install uwsgi==2.0.20 {{ patch("openedx-dockerfile-post-python-requirements") }} diff --git a/tutor/templates/config/base.yml b/tutor/templates/config/base.yml index 59a1c988d2e..6ecf851a359 100644 --- a/tutor/templates/config/base.yml +++ b/tutor/templates/config/base.yml @@ -1,4 +1,5 @@ --- +CMS_OAUTH2_SECRET: "{{ 24|random_string }}" ID: "{{ 24|random_string }}" JWT_RSA_PRIVATE_KEY: "{{ 2048|rsa_private_key }}" MYSQL_ROOT_PASSWORD: "{{ 8|random_string }}" diff --git a/tutor/templates/config/defaults.yml b/tutor/templates/config/defaults.yml index fd31c00b302..21e20db78a2 100644 --- a/tutor/templates/config/defaults.yml +++ b/tutor/templates/config/defaults.yml @@ -4,6 +4,8 @@ # This must be defined early CADDY_HTTP_PORT: 80 CMS_HOST: "studio.{{ LMS_HOST }}" +CMS_OAUTH2_KEY_SSO: "cms-sso" +CMS_OAUTH2_KEY_SSO_DEV: "cms-sso-dev" CONTACT_EMAIL: "contact@{{ LMS_HOST }}" DEV_PROJECT_NAME: "{{ TUTOR_APP }}_dev" DOCKER_REGISTRY: "docker.io/" @@ -13,11 +15,11 @@ DOCKER_IMAGE_CADDY: "{{ DOCKER_REGISTRY }}caddy:2.3.0" DOCKER_IMAGE_ELASTICSEARCH: "{{ DOCKER_REGISTRY }}elasticsearch:7.10.1" DOCKER_IMAGE_MONGODB: "{{ DOCKER_REGISTRY }}mongo:4.2.17" DOCKER_IMAGE_MYSQL: "{{ DOCKER_REGISTRY }}mysql:5.7.35" -DOCKER_IMAGE_ELASTICSEARCH: "{{ DOCKER_REGISTRY }}elasticsearch:7.10.1" DOCKER_IMAGE_NGINX: "{{ DOCKER_REGISTRY }}nginx:1.21.1" DOCKER_IMAGE_PERMISSIONS: "{{ DOCKER_REGISTRY }}overhangio/openedx-permissions:{{ TUTOR_VERSION }}" DOCKER_IMAGE_REDIS: "{{ DOCKER_REGISTRY }}redis:6.2.6" DOCKER_IMAGE_SMTP: "{{ DOCKER_REGISTRY }}devture/exim-relay:4.94.2-r0-4" +LOCAL_PROJECT_NAME: "{{ TUTOR_APP }}_local" ELASTICSEARCH_HOST: "elasticsearch" ELASTICSEARCH_PORT: 9200 ELASTICSEARCH_SCHEME: "http" @@ -45,8 +47,9 @@ OPENEDX_LMS_UWSGI_WORKERS: 2 OPENEDX_MYSQL_DATABASE: "openedx" OPENEDX_CSMH_MYSQL_DATABASE: "{{ OPENEDX_MYSQL_DATABASE }}_csmh" OPENEDX_MYSQL_USERNAME: "openedx" -OPENEDX_COMMON_VERSION: "master" +OPENEDX_COMMON_VERSION: "open-release/maple.beta1" OPENEDX_EXTRA_PIP_REQUIREMENTS: + # TODO upgrade scorm xblock - "openedx-scorm-xblock<13.0.0,>=12.0.0" MYSQL_HOST: "mysql" MYSQL_PORT: 3306 diff --git a/tutor/templates/hooks/lms/init b/tutor/templates/hooks/lms/init index 3b25b853e50..0057ada0ed6 100644 --- a/tutor/templates/hooks/lms/init +++ b/tutor/templates/hooks/lms/init @@ -4,6 +4,27 @@ echo "Loading settings $DJANGO_SETTINGS_MODULE" ./manage.py lms migrate +# Create oauth2 apps for CMS SSO +# https://github.com/edx/edx-platform/blob/master/docs/guides/studio_oauth.rst +./manage.py lms manage_user cms cms@openedx --unusable-password +./manage.py lms create_dot_application \ + --grant-type authorization-code \ + --redirect-uris "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ CMS_HOST }}/complete/edx-oauth2/" \ + --client-id {{ CMS_OAUTH2_KEY_SSO }} \ + --client-secret {{ CMS_OAUTH2_SECRET }} \ + --scopes user_id \ + --skip-authorization \ + --update cms-sso cms +./manage.py lms create_dot_application \ + --grant-type authorization-code \ + --redirect-uris "http://{{ CMS_HOST }}:8001/complete/edx-oauth2/" \ + --client-id {{ CMS_OAUTH2_KEY_SSO_DEV }} \ + --client-secret {{ CMS_OAUTH2_SECRET }} \ + --scopes user_id \ + --skip-authorization \ + --update cms-sso-dev cms + + # Fix incorrect uploaded file path if [ -d /openedx/data/uploads/ ]; then if [ -n "$(ls -A /openedx/data/uploads/)" ]; then