From 7c9fe7f6f917a3952b8dbd56aa58ed77467ac814 Mon Sep 17 00:00:00 2001 From: Warren Chen Date: Wed, 29 Oct 2025 18:41:16 +0900 Subject: [PATCH] Enhance ValidatingEmbedBlock with strict host validation and update ArticlePage fields in migration --- innovedus_cms/home/blocks.py | 35 ++++++++++++++---- ...alter_articlepage_banner_image_and_more.py | 36 +++++++++++++++++++ 2 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 innovedus_cms/home/migrations/0008_alter_articlepage_banner_image_and_more.py diff --git a/innovedus_cms/home/blocks.py b/innovedus_cms/home/blocks.py index 9474647..6a0071d 100644 --- a/innovedus_cms/home/blocks.py +++ b/innovedus_cms/home/blocks.py @@ -2,6 +2,8 @@ from django.core.exceptions import ValidationError from wagtail.embeds.blocks import EmbedBlock from wagtail.embeds import embeds as wagtail_embeds from wagtail import blocks +from urllib.parse import urlparse +from django.conf import settings class ValidatingEmbedBlock(EmbedBlock): @@ -13,13 +15,32 @@ class ValidatingEmbedBlock(EmbedBlock): def clean(self, value): value = super().clean(value) if value: - try: - # Attempt to resolve and cache embed; will raise on failure - wagtail_embeds.get_embed(value) - except Exception: - raise ValidationError( - "嵌入連結無法驗證,請確認為公開且可嵌入的 URL。" - ) + # Inherit base validation (already run via super().clean), and add + # optional network validation for selected providers. + url_str = value if isinstance(value, str) else getattr(value, "url", None) + if not url_str: + return value + host = (urlparse(url_str).hostname or "").lower() + + strict_hosts = getattr( + settings, + "EMBED_STRICT_HOSTS", + ("instagram.com", "facebook.com"), + ) + validate_all = getattr(settings, "EMBED_STRICT_VALIDATE_ALL", False) + + def _matches(h: str) -> bool: + return host == h or host.endswith("." + h) + + must_validate = bool(validate_all or any(_matches(h) for h in strict_hosts)) + if must_validate: + try: + # Attempt to resolve and cache embed; will raise on failure + wagtail_embeds.get_embed(url_str) + except Exception: + raise ValidationError( + "嵌入連結無法驗證(需公開且有權杖)。請確認 URL 與設定。" + ) return value diff --git a/innovedus_cms/home/migrations/0008_alter_articlepage_banner_image_and_more.py b/innovedus_cms/home/migrations/0008_alter_articlepage_banner_image_and_more.py new file mode 100644 index 0000000..dc0048a --- /dev/null +++ b/innovedus_cms/home/migrations/0008_alter_articlepage_banner_image_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 5.2.7 on 2025-10-29 09:33 + +import django.db.models.deletion +import wagtail.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('home', '0007_articlepage_banner_image_alter_articlepage_body'), + ('wagtailimages', '0027_image_description'), + ] + + operations = [ + migrations.AlterField( + model_name='articlepage', + name='banner_image', + field=models.ForeignKey(blank=True, help_text='文章內文橫幅圖片', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image'), + ), + migrations.AlterField( + model_name='articlepage', + name='body', + field=wagtail.fields.StreamField([('heading', 0), ('paragraph', 1), ('image', 2), ('embed', 3), ('hr', 4), ('html', 5)], block_lookup={0: ('home.blocks.H2HeadingBlock', (), {'form_classname': 'full title'}), 1: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link']}), 2: ('wagtail.images.blocks.ImageChooserBlock', (), {}), 3: ('home.blocks.ValidatingEmbedBlock', (), {}), 4: ('home.blocks.HorizontalRuleBlock', (), {}), 5: ('wagtail.blocks.RawHTMLBlock', (), {'help_text': '僅限信任來源的 blockquote/iframe 原始碼'})}), + ), + migrations.AlterField( + model_name='articlepage', + name='cover_image', + field=models.ForeignKey(blank=True, help_text='列表封面圖', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image'), + ), + migrations.AlterField( + model_name='articlepage', + name='recommended', + field=models.BooleanField(default=False, help_text='在推薦區塊顯示'), + ), + ]