sharing/bday/templating: default config rework

This commit is contained in:
Peter Bieringer
2026-05-31 07:06:35 +02:00
parent 70ec043423
commit 07c5873bdd
4 changed files with 67 additions and 42 deletions

View File

@@ -595,11 +595,11 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([
"help": "default permissions for map-based sharing",
"type": rights_permission}),
("conversion_bday_summary_template", {
"value": "[{n:f} {n:g} {n:a}|{fn}|{nickname}] (BDAY)",
"value": sharing.SHARING_BDAY_SUMMARY_TEMPLATE_DEFAULT,
"help": "conversion bday summary template",
"type": sharing.check_template}),
("conversion_bday_description_template", {
"value": "BDAY={year}-{month}-{day}",
"value": sharing.SHARING_BDAY_DESCRIPTION_TEMPLATE_DEFAULT,
"help": "conversion bday description template",
"type": sharing.check_template}),
("conversion_bday_alarm_trigger_template", {
@@ -607,11 +607,11 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([
"help": "conversion bday alarm trigger template",
"type": sharing.check_template_alarm_trigger}),
("conversion_bday_categories", {
"value": "Birthday",
"value": sharing.SHARING_BDAY_CATEGORIES_DEFAULT,
"help": "conversion bday categories",
"type": str}),
("conversion_bday_age_max", {
"value": "99",
"value": str(sharing.SHARING_BDAY_AGE_MAX_DEFAULT),
"help": "conversion bday age max",
"type": sharing.check_bday_max_age}),
])),

View File

