- Check if hook is enabled before constructing hook notification items
- Skip/early exit email hook if notification item is not a VCALENDAR
This commit is contained in:
@@ -89,34 +89,38 @@ class ApplicationPartDelete(ApplicationBase):
|
|||||||
if not access.check("D", item):
|
if not access.check("D", item):
|
||||||
logger.info("delete of collection is prevented by config/option [rights] permit_delete_collection and not explicit allowed by permission 'D': %s", path)
|
logger.info("delete of collection is prevented by config/option [rights] permit_delete_collection and not explicit allowed by permission 'D': %s", path)
|
||||||
return httputils.NOT_ALLOWED
|
return httputils.NOT_ALLOWED
|
||||||
for i in item.get_all():
|
if self._hook.enabled:
|
||||||
hook_notification_item_list.append(
|
for i in item.get_all():
|
||||||
HookNotificationItem(
|
hook_notification_item_list.append(
|
||||||
notification_item_type=HookNotificationItemTypes.DELETE,
|
HookNotificationItem(
|
||||||
path=access.path,
|
notification_item_type=HookNotificationItemTypes.DELETE,
|
||||||
content=i.uid,
|
path=access.path,
|
||||||
uid=i.uid,
|
content=i.uid,
|
||||||
old_content=i.serialize(), # type: ignore
|
content_type=i.name,
|
||||||
new_content=None
|
uid=i.uid,
|
||||||
|
old_content=i.serialize(), # type: ignore
|
||||||
|
new_content=None
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
xml_answer = xml_delete(base_prefix, path, item)
|
xml_answer = xml_delete(base_prefix, path, item)
|
||||||
else:
|
else:
|
||||||
assert item.collection is not None
|
assert item.collection is not None
|
||||||
assert item.href is not None
|
assert item.href is not None
|
||||||
hook_notification_item_list.append(
|
if self._hook.enabled:
|
||||||
HookNotificationItem(
|
hook_notification_item_list.append(
|
||||||
notification_item_type=HookNotificationItemTypes.DELETE,
|
HookNotificationItem(
|
||||||
path=access.path,
|
notification_item_type=HookNotificationItemTypes.DELETE,
|
||||||
content=item.uid,
|
path=access.path,
|
||||||
uid=item.uid,
|
content=item.uid,
|
||||||
old_content=item.serialize(), # type: ignore
|
content_type=item.name,
|
||||||
new_content=None,
|
uid=item.uid,
|
||||||
|
old_content=item.serialize(), # type: ignore
|
||||||
|
new_content=None,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
xml_answer = xml_delete(
|
xml_answer = xml_delete(
|
||||||
base_prefix, path, item.collection, item.href)
|
base_prefix, path, item.collection, item.href)
|
||||||
for notification_item in hook_notification_item_list:
|
for notification_item in hook_notification_item_list: # Will be empty if hook not enabled
|
||||||
self._hook.notify(notification_item)
|
self._hook.notify(notification_item)
|
||||||
headers = {"Content-Type": "text/xml; charset=%s" % self._encoding}
|
headers = {"Content-Type": "text/xml; charset=%s" % self._encoding}
|
||||||
return client.OK, headers, self._xml_response(xml_answer), None
|
return client.OK, headers, self._xml_response(xml_answer), None
|
||||||
|
|||||||
@@ -205,15 +205,17 @@ class ApplicationPartProppatch(ApplicationBase):
|
|||||||
xml_content,
|
xml_content,
|
||||||
encoding=self._encoding
|
encoding=self._encoding
|
||||||
).decode(encoding=self._encoding)
|
).decode(encoding=self._encoding)
|
||||||
hook_notification_item = HookNotificationItem(
|
if self._hook.enabled:
|
||||||
notification_item_type=HookNotificationItemTypes.CPATCH,
|
hook_notification_item = HookNotificationItem(
|
||||||
path=access.path,
|
notification_item_type=HookNotificationItemTypes.CPATCH,
|
||||||
content=content,
|
path=access.path,
|
||||||
uid=None,
|
content=content,
|
||||||
old_content=None,
|
content_type=None, # Can't easily determine content type, won't trigger email hook
|
||||||
new_content=content
|
uid=None,
|
||||||
)
|
old_content=None,
|
||||||
self._hook.notify(hook_notification_item)
|
new_content=content
|
||||||
|
)
|
||||||
|
self._hook.notify(hook_notification_item)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
# return better matching HTTP result in case errno is provided and catched
|
# return better matching HTTP result in case errno is provided and catched
|
||||||
errno_match = re.search("\\[Errno ([0-9]+)\\]", str(e))
|
errno_match = re.search("\\[Errno ([0-9]+)\\]", str(e))
|
||||||
|
|||||||
@@ -306,28 +306,32 @@ class ApplicationPartPut(ApplicationBase):
|
|||||||
href=path,
|
href=path,
|
||||||
items=prepared_items,
|
items=prepared_items,
|
||||||
props=props)
|
props=props)
|
||||||
for item in prepared_items:
|
# Only run the hook if the hook is enabled
|
||||||
# Try to grab the previously-existing item by href
|
if self._hook.enabled:
|
||||||
existing_item = replaced_items.get(item.href, None) # type: ignore
|
for item in prepared_items:
|
||||||
if existing_item:
|
# Try to grab the previously-existing item by href
|
||||||
hook_notification_item = HookNotificationItem(
|
existing_item = replaced_items.get(item.href, None) # type: ignore
|
||||||
notification_item_type=HookNotificationItemTypes.UPSERT,
|
if existing_item:
|
||||||
path=access.path,
|
hook_notification_item = HookNotificationItem(
|
||||||
content=existing_item.serialize(),
|
notification_item_type=HookNotificationItemTypes.UPSERT,
|
||||||
uid=None,
|
path=access.path,
|
||||||
old_content=existing_item.serialize(),
|
content=existing_item.serialize(),
|
||||||
new_content=item.serialize()
|
content_type=item.name,
|
||||||
)
|
uid=None,
|
||||||
else: # We assume the item is new because it was not in the replaced_items
|
old_content=existing_item.serialize(),
|
||||||
hook_notification_item = HookNotificationItem(
|
new_content=item.serialize(),
|
||||||
notification_item_type=HookNotificationItemTypes.UPSERT,
|
)
|
||||||
path=access.path,
|
else: # We assume the item is new because it was not in the replaced_items
|
||||||
content=item.serialize(),
|
hook_notification_item = HookNotificationItem(
|
||||||
uid=None,
|
notification_item_type=HookNotificationItemTypes.UPSERT,
|
||||||
old_content=None,
|
path=access.path,
|
||||||
new_content=item.serialize()
|
content=item.serialize(),
|
||||||
)
|
content_type=item.name,
|
||||||
self._hook.notify(hook_notification_item)
|
uid=None,
|
||||||
|
old_content=None,
|
||||||
|
new_content=item.serialize()
|
||||||
|
)
|
||||||
|
self._hook.notify(hook_notification_item)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Bad PUT request on %r (create_collection): %s", path, e, exc_info=True)
|
"Bad PUT request on %r (create_collection): %s", path, e, exc_info=True)
|
||||||
@@ -345,15 +349,17 @@ class ApplicationPartPut(ApplicationBase):
|
|||||||
try:
|
try:
|
||||||
uploaded_item, replaced_item = parent_item.upload(href, prepared_item)
|
uploaded_item, replaced_item = parent_item.upload(href, prepared_item)
|
||||||
etag = uploaded_item.etag
|
etag = uploaded_item.etag
|
||||||
hook_notification_item = HookNotificationItem(
|
if self._hook.enabled:
|
||||||
notification_item_type=HookNotificationItemTypes.UPSERT,
|
hook_notification_item = HookNotificationItem(
|
||||||
path=access.path,
|
notification_item_type=HookNotificationItemTypes.UPSERT,
|
||||||
content=prepared_item.serialize(),
|
path=access.path,
|
||||||
uid=None,
|
content=prepared_item.serialize(),
|
||||||
old_content=replaced_item.serialize() if replaced_item else None,
|
content_type=prepared_item.name,
|
||||||
new_content=prepared_item.serialize()
|
uid=None,
|
||||||
)
|
old_content=replaced_item.serialize() if replaced_item else None,
|
||||||
self._hook.notify(hook_notification_item)
|
new_content=prepared_item.serialize(),
|
||||||
|
)
|
||||||
|
self._hook.notify(hook_notification_item)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
# return better matching HTTP result in case errno is provided and catched
|
# return better matching HTTP result in case errno is provided and catched
|
||||||
errno_match = re.search("\\[Errno ([0-9]+)\\]", str(e))
|
errno_match = re.search("\\[Errno ([0-9]+)\\]", str(e))
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ class BaseHook:
|
|||||||
"""
|
"""
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enabled(self) -> bool:
|
||||||
|
"""Check if this hook is enabled."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def notify(self, notification_item):
|
def notify(self, notification_item):
|
||||||
"""Upload a new or replace an existing item."""
|
"""Upload a new or replace an existing item."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@@ -55,10 +60,12 @@ def _cleanup(path):
|
|||||||
|
|
||||||
class HookNotificationItem:
|
class HookNotificationItem:
|
||||||
|
|
||||||
def __init__(self, notification_item_type, path, content=None, uid=None, new_content=None, old_content=None):
|
def __init__(self, notification_item_type, path, content=None, content_type=None, uid=None, new_content=None,
|
||||||
|
old_content=None):
|
||||||
self.type = notification_item_type.value
|
self.type = notification_item_type.value
|
||||||
self.point = _cleanup(path)
|
self.point = _cleanup(path)
|
||||||
self._content_legacy = content
|
self._content_legacy = content
|
||||||
|
self.content_type = content_type
|
||||||
self.uid = uid
|
self.uid = uid
|
||||||
self.new_content = new_content
|
self.new_content = new_content
|
||||||
self.old_content = old_content
|
self.old_content = old_content
|
||||||
@@ -67,6 +74,30 @@ class HookNotificationItem:
|
|||||||
def content(self): # For backward compatibility
|
def content(self): # For backward compatibility
|
||||||
return self._content_legacy or self.uid or self.new_content or self.old_content
|
return self._content_legacy or self.uid or self.new_content or self.old_content
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_calendar_item(self) -> bool:
|
||||||
|
"""Check if this notification item is related to a VCALENDAR item."""
|
||||||
|
if not self.content_type:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.content_type.upper().strip() == "VCALENDAR"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_card_item(self) -> bool:
|
||||||
|
"""Check if this notification item is related to a VCARD item."""
|
||||||
|
if not self.content_type:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.content_type.upper().strip() == "VCARD"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_addressbook_item(self) -> bool:
|
||||||
|
"""Check if this notification item is related to a VADDRESSBOOK item."""
|
||||||
|
if not self.content_type:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.content_type.upper().strip() == "VADDRESSBOOK"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def replaces_existing_item(self) -> bool:
|
def replaces_existing_item(self) -> bool:
|
||||||
"""Check if this notification item replaces/deletes an existing item."""
|
"""Check if this notification item replaces/deletes an existing item."""
|
||||||
|
|||||||
@@ -930,12 +930,21 @@ class Hook(BaseHook):
|
|||||||
self.email_config
|
self.email_config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enabled(self) -> bool:
|
||||||
|
"""Check if this hook is enabled (has a notify method)."""
|
||||||
|
return self.email_config.host is not None
|
||||||
|
|
||||||
def notify(self, notification_item) -> None:
|
def notify(self, notification_item) -> None:
|
||||||
"""
|
"""
|
||||||
Entrypoint for processing a single notification item.
|
Entrypoint for processing a single notification item.
|
||||||
Overrides default notify method from BaseHook.
|
Overrides default notify method from BaseHook.
|
||||||
Triggered by Radicale when a notifiable event occurs (e.g. item added, updated or deleted)
|
Triggered by Radicale when a notifiable event occurs (e.g. item added, updated or deleted)
|
||||||
"""
|
"""
|
||||||
|
# Skip any non-VCALENDAR items
|
||||||
|
if not notification_item.is_calendar_item:
|
||||||
|
return
|
||||||
|
|
||||||
if isinstance(notification_item, HookNotificationItem):
|
if isinstance(notification_item, HookNotificationItem):
|
||||||
self._process_event_and_notify(notification_item)
|
self._process_event_and_notify(notification_item)
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,10 @@ from radicale import hook
|
|||||||
|
|
||||||
|
|
||||||
class Hook(hook.BaseHook):
|
class Hook(hook.BaseHook):
|
||||||
|
@property
|
||||||
|
def enabled(self) -> bool:
|
||||||
|
"""Check if this hook is enabled."""
|
||||||
|
return False
|
||||||
|
|
||||||
def notify(self, notification_item):
|
def notify(self, notification_item):
|
||||||
"""Notify nothing. Empty hook."""
|
"""Notify nothing. Empty hook."""
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ class Hook(hook.BaseHook):
|
|||||||
logger.debug("Hook 'rabbitmq': _make_declare_queue_synced")
|
logger.debug("Hook 'rabbitmq': _make_declare_queue_synced")
|
||||||
self._channel.queue_declare(queue=self._topic, durable=True, arguments={"x-queue-type": self._queue_type})
|
self._channel.queue_declare(queue=self._topic, durable=True, arguments={"x-queue-type": self._queue_type})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enabled(self) -> bool:
|
||||||
|
"""Check if this hook is enabled."""
|
||||||
|
return self._endpoint is not None
|
||||||
|
|
||||||
def notify(self, notification_item):
|
def notify(self, notification_item):
|
||||||
if isinstance(notification_item, HookNotificationItem):
|
if isinstance(notification_item, HookNotificationItem):
|
||||||
self._notify(notification_item, True)
|
self._notify(notification_item, True)
|
||||||
|
|||||||
Reference in New Issue
Block a user