66 lines
1.7 KiB
Python
66 lines
1.7 KiB
Python
"""Merging of policies."""
|
|
from typing import cast, Dict, List, Set
|
|
|
|
from .types import PolicyType, CategoryType
|
|
|
|
|
|
def merge_policies(policies: List[PolicyType]) -> PolicyType:
|
|
"""Merge policies."""
|
|
new_policy: Dict[str, CategoryType] = {}
|
|
seen: Set[str] = set()
|
|
for policy in policies:
|
|
for category in policy:
|
|
if category in seen:
|
|
continue
|
|
seen.add(category)
|
|
new_policy[category] = _merge_policies(
|
|
[policy.get(category) for policy in policies]
|
|
)
|
|
cast(PolicyType, new_policy)
|
|
return new_policy
|
|
|
|
|
|
def _merge_policies(sources: List[CategoryType]) -> CategoryType:
|
|
"""Merge a policy."""
|
|
# When merging policies, the most permissive wins.
|
|
# This means we order it like this:
|
|
# True > Dict > None
|
|
#
|
|
# True: allow everything
|
|
# Dict: specify more granular permissions
|
|
# None: no opinion
|
|
#
|
|
# If there are multiple sources with a dict as policy, we recursively
|
|
# merge each key in the source.
|
|
|
|
policy: CategoryType = None
|
|
seen: Set[str] = set()
|
|
for source in sources:
|
|
if source is None:
|
|
continue
|
|
|
|
# A source that's True will always win. Shortcut return.
|
|
if source is True:
|
|
return True
|
|
|
|
assert isinstance(source, dict)
|
|
|
|
if policy is None:
|
|
policy = cast(CategoryType, {})
|
|
|
|
assert isinstance(policy, dict)
|
|
|
|
for key in source:
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
|
|
key_sources = []
|
|
for src in sources:
|
|
if isinstance(src, dict):
|
|
key_sources.append(src.get(key))
|
|
|
|
policy[key] = _merge_policies(key_sources)
|
|
|
|
return policy
|