feat(modules): validate catalog permission declarations
This commit is contained in:
@@ -658,6 +658,10 @@ def _normalize_catalog_item(value: Any) -> dict[str, object]:
|
||||
"availability": _catalog_availability(value, module_id=module_id),
|
||||
"availability_reason": _optional_str(value, "availability_reason"),
|
||||
"configuration_requirements": _string_list(value.get("configuration_requirements")),
|
||||
"permissions": _normalize_catalog_permissions(
|
||||
value.get("permissions"),
|
||||
module_id=module_id,
|
||||
),
|
||||
}
|
||||
if item["availability"] == "withdrawn" and not item["availability_reason"]:
|
||||
raise ValueError(
|
||||
@@ -784,6 +788,109 @@ def _catalog_availability(value: Mapping[str, object], *, module_id: str) -> str
|
||||
return availability
|
||||
|
||||
|
||||
def _normalize_catalog_permissions(
|
||||
value: object,
|
||||
*,
|
||||
module_id: str,
|
||||
) -> list[dict[str, object]]:
|
||||
if value is None:
|
||||
return []
|
||||
if not isinstance(value, list):
|
||||
raise ValueError(
|
||||
f"Module package catalog permissions for {module_id!r} must be a list."
|
||||
)
|
||||
if len(value) > 1000:
|
||||
raise ValueError(
|
||||
f"Module package catalog permissions for {module_id!r} exceed 1000 entries."
|
||||
)
|
||||
normalized: list[dict[str, object]] = []
|
||||
seen: set[str] = set()
|
||||
for raw in value:
|
||||
if not isinstance(raw, Mapping):
|
||||
raise ValueError(
|
||||
f"Module package catalog permission entries for {module_id!r} must be objects."
|
||||
)
|
||||
scope = _bounded_catalog_permission_text(
|
||||
raw,
|
||||
"scope",
|
||||
module_id=module_id,
|
||||
maximum=200,
|
||||
)
|
||||
if scope in seen:
|
||||
raise ValueError(
|
||||
f"Module package catalog entry {module_id!r} declares permission {scope!r} more than once."
|
||||
)
|
||||
seen.add(scope)
|
||||
level = _bounded_catalog_permission_text(
|
||||
raw,
|
||||
"level",
|
||||
module_id=module_id,
|
||||
maximum=20,
|
||||
)
|
||||
if level not in {"system", "tenant"}:
|
||||
raise ValueError(
|
||||
f"Module package catalog permission {module_id!r}/{scope!r} has unsupported level {level!r}."
|
||||
)
|
||||
deprecated = raw.get("deprecated", False)
|
||||
if not isinstance(deprecated, bool):
|
||||
raise ValueError(
|
||||
f"Module package catalog permission {module_id!r}/{scope!r} deprecated must be true or false."
|
||||
)
|
||||
normalized.append(
|
||||
{
|
||||
"scope": scope,
|
||||
"label": _bounded_catalog_permission_text(
|
||||
raw,
|
||||
"label",
|
||||
module_id=module_id,
|
||||
maximum=200,
|
||||
),
|
||||
"description": _bounded_catalog_permission_text(
|
||||
raw,
|
||||
"description",
|
||||
module_id=module_id,
|
||||
maximum=1000,
|
||||
),
|
||||
"category": _bounded_catalog_permission_text(
|
||||
raw,
|
||||
"category",
|
||||
module_id=module_id,
|
||||
maximum=120,
|
||||
),
|
||||
"level": level,
|
||||
"resource": _bounded_catalog_permission_text(
|
||||
raw,
|
||||
"resource",
|
||||
module_id=module_id,
|
||||
maximum=120,
|
||||
),
|
||||
"action": _bounded_catalog_permission_text(
|
||||
raw,
|
||||
"action",
|
||||
module_id=module_id,
|
||||
maximum=120,
|
||||
),
|
||||
"deprecated": deprecated,
|
||||
}
|
||||
)
|
||||
return normalized
|
||||
|
||||
|
||||
def _bounded_catalog_permission_text(
|
||||
value: Mapping[str, object],
|
||||
key: str,
|
||||
*,
|
||||
module_id: str,
|
||||
maximum: int,
|
||||
) -> str:
|
||||
text = _required_str(value, key)
|
||||
if len(text) > maximum:
|
||||
raise ValueError(
|
||||
f"Module package catalog permission {key!r} for {module_id!r} exceeds {maximum} characters."
|
||||
)
|
||||
return text
|
||||
|
||||
|
||||
def _normalize_catalog_source(
|
||||
value: object,
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user