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
This commit is contained in:
Markos Gogoulos
2025-02-13 13:41:53 +02:00
committed by GitHub
parent 5402ee7bc5
commit 3a8cacc847
13 changed files with 151 additions and 120 deletions

View File

@ -26,7 +26,6 @@ INSTALLED_APPS = [
"crispy_bootstrap5",
'uploader.apps.UploaderConfig',
'djcelery_email',
'ckeditor',
'drf_yasg',
'corsheaders',
]

View File

@ -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

View File

@ -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<format>\.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'),

View File

@ -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 ;
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -7,8 +7,6 @@
{% block headermeta %}{% endblock headermeta %}
{% block innercontent %}
<script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
<script type="text/javascript" src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>
<div class="user-action-form-wrap">
<div class="user-action-form-inner">

View File

@ -20,100 +20,105 @@
<meta property="og:type" content="website">
{% endif %}
{% if media_object.media_type == "video" %}
{% if media_object.state != "private" %}
<meta property="og:image" content="{{FRONTEND_HOST}}{{media_object.poster_url}}">
{% if media_object.media_type == "video" %}
<meta name="twitter:card" content="summary_large_image">
<meta property="og:image" content="{{FRONTEND_HOST}}{{media_object.poster_url}}">
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "VideoObject",
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
"thumbnailUrl": [
"{{FRONTEND_HOST}}{{media_object.poster_url}}"
],
"uploadDate": "{{media_object.add_date}}",
"dateModified": "{{media_object.edit_date}}",
"embedUrl": "{{FRONTEND_HOST}}/embed?m={{media}}",
"duration": "T{{media_object.duration}}S",
"potentialAction": {
"@type": "ViewAction",
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
<meta name="twitter:card" content="summary_large_image">
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "VideoObject",
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
"thumbnailUrl": [
"{{FRONTEND_HOST}}{{media_object.poster_url}}"
],
"uploadDate": "{{media_object.add_date}}",
"dateModified": "{{media_object.edit_date}}",
"embedUrl": "{{FRONTEND_HOST}}/embed?m={{media}}",
"duration": "T{{media_object.duration}}S",
"potentialAction": {
"@type": "ViewAction",
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
}
}
}
</script>
</script>
{% elif media_object.media_type == "audio" %}
{% elif media_object.media_type == "audio" %}
<meta property="og:image" content="{{FRONTEND_HOST}}{{media_object.poster_url}}">
<meta property="og:image" content="{{FRONTEND_HOST}}{{media_object.poster_url}}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:card" content="summary_large_image">
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "AudioObject",
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
"uploadDate": "{{media_object.add_date}}",
"dateModified": "{{media_object.edit_date}}",
"duration": "T{{media_object.duration}}S",
"potentialAction": {
"@type": "ViewAction",
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "AudioObject",
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
"uploadDate": "{{media_object.add_date}}",
"dateModified": "{{media_object.edit_date}}",
"duration": "T{{media_object.duration}}S",
"potentialAction": {
"@type": "ViewAction",
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
}
}
}
</script>
</script>
{% elif media_object.media_type == "image" %}
{% elif media_object.media_type == "image" %}
<meta property="og:image" content="{{FRONTEND_HOST}}{{media_object.original_media_url}}">
<meta property="og:image" content="{{FRONTEND_HOST}}{{media_object.original_media_url}}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:card" content="summary_large_image">
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "ImageObject",
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
"uploadDate": "{{media_object.add_date}}",
"dateModified": "{{media_object.edit_date}}",
"potentialAction": {
"@type": "ViewAction",
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "ImageObject",
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
"uploadDate": "{{media_object.add_date}}",
"dateModified": "{{media_object.edit_date}}",
"potentialAction": {
"@type": "ViewAction",
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
}
}
}
</script>
</script>
{% else %}
<meta name="twitter:card" content="summary">
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "MediaObject",
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
"uploadDate": "{{media_object.add_date}}",
"dateModified": "{{media_object.edit_date}}",
"potentialAction": {
"@type": "ViewAction",
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
}
}
</script>
{% endif %}
{% else %}
<meta name="twitter:card" content="summary">
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "MediaObject",
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
"uploadDate": "{{media_object.add_date}}",
"dateModified": "{{media_object.edit_date}}",
"potentialAction": {
"@type": "ViewAction",
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
}
}
</script>
{% endif %}
{% endblock headermeta %}
{% block topimports %}

View File

@ -3,8 +3,6 @@
{% block headtitle %}Edit profile - {% endblock headtitle %}
{% block innercontent %}
<script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
<script type="text/javascript" src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>
<div class="user-action-form-wrap">
<div class="user-action-form-inner">

View File

@ -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 %}