From 3a8cacc847fb75d409fa8ccd0ed362a4d7ea0d35 Mon Sep 17 00:00:00 2001 From: Markos Gogoulos Date: Thu, 13 Feb 2025 13:41:53 +0200 Subject: [PATCH] feat: Bulk fixes (#1195) remove ckeditor - not in use add more strict default password validators set Django admin as configurable URL add nginx HSTS and CSP headers enable moving from private to unlisted in the PORTAL_WORKFLOW private on default comments listing, show only comments for public media in case of a private media, dont expose any unneeded metadata --- cms/dev_settings.py | 1 - cms/settings.py | 45 +++------ cms/urls.py | 3 +- deploy/docker/nginx_http_only.conf | 20 ++++ deploy/local_install/mediacms.io | 22 +++- files/context_processors.py | 2 + files/methods.py | 6 +- files/views.py | 8 +- requirements.txt | 1 - templates/cms/edit_media.html | 2 - templates/cms/media.html | 157 +++++++++++++++-------------- templates/cms/user_edit.html | 2 - templates/config/core/url.html | 2 +- 13 files changed, 151 insertions(+), 120 deletions(-) diff --git a/cms/dev_settings.py b/cms/dev_settings.py index 939f8755..45b7db9c 100644 --- a/cms/dev_settings.py +++ b/cms/dev_settings.py @@ -26,7 +26,6 @@ INSTALLED_APPS = [ "crispy_bootstrap5", 'uploader.apps.UploaderConfig', 'djcelery_email', - 'ckeditor', 'drf_yasg', 'corsheaders', ] diff --git a/cms/settings.py b/cms/settings.py index f08b9276..69d8cf05 100644 --- a/cms/settings.py +++ b/cms/settings.py @@ -232,7 +232,7 @@ CANNOT_ADD_MEDIA_MESSAGE = "" MP4HLS_COMMAND = "/home/mediacms.io/mediacms/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/bin/mp4hls" # highly experimental, related with remote workers -ADMIN_TOKEN = "c2b8e1838b6128asd333ddc5e24" +ADMIN_TOKEN = "" # this is used by remote workers to push # encodings once they are done # USE_BASIC_HTTP = True @@ -247,35 +247,6 @@ ADMIN_TOKEN = "c2b8e1838b6128asd333ddc5e24" # uncomment the two lines related to htpasswd -CKEDITOR_CONFIGS = { - "default": { - "toolbar": "Custom", - "width": "100%", - "toolbar_Custom": [ - ["Styles"], - ["Format"], - ["Bold", "Italic", "Underline"], - ["HorizontalRule"], - [ - "NumberedList", - "BulletedList", - "-", - "Outdent", - "Indent", - "-", - "JustifyLeft", - "JustifyCenter", - "JustifyRight", - "JustifyBlock", - ], - ["Link", "Unlink"], - ["Image"], - ["RemoveFormat", "Source"], - ], - } -} - - AUTH_USER_MODEL = "users.User" LOGIN_REDIRECT_URL = "/" @@ -307,7 +278,6 @@ INSTALLED_APPS = [ "crispy_bootstrap5", "uploader.apps.UploaderConfig", "djcelery_email", - "ckeditor", "drf_yasg", ] @@ -349,11 +319,15 @@ WSGI_APPLICATION = "cms.wsgi.application" AUTH_PASSWORD_VALIDATORS = [ { "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + "OPTIONS": { + "user_attributes": ("username", "email", "first_name", "last_name"), + "max_similarity": 0.7, + }, }, { "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", "OPTIONS": { - "min_length": 5, + "min_length": 7, }, }, { @@ -547,3 +521,10 @@ CALCULATE_MD5SUM = False CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" CRISPY_TEMPLATE_PACK = "bootstrap5" + +# allow option to override the default admin url +# keep the trailing slash +DJANGO_ADMIN_URL = "admin/" + +# CSRF_COOKIE_SECURE = True +# SESSION_COOKIE_SECURE = True diff --git a/cms/urls.py b/cms/urls.py index 28382b32..ea8f92ac 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -1,4 +1,5 @@ import debug_toolbar +from django.conf import settings from django.conf.urls import include from django.contrib import admin from django.urls import path, re_path @@ -25,7 +26,7 @@ urlpatterns = [ re_path(r"^", include("users.urls")), re_path(r"^accounts/", include("allauth.urls")), re_path(r"^api-auth/", include("rest_framework.urls")), - path("admin/", admin.site.urls), + path(settings.DJANGO_ADMIN_URL, admin.site.urls), re_path(r'^swagger(?P\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'), re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), path('docs/api/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), diff --git a/deploy/docker/nginx_http_only.conf b/deploy/docker/nginx_http_only.conf index 44294c5f..a4647763 100644 --- a/deploy/docker/nginx_http_only.conf +++ b/deploy/docker/nginx_http_only.conf @@ -6,6 +6,26 @@ server { error_log /var/log/nginx/mediacms.io.error.log warn; + # HSTS header + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + # CSP header + add_header Content-Security-Policy " + default-src 'self'; + script-src 'self'; + style-src 'self'; + img-src 'self' data: blob:; + media-src 'self' blob:; + frame-src 'self'; + font-src 'self'; + connect-src 'self'; + object-src 'none'; + frame-ancestors 'self'; + form-action 'self'; + base-uri 'self'; + upgrade-insecure-requests; + " always; + location /static { alias /home/mediacms.io/mediacms/static ; } diff --git a/deploy/local_install/mediacms.io b/deploy/local_install/mediacms.io index 1d929c5d..3f978862 100644 --- a/deploy/local_install/mediacms.io +++ b/deploy/local_install/mediacms.io @@ -17,6 +17,26 @@ server { # rewrite ^/(.*)$ https://localhost/$1 permanent; # } + # HSTS header + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + # CSP header + add_header Content-Security-Policy " + default-src 'self'; + script-src 'self'; + style-src 'self'; + img-src 'self' data: blob:; + media-src 'self' blob:; + frame-src 'self'; + font-src 'self'; + connect-src 'self'; + object-src 'none'; + frame-ancestors 'self'; + form-action 'self'; + base-uri 'self'; + upgrade-insecure-requests; + " always; + location /static { alias /home/mediacms.io/mediacms/static ; } @@ -49,7 +69,7 @@ server { ssl_dhparam /etc/nginx/dhparams/dhparams.pem; ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE - ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_ecdh_curve secp521r1:secp384r1; ssl_prefer_server_ciphers on; diff --git a/files/context_processors.py b/files/context_processors.py index fe45c983..1cc2cc61 100644 --- a/files/context_processors.py +++ b/files/context_processors.py @@ -34,5 +34,7 @@ def stuff(request): ret["RSS_URL"] = "/rss" ret["TRANSLATION"] = get_translation(request.LANGUAGE_CODE) ret["REPLACEMENTS"] = get_translation_strings(request.LANGUAGE_CODE) + if request.user.is_superuser: + ret["DJANGO_ADMIN_URL"] = settings.DJANGO_ADMIN_URL return ret diff --git a/files/methods.py b/files/methods.py index 2ea207f5..2fdcaa20 100644 --- a/files/methods.py +++ b/files/methods.py @@ -119,12 +119,16 @@ def get_next_state(user, current_state, next_state): if next_state not in ["public", "private", "unlisted"]: next_state = settings.PORTAL_WORKFLOW # get default state + if is_mediacms_editor(user): # allow any transition return next_state if settings.PORTAL_WORKFLOW == "private": - next_state = "private" + if next_state in ["private", "unlisted"]: + next_state = next_state + else: + next_state = current_state if settings.PORTAL_WORKFLOW == "unlisted": # don't allow to make media public in this case diff --git a/files/views.py b/files/views.py index d96da9a5..5e22e2e8 100644 --- a/files/views.py +++ b/files/views.py @@ -675,6 +675,9 @@ class MediaActions(APIView): def get(self, request, friendly_token, format=None): # show date and reason for each time media was reported media = self.get_object(friendly_token) + if not (request.user == media.user or is_mediacms_editor(request.user) or is_mediacms_manager(request.user)): + return Response({"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST) + if isinstance(media, Response): return media @@ -928,9 +931,10 @@ class PlaylistDetail(APIView): serializer = PlaylistDetailSerializer(playlist, context={"request": request}) - playlist_media = PlaylistMedia.objects.filter(playlist=playlist).prefetch_related("media__user") + playlist_media = PlaylistMedia.objects.filter(playlist=playlist, media__state="public").prefetch_related("media__user") playlist_media = [c.media for c in playlist_media] + playlist_media_serializer = MediaSerializer(playlist_media, many=True, context={"request": request}) ret = serializer.data ret["playlist_media"] = playlist_media_serializer.data @@ -1195,7 +1199,7 @@ class CommentList(APIView): def get(self, request, format=None): pagination_class = api_settings.DEFAULT_PAGINATION_CLASS paginator = pagination_class() - comments = Comment.objects.filter() + comments = Comment.objects.filter(media__state="public").order_by("-add_date") comments = comments.prefetch_related("user") comments = comments.prefetch_related("media") params = self.request.query_params diff --git a/requirements.txt b/requirements.txt index 61674598..44526f4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,6 @@ crispy-bootstrap5==2024.10 requests==2.32.3 django-celery-email==3.0.0 m3u8==6.0.0 -django-ckeditor==6.7.2 django-debug-toolbar==5.0.1 django-login-required-middleware==0.9.0 pre-commit==4.1.0 diff --git a/templates/cms/edit_media.html b/templates/cms/edit_media.html index 55d6976b..13324f6c 100644 --- a/templates/cms/edit_media.html +++ b/templates/cms/edit_media.html @@ -7,8 +7,6 @@ {% block headermeta %}{% endblock headermeta %} {% block innercontent %} - -
diff --git a/templates/cms/media.html b/templates/cms/media.html index 2999f19d..7953aca6 100644 --- a/templates/cms/media.html +++ b/templates/cms/media.html @@ -20,100 +20,105 @@ {% endif %} -{% if media_object.media_type == "video" %} +{% if media_object.state != "private" %} - + {% if media_object.media_type == "video" %} - + - + -{% elif media_object.media_type == "audio" %} + {% elif media_object.media_type == "audio" %} - + - + - + -{% elif media_object.media_type == "image" %} + {% elif media_object.media_type == "image" %} - + - + - + + + {% else %} + + + + + + {% endif %} {% else %} - - - - {% endif %} - {% endblock headermeta %} {% block topimports %} diff --git a/templates/cms/user_edit.html b/templates/cms/user_edit.html index 49556ad2..51e76ed0 100644 --- a/templates/cms/user_edit.html +++ b/templates/cms/user_edit.html @@ -3,8 +3,6 @@ {% block headtitle %}Edit profile - {% endblock headtitle %} {% block innercontent %} - -
diff --git a/templates/config/core/url.html b/templates/config/core/url.html index a6ca6d80..9be88796 100644 --- a/templates/config/core/url.html +++ b/templates/config/core/url.html @@ -22,7 +22,7 @@ MediaCMS.url = { editChannel: "{{user.default_channel_edit_url}}", changePassword: "/accounts/password/change/", /* Administration pages */ - {% if IS_MEDIACMS_ADMIN %}admin: '/admin',{% endif %} + {% if IS_MEDIACMS_ADMIN %}admin: '/{{DJANGO_ADMIN_URL}}',{% endif %} /* Management pages */ {% if IS_MEDIACMS_EDITOR %}manageMedia: "/manage/media",{% endif %} {% if IS_MEDIACMS_MANAGER %}manageUsers: "/manage/users",{% endif %}