@@ -38,7 +38,7 @@ from typing import (Any, Callable, List, MutableMapping, Optional, Sequence,
import vobject
from radicale import storage # noqa:F401
from radicale import pathutils, utils
from radicale import pathutils, sharing, utils
from radicale.item import filter as radicale_filter
from radicale.log import logger
@@ -594,7 +594,6 @@ class Item:
placeholder_mapping: dict = {}
bdayS = match[1] + match[2] + match[3]
bdaySdesc = match[1] + "-" + match[2] + "-" + match[3]
bdayY = int(match[1])
bdayM = int(match[2])
bdayD = int(match[3])
@@ -649,37 +648,49 @@ class Item:
else:
item_ics.add('prodid').value = PRODID_CONVERTED
# create SUMMARY
summary = name + " (BDAY)" # default
if ShareActions is not None and 'config' in ShareActions:
if 'conversion_bday_summary_template' in ShareActions['config']:
summary = ShareActions['config']['conversion_bday_summary_template']
summary = replace_placeholders(summary, placeholder_mapping)
# prepare SUMMARY
if ShareActions is not None and 'config' in ShareActions and 'conversion_bday_summary_template' in ShareActions['config']:
summary = ShareActions['config']['conversion_bday_summary_template']
elif ShareActions is not None and 'config_default' in ShareActions and 'conversion_bday_summary_template' in ShareActions['config_default']:
summary = ShareActions['config_default']['conversion_bday_summary_template']
else:
summary = sharing.SHARING_BDAY_SUMMARY_TEMPLATE_DEFAULT # fallback
summary = replace_placeholders(summary, placeholder_mapping)
# create DESCRIPTION
description = "BDAY=" + bdaySdesc # default
if ShareActions is not None and 'config' in ShareActions:
if 'conversion_bday_description_template' in ShareActions['config']:
description = ShareActions['config']['conversion_bday_description_template']
description = replace_placeholders(description, placeholder_mapping)
# prepare DESCRIPTION
if ShareActions is not None and 'config' in ShareActions and 'conversion_bday_description_template' in ShareActions['config']:
description = ShareActions['config']['conversion_bday_description_template']
elif ShareActions is not None and 'config_default' in ShareActions and 'conversion_bday_description_template' in ShareActions['config_default']:
description = ShareActions['config_default']['conversion_bday_description_template']
else:
description = sharing.SHARING_BDAY_DESCRIPTION_TEMPLATE_DEFAULT # fallback
description = replace_placeholders(description, placeholder_mapping)
# create CATEGORIES
categories: list = ["Birthday"] # default
if ShareActions is not None and 'config' in ShareActions:
if 'conversion_bday_categories' in ShareActions['config']:
categories = ShareActions['config']['conversion_bday_categories'].split(',')
if ShareActions is not None and 'config' in ShareActions and 'conversion_bday_categories' in ShareActions['config']:
categories = ShareActions['config']['conversion_bday_categories'].split(',')
elif ShareActions is not None and 'config_default' in ShareActions and 'conversion_bday_categories' in ShareActions['config_default']:
categories = ShareActions['config_default']['conversion_bday_categories'].split(',')
else:
categories = sharing.SHARING_BDAY_CATEGORIES_DEFAULT.split(',') # fallback
# check ALARM
alarm_trigger = "" # default
if ShareActions is not None and 'config' in ShareActions:
if ShareActions is not None and 'config' in ShareActions and 'conversion_bday_alarm_trigger_template' in ShareActions['config']:
alarm_trigger = ShareActions['config']['conversion_bday_alarm_trigger_template']
elif ShareActions is not None and 'config_default' in ShareActions and 'conversion_bday_alarm_trigger_template' in ShareActions['config_default']:
alarm_trigger = ShareActions['config_default']['conversion_bday_alarm_trigger_template']
else:
alarm_trigger = "" # default
vevent_enable_age = False
age_max = 0
if "{age}" in summary or "{age}" in description or "age" in alarm_trigger:
if ShareActions is not None and 'config' in ShareActions:
if 'conversion_bday_age_max' in ShareActions['config']:
age_max = ShareActions['config']['conversion_bday_age_max']
if ShareActions is not None and 'config' in ShareActions and 'conversion_bday_age_max' in ShareActions['config']:
age_max = ShareActions['config']['conversion_bday_age_max']
elif ShareActions is not None and 'config_default' in ShareActions and 'conversion_bday_age_max' in ShareActions['config_default']:
age_max = ShareActions['config_default']['conversion_bday_age_max']
else:
age_max = sharing.SHARING_BDAY_AGE_MAX_DEFAULT # fallback
vevent_enable_age = True
# create UID

View File

@@ -123,15 +123,19 @@ TOKEN_PATTERN_V1: str = "v1/[a-zA-Z0-9_\\-]{44}"
OVERLAY_PROPERTIES_WHITELIST: Sequence[str] = ("C:calendar-description", "ICAL:calendar-color", "CR:addressbook-description", "INF:addressbook-color", "D:displayname", "ICAL:calendar-order")
SHARING_BDAY_AGE_MAX: int = 199 # maximum age to prevent unexpected DoS by config
SHARING_BDAY_AGE_MAX_LIMIT: int = 199 # maximum age to prevent unexpected DoS by config
SHARING_BDAY_AGE_MAX_DEFAULT: int = 99
SHARING_BDAY_SUMMARY_TEMPLATE_DEFAULT: str = "[{n:f} {n:g} {n:a}|{fn}|{nickname}] ({year}) (BDAY)"
SHARING_BDAY_DESCRIPTION_TEMPLATE_DEFAULT: str = "BDAY={year}-{month}-{day}"
SHARING_BDAY_CATEGORIES_DEFAULT: str = 'Birthday'
def check_bday_max_age(data: Any) -> int:
value = int(data)
if value < 0:
raise ValueError("value is negative: %d" % value)
if value > SHARING_BDAY_AGE_MAX:
raise ValueError("value exceeds maximum (%d): %d" % (SHARING_BDAY_AGE_MAX, value))
if value > SHARING_BDAY_AGE_MAX_LIMIT:
raise ValueError("value exceeds maximum (%d): %d" % (SHARING_BDAY_AGE_MAX_LIMIT, value))
return value
@@ -478,7 +482,7 @@ class BaseSharing:
# autogenerate Actions if not existing
if share['Actions'] is None or 'config' not in share['Actions']:
share['Actions'] = {
'config': {
'config_default': {
'conversion_bday_summary_template': self.conversion_bday_summary_template,
'conversion_bday_description_template': self.conversion_bday_description_template,
'conversion_bday_alarm_trigger_template': self.conversion_bday_alarm_trigger_template,
@@ -492,7 +496,9 @@ class BaseSharing:
# nothing to do
pass
else:
share['Actions']['config'].update(
if 'config_default' not in share['Actions']:
share['Actions'].update({'config_default': {}})
share['Actions']['config_default'].update(
{'conversion_bday_summary_template': self.conversion_bday_summary_template}
)
@@ -500,7 +506,9 @@ class BaseSharing:
# nothing to do
pass
else:
share['Actions']['config'].update(
if 'config_default' not in share['Actions']:
share['Actions'].update({'config_default': {}})
share['Actions']['config_default'].update(
{'conversion_bday_description_template': self.conversion_bday_description_template}
)
@@ -508,7 +516,9 @@ class BaseSharing:
# nothing to do
pass
else:
share['Actions']['config'].update(
if 'config_default' not in share['Actions']:
share['Actions'].update({'config_default': {}})
share['Actions']['config_default'].update(
{'conversion_bday_alarm_trigger_template': self.conversion_bday_alarm_trigger_template}
)
@@ -516,7 +526,9 @@ class BaseSharing:
# nothing to do
pass
else:
share['Actions']['config'].update(
if 'config_default' not in share['Actions']:
share['Actions'].update({'config_default': {}})
share['Actions']['config_default'].update(
{'conversion_bday_categories': self.conversion_bday_categories}
)
@@ -524,7 +536,9 @@ class BaseSharing:
# nothing to do
pass
else:
share['Actions']['config'].update(
if 'config_default' not in share['Actions']:
share['Actions'].update({'config_default': {}})
share['Actions']['config_default'].update(
{'conversion_bday_age_max': self.conversion_bday_age_max}
)

View File

@@ -5008,7 +5008,7 @@ permissions: RrWw""")
assert "SUMMARY:Test-FN (BDAY)" in answer
self.configure({"sharing": {"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (Birthday)"}})
logging.info("\n*** GET collection user format:text -> ok")
logging.info("\n*** GET collection user format with fn+Birthday -> ok")
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
assert "SUMMARY:Test-FN (Birthday)" in answer
@@ -5244,7 +5244,7 @@ permissions: RrWw""")
logging.info("\n*** configuration test: conversion_bday_age_max > MAX")
try:
self.configure({"sharing": {
"conversion_bday_age_max": (sharing.SHARING_BDAY_AGE_MAX + 1),
"conversion_bday_age_max": (sharing.SHARING_BDAY_AGE_MAX_LIMIT + 1),
}})
except RuntimeError:
pass
@@ -5349,7 +5349,7 @@ permissions: RrWw""")
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_age_max": (sharing.SHARING_BDAY_AGE_MAX + 1),
"conversion_bday_age_max": (sharing.SHARING_BDAY_AGE_MAX_LIMIT + 1),
}}
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
@@ -5371,7 +5371,7 @@ permissions: RrWw""")
json_dict['PathMapped'] = path_mapped
json_dict['PathOrToken'] = path_shared_r
json_dict['Actions'] = {"config": {
"conversion_bday_age_max": sharing.SHARING_BDAY_AGE_MAX,
"conversion_bday_age_max": sharing.SHARING_BDAY_AGE_MAX_LIMIT,
}}
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
@@ -5952,8 +5952,8 @@ permissions: RrWw""")
logging.info("\n*** GET bday with token")
_, answer = self.get(path_shared)
assert "VCARD" not in answer
assert "Test-FN-C3 (BDAY)" in answer
assert "Test-FN (BDAY)" in answer
assert "Test-FN-C3 (BDAY)" in answer # contact2
assert "Test-FN (BDAY)" in answer # contact1
# verify content as owner
logging.info("\n*** GET collection owner -> ok")