Enhance ValidatingEmbedBlock with strict host validation and update ArticlePage fields in migration

This commit is contained in:
Warren Chen 2025-10-29 18:41:16 +09:00
parent ee6eb0db17
commit 7c9fe7f6f9
2 changed files with 64 additions and 7 deletions

View File

@ -2,6 +2,8 @@ from django.core.exceptions import ValidationError
from wagtail.embeds.blocks import EmbedBlock from wagtail.embeds.blocks import EmbedBlock
from wagtail.embeds import embeds as wagtail_embeds from wagtail.embeds import embeds as wagtail_embeds
from wagtail import blocks from wagtail import blocks
from urllib.parse import urlparse
from django.conf import settings
class ValidatingEmbedBlock(EmbedBlock): class ValidatingEmbedBlock(EmbedBlock):
@ -13,12 +15,31 @@ class ValidatingEmbedBlock(EmbedBlock):
def clean(self, value): def clean(self, value):
value = super().clean(value) value = super().clean(value)
if value: if value:
# 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: try:
# Attempt to resolve and cache embed; will raise on failure # Attempt to resolve and cache embed; will raise on failure
wagtail_embeds.get_embed(value) wagtail_embeds.get_embed(url_str)
except Exception: except Exception:
raise ValidationError( raise ValidationError(
"嵌入連結無法驗證,請確認為公開且可嵌入的 URL。" "嵌入連結無法驗證(需公開且有權杖)。請確認 URL 與設定"
) )
return value return value

View File

@ -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='在推薦區塊顯示'),
),
]