From 2a029391045cecd27da376ca77f91ebdc0ebc384 Mon Sep 17 00:00:00 2001 From: yatesdr Date: Wed, 23 Oct 2024 14:46:02 -0400 Subject: [PATCH 01/10] Enable cross-domain embedding by list of referers. Add settings key GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS, and exclude embed? and /media/ from auth requirements if present. --- cms/settings.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cms/settings.py b/cms/settings.py index 8a0410c28..798b776e1 100644 --- a/cms/settings.py +++ b/cms/settings.py @@ -134,6 +134,7 @@ # Empty list disables. ALLOWED_DOMAINS_FOR_USER_REGISTRATION = [] + # django rest settings REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ( @@ -459,6 +460,12 @@ # it is placed here so it can be overrided on local_settings.py GLOBAL_LOGIN_REQUIRED = False +# When GLOBAL_LOGIN_REQUIRED is True, allow certain domains to embed the content. +# This is useful when you want to serve content on your servers but not allow general public access. +# You also must properly configure CORS origins for this to work. +# Should be a comma-separated list of domains. +GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS = [] # ['my-refering-domain.com', 'cdn.my-site.com'] + # TODO: separate settings on production/development more properly, for now # this should be ok CELERY_TASK_ALWAYS_EAGER = False @@ -497,6 +504,11 @@ r'/accounts/confirm-email/.*/$', r'/api/v[0-9]+/', ] + if (GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS): + LOGIN_REQUIRED_IGNORE_PATHS += [ + r'^/embed.*', #r'/embed\?m=.*$', + r'^/media/.*' + ] # if True, only show original, don't perform any action on videos DO_NOT_TRANSCODE_VIDEO = False @@ -538,4 +550,4 @@ SPRITE_NUM_SECS = 10 # number of seconds for sprite image. # If you plan to change this, you must also follow the instructions on admin_docs.md -# to change the equivalent value in ./frontend/src/static/js/components/media-viewer/VideoViewer/index.js and then re-build frontend \ No newline at end of file +# to change the equivalent value in ./frontend/src/static/js/components/media-viewer/VideoViewer/index.js and then re-build frontend From 3a7d3bb2897a36955b96814c02df83809db49b42 Mon Sep 17 00:00:00 2001 From: yatesdr Date: Wed, 23 Oct 2024 14:47:44 -0400 Subject: [PATCH 02/10] Check request referer against allowed embedding referers. --- files/views.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/files/views.py b/files/views.py index b37e98b16..b3e652435 100644 --- a/files/views.py +++ b/files/views.py @@ -50,6 +50,7 @@ ) from .stop_words import STOP_WORDS from .tasks import save_user_action +from urllib.parse import urlparse VALID_USER_ACTIONS = [action for action, name in USER_MEDIA_ACTIONS] @@ -207,6 +208,21 @@ def embed_media(request): if not media: return HttpResponseRedirect("/") + try: + if (settings.GLOBAL_LOGIN_REQUIRED and + hasattr(settings,'GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS') and + settings.GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS): + + if request.META.get('HTTP_REFERER'): + referring_domain = urlparse(request.META['HTTP_REFERER']).hostname + if (not referring_domain) or (referring_domain not in settings.GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS): + raise PermissionDenied("HTTP referer not permitted.") + else: + raise PermissionDenied("HTTP referer not set.") + + except PermissionDenied: + return render(request,"cms/embed-403.html") + context = {} context["media"] = friendly_token return render(request, "cms/embed.html", context) From 06502674f43f5ae4a05a8e7bc9312d6882b57c44 Mon Sep 17 00:00:00 2001 From: yatesdr Date: Wed, 23 Oct 2024 14:49:41 -0400 Subject: [PATCH 03/10] Create embed-403.html Dummy player for embedding on rich editors, to permit layout evaluation if the referer is not set until save. --- templates/cms/embed-403.html | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 templates/cms/embed-403.html diff --git a/templates/cms/embed-403.html b/templates/cms/embed-403.html new file mode 100644 index 000000000..2f576bba8 --- /dev/null +++ b/templates/cms/embed-403.html @@ -0,0 +1,39 @@ + + +
+
+

