X-Git-Url: https://wannabe.guru.org/gitweb/?a=blobdiff_plain;f=acl.py;h=2b347673af1b14dbb144ca9179d115a5eba642b0;hb=ed8fa2b10b0177b15b7423263bdd390efde2f0c8;hp=5040304a7913fe4e452ceb2dc5ef5f2a76b36491;hpb=e516059c716537259c601c022cc3bad44025385e;p=python_utils.git diff --git a/acl.py b/acl.py index 5040304..2b34767 100644 --- a/acl.py +++ b/acl.py @@ -1,17 +1,27 @@ #!/usr/bin/env python3 from abc import ABC, abstractmethod +import enum import fnmatch import logging import re -from typing import Any, Callable, List, Optional, Set +from typing import Any, Callable, List, Optional, Set, Sequence +from overrides import overrides + +# This module is commonly used by others in here and should avoid +# taking any unnecessary dependencies back on them. logger = logging.getLogger(__name__) -ACL_ORDER_ALLOW_DENY = 1 -ACL_ORDER_DENY_ALLOW = 2 +class Order(enum.Enum): + """A helper to express the order of evaluation for allows/denies + in an Access Control List. + """ + UNDEFINED = 0 + ALLOW_DENY = 1 + DENY_ALLOW = 2 class SimpleACL(ABC): @@ -20,22 +30,22 @@ class SimpleACL(ABC): def __init__( self, *, - order_to_check_allow_deny: int, + order_to_check_allow_deny: Order, default_answer: bool ): if order_to_check_allow_deny not in ( - ACL_ORDER_ALLOW_DENY, ACL_ORDER_DENY_ALLOW + Order.ALLOW_DENY, Order.DENY_ALLOW ): raise Exception( - 'order_to_check_allow_deny must be ACL_ORDER_ALLOW_DENY or ' + - 'ACL_ORDER_DENY_ALLOW') + 'order_to_check_allow_deny must be Order.ALLOW_DENY or ' + + 'Order.DENY_ALLOW') self.order_to_check_allow_deny = order_to_check_allow_deny self.default_answer = default_answer def __call__(self, x: Any) -> bool: """Returns True if x is allowed, False otherwise.""" logger.debug(f'SimpleACL checking {x}') - if self.order_to_check_allow_deny == ACL_ORDER_ALLOW_DENY: + if self.order_to_check_allow_deny == Order.ALLOW_DENY: logger.debug('Checking allowed first...') if self.check_allowed(x): logger.debug(f'{x} was allowed explicitly.') @@ -44,7 +54,7 @@ class SimpleACL(ABC): if self.check_denied(x): logger.debug(f'{x} was denied explicitly.') return False - elif self.order_to_check_allow_deny == ACL_ORDER_DENY_ALLOW: + elif self.order_to_check_allow_deny == Order.DENY_ALLOW: logger.debug('Checking denied first...') if self.check_denied(x): logger.debug(f'{x} was denied explicitly.') @@ -76,7 +86,7 @@ class SetBasedACL(SimpleACL): *, allow_set: Optional[Set[Any]] = None, deny_set: Optional[Set[Any]] = None, - order_to_check_allow_deny: int, + order_to_check_allow_deny: Order, default_answer: bool) -> None: super().__init__( order_to_check_allow_deny=order_to_check_allow_deny, @@ -85,24 +95,65 @@ class SetBasedACL(SimpleACL): self.allow_set = allow_set self.deny_set = deny_set + @overrides def check_allowed(self, x: Any) -> bool: if self.allow_set is None: return False return x in self.allow_set + @overrides def check_denied(self, x: Any) -> bool: if self.deny_set is None: return False return x in self.deny_set +class AllowListACL(SetBasedACL): + """Convenience subclass for a list that only allows known items. + i.e. a 'allowlist' + """ + def __init__(self, + *, + allow_set: Optional[Set[Any]]) -> None: + super().__init__( + allow_set = allow_set, + order_to_check_allow_deny = Order.ALLOW_DENY, + default_answer = False) + + +class DenyListACL(SetBasedACL): + """Convenience subclass for a list that only disallows known items. + i.e. a 'blocklist' + """ + def __init__(self, + *, + deny_set: Optional[Set[Any]]) -> None: + super().__init__( + deny_set = deny_set, + order_to_check_allow_deny = Order.ALLOW_DENY, + default_answer = True) + + +class BlockListACL(SetBasedACL): + """Convenience subclass for a list that only disallows known items. + i.e. a 'blocklist' + """ + def __init__(self, + *, + deny_set: Optional[Set[Any]]) -> None: + super().__init__( + deny_set = deny_set, + order_to_check_allow_deny = Order.ALLOW_DENY, + default_answer = True) + + class PredicateListBasedACL(SimpleACL): """An ACL that allows or denies by applying predicates.""" def __init__(self, *, - allow_predicate_list: List[Callable[[Any], bool]] = None, - deny_predicate_list: List[Callable[[Any], bool]] = None, - order_to_check_allow_deny: int, + allow_predicate_list: Sequence[Callable[[Any], bool]] = None, + deny_predicate_list: Sequence[Callable[[Any], bool]] = None, + order_to_check_allow_deny: Order, default_answer: bool) -> None: super().__init__( order_to_check_allow_deny=order_to_check_allow_deny, @@ -111,11 +162,13 @@ class PredicateListBasedACL(SimpleACL): self.allow_predicate_list = allow_predicate_list self.deny_predicate_list = deny_predicate_list + @overrides def check_allowed(self, x: Any) -> bool: if self.allow_predicate_list is None: return False return any(predicate(x) for predicate in self.allow_predicate_list) + @overrides def check_denied(self, x: Any) -> bool: if self.deny_predicate_list is None: return False @@ -128,7 +181,7 @@ class StringWildcardBasedACL(PredicateListBasedACL): *, allowed_patterns: Optional[List[str]] = None, denied_patterns: Optional[List[str]] = None, - order_to_check_allow_deny: int, + order_to_check_allow_deny: Order, default_answer: bool) -> None: allow_predicates = [] if allowed_patterns is not None: @@ -158,7 +211,7 @@ class StringREBasedACL(PredicateListBasedACL): *, allowed_regexs: Optional[List[re.Pattern]] = None, denied_regexs: Optional[List[re.Pattern]] = None, - order_to_check_allow_deny: int, + order_to_check_allow_deny: Order, default_answer: bool) -> None: allow_predicates = None if allowed_regexs is not None: @@ -182,21 +235,53 @@ class StringREBasedACL(PredicateListBasedACL): ) -class AnyCompoundACL(object): +class AnyCompoundACL(SimpleACL): """An ACL that allows if any of its subacls allow.""" - def __init__(self, subacls: List[SimpleACL]): - assert subacls is not None + def __init__(self, + *, + subacls: Optional[List[SimpleACL]] = None, + order_to_check_allow_deny: Order, + default_answer: bool) -> None: + super().__init__( + order_to_check_allow_deny = order_to_check_allow_deny, + default_answer = default_answer + ) self.subacls = subacls - def __call__(self, x: Any): + @overrides + def check_allowed(self, x: Any) -> bool: + if self.subacls is None: + return False return any(acl(x) for acl in self.subacls) + @overrides + def check_denied(self, x: Any) -> bool: + if self.subacls is None: + return False + return any(not acl(x) for acl in self.subacls) -class AllCompoundACL(object): + +class AllCompoundACL(SimpleACL): """An ACL that allows if all of its subacls allow.""" - def __init__(self, subacls: List[SimpleACL]): - assert subacls is not None + def __init__(self, + *, + subacls: Optional[List[SimpleACL]] = None, + order_to_check_allow_deny: Order, + default_answer: bool) -> None: + super().__init__( + order_to_check_allow_deny = order_to_check_allow_deny, + default_answer = default_answer + ) self.subacls = subacls - def __call__(self, x: Any): + @overrides + def check_allowed(self, x: Any) -> bool: + if self.subacls is None: + return False return all(acl(x) for acl in self.subacls) + + @overrides + def check_denied(self, x: Any) -> bool: + if self.subacls is None: + return False + return any(not acl(x) for acl in self.subacls)