Skip to content

Commit

Permalink
Replaced single 'override tag' setting with explicit allow, deny tags.
Browse files Browse the repository at this point in the history
Previously used a single setting to reverse the effects of allow/deny
lists, which would require users to know exactly what the current
settings were in order to know how the override tag would affect each
link.

Now separate tags `settings.WEBMENTIONS_DOMAINS_OUTGOING_TAG_ALLOW`,
`settings.WEBMENTIONS_DOMAINS_OUTGOING_TAG_DENY` make the effect
explicit at point of use.
  • Loading branch information
beatonma committed Feb 10, 2024
1 parent 0913463 commit d2f1790
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 102 deletions.
17 changes: 10 additions & 7 deletions CHANGELOG.md
Expand Up @@ -3,7 +3,7 @@
> [!NOTE]
> Release notes are also available on the [wiki](https://github.com/beatonma/django-wm/wiki/Releases).
## 4.1 (2024-02-09)
## 4.1.0 (2024-02-10)

> [!WARNING]
> `python manage.py migrate` required for new fields.
Expand All @@ -14,12 +14,15 @@

- New `objects` manager for Webmention with some common filters and actions.

- New [settings](https://github.com/beatonma/django-wm/wiki/Settings) for allowing or disabling webmentions to/from a set of domains:
- <a href="https://github.com/beatonma/django-wm/wiki/Settings#WEBMENTIONS_DOMAINS_INCOMING_ALLOW">`WEBMENTIONS_DOMAINS_INCOMING_ALLOW: Iterable[str] = None`</a>
- <a href="https://github.com/beatonma/django-wm/wiki/Settings#WEBMENTIONS_DOMAINS_INCOMING_DENY">`WEBMENTIONS_DOMAINS_INCOMING_DENY: Iterable[str] = None`</a>
- <a href="https://github.com/beatonma/django-wm/wiki/Settings#WEBMENTIONS_DOMAINS_OUTGOING_ALLOW">`WEBMENTIONS_DOMAINS_OUTGOING_ALLOW: Iterable[str] = None`</a>
- <a href="https://github.com/beatonma/django-wm/wiki/Settings#WEBMENTIONS_DOMAINS_OUTGOING_DENY">`WEBMENTIONS_DOMAINS_OUTGOING_DENY: Iterable[str] = None`</a>
- <a href="https://github.com/beatonma/django-wm/wiki/Settings#WEBMENTIONS_DOMAINS_OUTGOING_OVERRIDE_TAG">`WEBMENTIONS_DOMAINS_OUTGOING_OVERRIDE_TAG: str = None`</a>
- New settings for allowing or disabling webmentions to/from a set of domains:
- <a href="Settings#WEBMENTIONS_DOMAINS_INCOMING_ALLOW">`WEBMENTIONS_DOMAINS_INCOMING_ALLOW: Iterable[str] = None`</a>
- <a href="Settings#WEBMENTIONS_DOMAINS_INCOMING_DENY">`WEBMENTIONS_DOMAINS_INCOMING_DENY: Iterable[str] = None`</a>
- <a href="Settings#WEBMENTIONS_DOMAINS_OUTGOING_ALLOW">`WEBMENTIONS_DOMAINS_OUTGOING_ALLOW: Iterable[str] = None`</a>
- <a href="Settings#WEBMENTIONS_DOMAINS_OUTGOING_DENY">`WEBMENTIONS_DOMAINS_OUTGOING_DENY: Iterable[str] = None`</a>

- New settings for tags that can be added to your HTML links to allow/disable sending webmentions for just that specific link. (Overrides above allow/deny lists)
- <a href="Settings#WEBMENTIONS_DOMAINS_OUTGOING_TAG_ALLOW">`WEBMENTIONS_DOMAINS_OUTGOING_TAG_ALLOW: str = None`</a>
- <a href="Settings#WEBMENTIONS_DOMAINS_OUTGOING_TAG_DENY">`WEBMENTIONS_DOMAINS_OUTGOING_TAG_DENY: str = None`</a>


## 4.0.4 (2024-02-02)
Expand Down
2 changes: 1 addition & 1 deletion mentions/__init__.py
@@ -1,2 +1,2 @@
__version__ = "4.1"
__version__ = "4.1.0"
__url__ = "https://github.com/beatonma/django-wm/"
8 changes: 2 additions & 6 deletions mentions/config.py
Expand Up @@ -63,7 +63,6 @@ def accept_domain_incoming(

def accept_domain_outgoing(
url: str,
inline_override: bool,
allow_self_mention: bool,
domains_allow: Optional[Set[str]],
domains_deny: Optional[Set[str]],
Expand All @@ -72,9 +71,6 @@ def accept_domain_outgoing(
Args:
url: The URL to check.
inline_override: Provide True if the source of `url` is annotated with
value of `options.outgoing_domains_override_attr()`,
otherwise False.
allow_self_mention: Current value of `options.allow_self_mentions()`.
domains_allow: Current value of `options.outgoing_domains_allow()`.
domains_deny: Current value of `options.outgoing_domains_deny()`.
Expand All @@ -93,10 +89,10 @@ def accept_domain_outgoing(
)

if domains_deny:
return _domain_in_set(domain, domains_deny) is inline_override
return not _domain_in_set(domain, domains_deny)

if domains_allow:
return _domain_in_set(domain, domains_allow) is not inline_override
return _domain_in_set(domain, domains_allow)

return True

Expand Down
29 changes: 20 additions & 9 deletions mentions/options.py
Expand Up @@ -34,7 +34,8 @@
"incoming_domains_deny",
"outgoing_domains_deny",
"outgoing_domains_allow",
"outgoing_domains_override_tag",
"outgoing_domains_tag_allow",
"outgoing_domains_tag_deny",
"get_config",
"max_retries",
"retry_interval",
Expand All @@ -56,7 +57,8 @@
SETTING_DOMAINS_INCOMING_DENY = f"{NAMESPACE}_DOMAINS_INCOMING_DENY"
SETTING_DOMAINS_OUTGOING_ALLOW = f"{NAMESPACE}_DOMAINS_OUTGOING_ALLOW"
SETTING_DOMAINS_OUTGOING_DENY = f"{NAMESPACE}_DOMAINS_OUTGOING_DENY"
SETTING_DOMAINS_OUTGOING_OVERRIDE_TAG = f"{NAMESPACE}_DOMAINS_OUTGOING_OVERRIDE_TAG"
SETTING_DOMAINS_OUTGOING_TAG_ALLOW = f"{NAMESPACE}_DOMAINS_OUTGOING_TAG_ALLOW"
SETTING_DOMAINS_OUTGOING_TAG_DENY = f"{NAMESPACE}_DOMAINS_OUTGOING_TAG_DENY"
SETTING_INCOMING_TARGET_MODEL_REQUIRED = f"{NAMESPACE}_INCOMING_TARGET_MODEL_REQUIRED"
SETTING_MAX_RETRIES = f"{NAMESPACE}_MAX_RETRIES"
SETTING_RETRY_INTERVAL = f"{NAMESPACE}_RETRY_INTERVAL"
Expand All @@ -80,7 +82,8 @@
SETTING_DOMAINS_INCOMING_DENY: None,
SETTING_DOMAINS_OUTGOING_ALLOW: None,
SETTING_DOMAINS_OUTGOING_DENY: None,
SETTING_DOMAINS_OUTGOING_OVERRIDE_TAG: None,
SETTING_DOMAINS_OUTGOING_TAG_ALLOW: None,
SETTING_DOMAINS_OUTGOING_TAG_DENY: None,
SETTING_INCOMING_TARGET_MODEL_REQUIRED: False,
SETTING_MAX_RETRIES: 5,
SETTING_RETRY_INTERVAL: 60 * 10,
Expand Down Expand Up @@ -206,14 +209,22 @@ def outgoing_domains_deny() -> Set[str]:
return _get_attr(SETTING_DOMAINS_OUTGOING_DENY, _coerce_to_set)


def outgoing_domains_override_tag() -> str:
"""Return settings.WEBMENTIONS_DOMAINS_OUTGOING_OVERRIDE_TAG.
def outgoing_domains_tag_allow() -> str:
"""Return settings.WEBMENTIONS_DOMAINS_OUTGOING_TAG_ALLOW.
Name of CSS class or HTML `data-` attribute which, if present, reverses
the behaviour of WEBMENTIONS_DOMAINS_OUTGOING_ALLOW or
WEBMENTIONS_DOMAINS_OUTGOING_DENY.
Tag which can be included in HTML links as a class or attribute to override
the behaviour of WEBMENTIONS_DOMAINS_OUTGOING_DENY.
"""
return _get_attr(SETTING_DOMAINS_OUTGOING_OVERRIDE_TAG)
return _get_attr(SETTING_DOMAINS_OUTGOING_TAG_ALLOW)


def outgoing_domains_tag_deny() -> str:
"""Return settings.WEBMENTIONS_DOMAINS_OUTGOING_TAG_DENY.
Tag which can be included in HTML links as a class or attribute to override
the behaviour of WEBMENTIONS_DOMAINS_OUTGOING_ALLOW.
"""
return _get_attr(SETTING_DOMAINS_OUTGOING_TAG_DENY)


def max_retries() -> int:
Expand Down
26 changes: 14 additions & 12 deletions mentions/tasks/outgoing/local.py
@@ -1,5 +1,5 @@
import logging
from typing import Iterable, List, Optional, Set
from typing import Iterable, Optional, Set
from urllib.parse import urljoin

from bs4 import Tag
Expand All @@ -21,9 +21,10 @@ def get_target_links_in_html(
html: str,
source_path: str,
allow_self_mentions: bool = options.allow_self_mentions(),
domains_allow: Optional[List[str]] = None,
domains_deny: Optional[List[str]] = None,
domain_override_attr: Optional[str] = options.outgoing_domains_override_tag(),
domains_allow: Optional[Iterable[str]] = None,
domains_deny: Optional[Iterable[str]] = None,
domains_allow_tag: Optional[str] = options.outgoing_domains_tag_allow(),
domains_deny_tag: Optional[str] = options.outgoing_domains_tag_deny(),
) -> Set[str]:
"""Get any links from `html` that should be treated as webmention targets.
Expand All @@ -37,7 +38,8 @@ def get_target_links_in_html(
allow_self_mentions: See `options.allow_self_mentions`.
domains_allow: See `options.outgoing_domains_allow`.
domains_deny: See `options.outgoing_domains_deny`.
domain_override_attr: See `options.outgoing_domains_override_attr`
domains_allow_tag: See `options.outgoing_domains_tag_allow`
domains_deny_tag: See `options.outgoing_domains_tag_deny`
Returns:
Absolute URLs for any valid links from `html`.
"""
Expand All @@ -55,15 +57,18 @@ def get_target_links_in_html(

href = _resolve_relative_url(source_path, href)

if _has_class_or_attribute(link, domains_deny_tag):
continue

if _has_class_or_attribute(link, domains_allow_tag):
valid_links.add(href)
continue

if is_valid_target(
href,
allow_self_mentions,
domains_allow=domains_allow,
domains_deny=domains_deny,
override_include_exclude=_has_class_or_attribute(
tag=link,
attr=domain_override_attr,
),
):
valid_links.add(href)

Expand All @@ -75,15 +80,13 @@ def is_valid_target(
allow_self_mention: bool,
domains_allow: Optional[Iterable[str]],
domains_deny: Optional[Iterable[str]],
override_include_exclude: bool = False,
) -> bool:
"""
Args:
url: The URL to be tested
allow_self_mention: See `options.allow_self_mentions`.
domains_allow: See `options.outgoing_domains_allow`.
domains_deny: See `options.outgoing_domains_deny`.
override_include_exclude: See `options.domains_override_attr`
Returns:
True if `url` is a valid URL and should be treated as a possible
Expand All @@ -98,7 +101,6 @@ def is_valid_target(

return config.accept_domain_outgoing(
url=url,
inline_override=override_include_exclude,
allow_self_mention=allow_self_mention,
domains_allow=domains_allow,
domains_deny=domains_deny,
Expand Down
44 changes: 22 additions & 22 deletions tests/tests/test_options/test_config.py
Expand Up @@ -6,10 +6,16 @@


class AcceptDomainIncomingTests(SimpleTestCase):
"""Tests for config.accept_domain_incoming neither domains_allow, domains_deny defined."""
"""Tests for config.accept_domain_incoming with neither domains_allow, domains_deny defined."""

def test_with_no_restrictions(self):
self.assertTrue(accept_domain_incoming(testfunc.random_url(), None, None))
self.assertTrue(
accept_domain_incoming(
testfunc.random_url(),
None,
None,
)
)


class AcceptDomainIncoming_withAllowList_Tests(SimpleTestCase):
Expand Down Expand Up @@ -115,18 +121,30 @@ def test_mixed_domains(self):
self._assertNotDenied("https://sub.deny.com", deny)


class AcceptDomainOutgoing(SimpleTestCase):
"""Tests for config.accept_domain_outgoing with neither domains_allow, domains_deny defined."""

def test_with_no_restrictions(self):
self.assertTrue(
accept_domain_outgoing(
testfunc.random_url(),
True,
None,
None,
)
)


class AcceptDomainOutgoing_withAllowList_Tests(SimpleTestCase):
def _assertAllowed(
self,
url: str,
domains_allow: Set[str],
inline_override: bool = False,
allow_self_mention: bool = True,
):
self.assertTrue(
accept_domain_outgoing(
url,
inline_override=inline_override,
allow_self_mention=allow_self_mention,
domains_allow=domains_allow,
domains_deny=None,
Expand All @@ -138,13 +156,11 @@ def _assertNotAllowed(
self,
url: str,
domains_allow: Set[str],
inline_override: bool = False,
allow_self_mention: bool = True,
):
self.assertFalse(
accept_domain_outgoing(
url,
inline_override=inline_override,
allow_self_mention=allow_self_mention,
domains_allow=domains_allow,
domains_deny=None,
Expand Down Expand Up @@ -179,26 +195,17 @@ def test_wildcard_domains_are_not_accepted(self):
self._assertNotAllowed("https://fallow.org", allow)
self._assertNotAllowed("https://not.allow.net", allow)

def test_inline_override(self):
allow = {"*.allow.org", "*.allow.com", ""}
self._assertNotAllowed("https://allow.org", allow, inline_override=True)
self._assertAllowed(
"https://normally-disallowed.org", allow, inline_override=True
)


class AcceptDomainOutgoing_withDenyList_Tests(SimpleTestCase):
def _assertDenied(
self,
url: str,
domains_deny: Set[str],
inline_override: bool = False,
allow_self_mention: bool = True,
):
self.assertFalse(
accept_domain_outgoing(
url,
inline_override=inline_override,
allow_self_mention=allow_self_mention,
domains_allow=None,
domains_deny=domains_deny,
Expand All @@ -210,13 +217,11 @@ def _assertNotDenied(
self,
url: str,
domains_deny: Set[str],
inline_override: bool = False,
allow_self_mention: bool = True,
):
self.assertTrue(
accept_domain_outgoing(
url,
inline_override=inline_override,
allow_self_mention=allow_self_mention,
domains_allow=None,
domains_deny=domains_deny,
Expand All @@ -243,8 +248,3 @@ def test_wildcard_domains_are_not_denied(self):
deny = {"*.deny.org", "*.also.deny.net"}
self._assertNotDenied(testfunc.random_url(), deny)
self._assertNotDenied("https://other.deny.net/123", deny)

def test_inline_override(self):
deny = {"*.deny.org", "*.deny.com", ""}
self._assertNotDenied("https://deny.org", deny, inline_override=True)
self._assertDenied("https://normally-denied.org", deny, inline_override=True)
12 changes: 8 additions & 4 deletions tests/tests/test_options/test_option_formats.py
Expand Up @@ -20,7 +20,8 @@ def test_flat_settings(self):
settings.WEBMENTIONS_DEFAULT_URL_PARAMETER_MAPPING = {"foo": "bar"}
settings.WEBMENTIONS_DOMAINS_OUTGOING_ALLOW = {"include"}
settings.WEBMENTIONS_DOMAINS_OUTGOING_DENY = {"exclude"}
settings.WEBMENTIONS_DOMAINS_OUTGOING_OVERRIDE_TAG = "wm-override"
settings.WEBMENTIONS_DOMAINS_OUTGOING_TAG_ALLOW = "wm-send"
settings.WEBMENTIONS_DOMAINS_OUTGOING_TAG_DENY = "wm-nosend"
settings.WEBMENTIONS_INCOMING_TARGET_MODEL_REQUIRED = True
settings.WEBMENTIONS_MAX_RETRIES = 25
settings.WEBMENTIONS_RETRY_INTERVAL = 30
Expand All @@ -42,7 +43,8 @@ def test_flat_settings(self):
self.assertDictEqual(options.default_url_parameter_mapping(), dict(foo="bar"))
self.assertSetEqual(options.outgoing_domains_allow(), {"include"})
self.assertSetEqual(options.outgoing_domains_deny(), {"exclude"})
self.assertEqual(options.outgoing_domains_override_tag(), "wm-override")
self.assertEqual(options.outgoing_domains_tag_allow(), "wm-send")
self.assertEqual(options.outgoing_domains_tag_deny(), "wm-nosend")
self.assertEqual(options.user_agent(), "empty")

def test_namespaced_settings(self):
Expand All @@ -56,7 +58,8 @@ def test_namespaced_settings(self):
"DEFAULT_URL_PARAMETER_MAPPING": {"bar": "foo"},
"DOMAINS_OUTGOING_ALLOW": ("included",),
"DOMAINS_OUTGOING_DENY": ["excluded"],
"DOMAINS_OUTGOING_OVERRIDE_TAG": "override-wm",
"DOMAINS_OUTGOING_TAG_ALLOW": "send-wm",
"DOMAINS_OUTGOING_TAG_DENY": "nosend-wm",
"INCOMING_TARGET_MODEL_REQUIRED": False,
"MAX_RETRIES": 26,
"RETRY_INTERVAL": 31,
Expand All @@ -79,5 +82,6 @@ def test_namespaced_settings(self):
self.assertDictEqual(options.default_url_parameter_mapping(), dict(bar="foo"))
self.assertSetEqual(options.outgoing_domains_allow(), {"included"})
self.assertSetEqual(options.outgoing_domains_deny(), {"excluded"})
self.assertEqual(options.outgoing_domains_override_tag(), "override-wm")
self.assertEqual(options.outgoing_domains_tag_allow(), "send-wm")
self.assertEqual(options.outgoing_domains_tag_deny(), "nosend-wm")
self.assertEqual(options.user_agent(), "nope")

0 comments on commit d2f1790

Please sign in to comment.