403 - Forbidden

+

+ + + +

+

Embedded media is not permitted from this referer.

+

If this is a preview, please save your content to see the video.

+
+
+ From effa1dba7447832cd95aed19ac5837b86bc483fb Mon Sep 17 00:00:00 2001 From: yatesdr Date: Wed, 23 Oct 2024 14:53:12 -0400 Subject: [PATCH 04/10] Doc for GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS Add note for usage. --- docs/admins_docs.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/admins_docs.md b/docs/admins_docs.md index 2495f8a8f..d48d0c4b0 100644 --- a/docs/admins_docs.md +++ b/docs/admins_docs.md @@ -481,6 +481,7 @@ ADMINS_NOTIFICATIONS = { - Make the portal workflow public, but at the same time set `GLOBAL_LOGIN_REQUIRED = True` so that only logged in users can see content. - You can either set `REGISTER_ALLOWED = False` if you want to add members yourself or checkout options on "django-allauth settings" that affects registration in `cms/settings.py`. Eg set the portal invite only, or set email confirmation as mandatory, so that you control who registers. +- If you wish to have private media management, but allow trusted referers to embed the media for viewing, you can over-ride the global login requirement for embedded media on some sites by setting `GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS = ['your-approved-domain.com', ...]`. This has the effect of checking the HTTP_REFERER domain in the request against the list of approved referers, and allowing the video to stream if there is a match. The videos you wish to embed must be set to unlisted or public in the media management, and the referring site or proxy must properly set the HTTP_REFERER header to an approved domain. You may also need to properly set or update CORS_ALLOWED_ORIGINS headers depending on your specific configuration and cross-site requirements. Depending on your use-case, you may need to patch the default player auto-play and share links to get a suitable plain-jane video feed. ### 5.24 Enable the sitemap From a7550ea6631b8c6048ba3a69749e6d764a2b4cca Mon Sep 17 00:00:00 2001 From: yatesdr Date: Wed, 23 Oct 2024 15:57:53 -0400 Subject: [PATCH 05/10] Fix lint --- cms/settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cms/settings.py b/cms/settings.py index 798b776e1..d903f2780 100644 --- a/cms/settings.py +++ b/cms/settings.py @@ -132,7 +132,7 @@ # Comma separated list of domains: ["organization.com", "private.organization.com", "org2.com"] # Empty list disables. -ALLOWED_DOMAINS_FOR_USER_REGISTRATION = [] +ALLOWED_DOMAINS_FOR_USER_REGISTRATION = [] # django rest settings @@ -464,7 +464,7 @@ # This is useful when you want to serve content on your servers but not allow general public access. # You also must properly configure CORS origins for this to work. # Should be a comma-separated list of domains. -GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS = [] # ['my-refering-domain.com', 'cdn.my-site.com'] +GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS = [] # ['my-refering-domain.com', 'cdn.my-site.com'] # TODO: separate settings on production/development more properly, for now # this should be ok @@ -506,7 +506,7 @@ ] if (GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS): LOGIN_REQUIRED_IGNORE_PATHS += [ - r'^/embed.*', #r'/embed\?m=.*$', + r'^/embed.*', # r'/embed\?m=.*$', r'^/media/.*' ] From f3ea941472edac3a88ff39c63a7185949e531842 Mon Sep 17 00:00:00 2001 From: yatesdr Date: Wed, 23 Oct 2024 15:59:08 -0400 Subject: [PATCH 06/10] Fix linter --- files/frontend_translations/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/files/frontend_translations/__init__.py b/files/frontend_translations/__init__.py index 246a0d5af..95c49f645 100644 --- a/files/frontend_translations/__init__.py +++ b/files/frontend_translations/__init__.py @@ -32,7 +32,6 @@ def check_language_code(language_code): replacement_strings[language_code] = tr_module.replacement_strings - def get_translation(language_code): # get list of translations per language if not check_language_code(language_code): From 73d229a12f0fa13c55fd2ce9cb5bcf7d4ab4c13b Mon Sep 17 00:00:00 2001 From: yatesdr Date: Wed, 23 Oct 2024 16:00:41 -0400 Subject: [PATCH 07/10] Fix linter --- files/management/commands/process_translations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/management/commands/process_translations.py b/files/management/commands/process_translations.py index b90bf78da..b923efe5e 100644 --- a/files/management/commands/process_translations.py +++ b/files/management/commands/process_translations.py @@ -44,12 +44,12 @@ def process_translation_files(self, translations_dir): with open(file_path, 'w') as f: f.write("translation_strings = {\n") for key, value in translation_strings_wip.items(): - f.write(f' "{key}": "{value}",\n') + f.write(f' "{key}": "{value}", \n') f.write("}\n\n") f.write("replacement_strings = {\n") for key, value in replacement_strings_wip.items(): - f.write(f' "{key}": "{value}",\n') + f.write(f' "{key}": "{value}", \n') f.write("}\n") self.stdout.write(self.style.SUCCESS(f'Processed {file}')) From 284a3fad2351ea7263e68d3dfec7a887091f8cd4 Mon Sep 17 00:00:00 2001 From: yatesdr Date: Wed, 23 Oct 2024 16:03:48 -0400 Subject: [PATCH 08/10] Fix linter --- files/views.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/files/views.py b/files/views.py index b3e652435..1ac08dc3d 100644 --- a/files/views.py +++ b/files/views.py @@ -209,10 +209,7 @@ def embed_media(request): return HttpResponseRedirect("/") try: - if (settings.GLOBAL_LOGIN_REQUIRED and - hasattr(settings,'GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS') and - settings.GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS): - + if (settings.GLOBAL_LOGIN_REQUIRED and hasattr(settings, 'GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS') and settings.GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS): if request.META.get('HTTP_REFERER'): referring_domain = urlparse(request.META['HTTP_REFERER']).hostname if (not referring_domain) or (referring_domain not in settings.GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS): @@ -221,7 +218,7 @@ def embed_media(request): raise PermissionDenied("HTTP referer not set.") except PermissionDenied: - return render(request,"cms/embed-403.html") + return render(request, "cms/embed-403.html") context = {} context["media"] = friendly_token From d4381440a960050deec51fa93e55dff490907bf6 Mon Sep 17 00:00:00 2001 From: yatesdr Date: Wed, 23 Oct 2024 16:05:10 -0400 Subject: [PATCH 09/10] Fix linter --- files/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/views.py b/files/views.py index 1ac08dc3d..7333cbeab 100644 --- a/files/views.py +++ b/files/views.py @@ -219,7 +219,7 @@ def embed_media(request): except PermissionDenied: return render(request, "cms/embed-403.html") - + context = {} context["media"] = friendly_token return render(request, "cms/embed.html", context) From 6c7dacf77a623bdba9cf7f1bc318ecc485ee58ac Mon Sep 17 00:00:00 2001 From: yatesdr Date: Wed, 23 Oct 2024 16:08:51 -0400 Subject: [PATCH 10/10] Fix linter --- users/adapter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/users/adapter.py b/users/adapter.py index b3cc9e010..dd75f0f33 100644 --- a/users/adapter.py +++ b/users/adapter.py @@ -10,10 +10,10 @@ def get_email_confirmation_url_stub(self, request, emailconfirmation): return settings.SSL_FRONTEND_HOST + url def clean_email(self, email): - if hasattr(settings,"ALLOWED_DOMAINS_FOR_USER_REGISTRATION") and settings.ALLOWED_DOMAINS_FOR_USER_REGISTRATION: + if hasattr(settings, "ALLOWED_DOMAINS_FOR_USER_REGISTRATION") and settings.ALLOWED_DOMAINS_FOR_USER_REGISTRATION: if email.split("@")[1] not in settings.ALLOWED_DOMAINS_FOR_USER_REGISTRATION: raise ValidationError("Domain is not in the permitted list") - + if email.split("@")[1] in settings.RESTRICTED_DOMAINS_FOR_USER_REGISTRATION: raise ValidationError("Domain is restricted from registering") return email