From 3bc4daf1edc121cd633429187392227f2fa61885 Mon Sep 17 00:00:00 2001 From: Scott Gasch Date: Thu, 8 Jul 2021 19:44:27 -0700 Subject: Lots of changes. --- simple_acl.py | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 simple_acl.py (limited to 'simple_acl.py') diff --git a/simple_acl.py b/simple_acl.py new file mode 100644 index 0000000..39129ce --- /dev/null +++ b/simple_acl.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 + +from abc import ABC, abstractmethod +import fnmatch +import logging +import re +from typing import Any, Callable, List, Optional, Set + + +logger = logging.getLogger(__name__) + + +ACL_ORDER_ALLOW_DENY = 1 +ACL_ORDER_DENY_ALLOW = 2 + + +class SimpleACL(ABC): + """A simple Access Control List interface.""" + + def __init__( + self, + *, + order_to_check_allow_deny: int, + default_answer: bool + ): + if order_to_check_allow_deny not in ( + ACL_ORDER_ALLOW_DENY, ACL_ORDER_DENY_ALLOW + ): + raise Exception( + 'order_to_check_allow_deny must be ACL_ORDER_ALLOW_DENY or ' + + 'ACL_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.""" + if self.order_to_check_allow_deny == ACL_ORDER_ALLOW_DENY: + if self.check_allowed(x): + return True + if self.check_denied(x): + return False + return self.default_answer + elif self.order_to_check_allow_deny == ACL_ORDER_DENY_ALLOW: + if self.check_denied(x): + return False + if self.check_allowed(x): + return True + return self.default_answer + raise Exception('Should never get here.') + + @abstractmethod + def check_allowed(self, x: Any) -> bool: + """Return True if x is allowed, False otherwise.""" + pass + + @abstractmethod + def check_denied(self, x: Any) -> bool: + """Return True if x is denied, False otherwise.""" + pass + + +class SetBasedACL(SimpleACL): + def __init__(self, + *, + allow_set: Optional[Set[Any]] = None, + deny_set: Optional[Set[Any]] = None, + order_to_check_allow_deny: int, + default_answer: bool) -> None: + super().__init__( + order_to_check_allow_deny=order_to_check_allow_deny, + default_answer=default_answer + ) + self.allow_set = allow_set + self.deny_set = deny_set + + def check_allowed(self, x: Any) -> bool: + if self.allow_set is None: + return False + return x in self.allow_set + + def check_denied(self, x: Any) -> bool: + if self.deny_set is None: + return False + return x in self.deny_set + + +class PredicateListBasedACL(SimpleACL): + 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, + default_answer: bool) -> None: + super().__init__( + order_to_check_allow_deny=order_to_check_allow_deny, + default_answer=default_answer + ) + self.allow_predicate_list = allow_predicate_list + self.deny_predicate_list = deny_predicate_list + + 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) + + def check_denied(self, x: Any) -> bool: + if self.deny_predicate_list is None: + return False + return any(predicate(x) for predicate in self.deny_predicate_list) + + +class StringWildcardBasedACL(PredicateListBasedACL): + def __init__(self, + *, + allowed_patterns: Optional[List[str]] = None, + denied_patterns: Optional[List[str]] = None, + order_to_check_allow_deny: int, + default_answer: bool) -> None: + allow_predicates = [] + if allowed_patterns is not None: + for pattern in allowed_patterns: + allow_predicates.append( + lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern) + ) + deny_predicates = None + if denied_patterns is not None: + deny_predicates = [] + for pattern in denied_patterns: + deny_predicates.append( + lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern) + ) + + super().__init__( + allow_predicate_list=allow_predicates, + deny_predicate_list=deny_predicates, + order_to_check_allow_deny=order_to_check_allow_deny, + default_answer=default_answer, + ) + + +class StringREBasedACL(PredicateListBasedACL): + def __init__(self, + *, + allowed_regexs: Optional[List[re.Pattern]] = None, + denied_regexs: Optional[List[re.Pattern]] = None, + order_to_check_allow_deny: int, + default_answer: bool) -> None: + allow_predicates = None + if allowed_regexs is not None: + allow_predicates = [] + for pattern in allowed_regexs: + allow_predicates.append( + lambda x, pattern=pattern: pattern.match(x) is not None + ) + deny_predicates = None + if denied_regexs is not None: + deny_predicates = [] + for pattern in denied_regexs: + deny_predicates.append( + lambda x, pattern=pattern: pattern.match(x) is not None + ) + super().__init__( + allow_predicate_list=allow_predicates, + deny_predicate_list=deny_predicates, + order_to_check_allow_deny=order_to_check_allow_deny, + default_answer=default_answer, + ) + + +class CompoundACL(object): + ANY = 1 + ALL = 2 + + def __init__( + self, + *, + subacls: Optional[List[SimpleACL]], + match_requirement: int = ALL + ) -> None: + self.subacls = subacls + if match_requirement not in (CompoundACL.ANY, CompoundACL.ALL): + raise Exception( + 'match_requirement must be CompoundACL.ANY or CompoundACL.ALL' + ) + self.match_requirement = match_requirement + + def __call__(self, x: Any) -> bool: + if self.match_requirement == CompoundACL.ANY: + return any(acl(x) for acl in self.subacls) + elif self.match_requirement == CompoundACL.ALL: + return all(acl(x) for acl in self.subacls) + raise Exception('Should never get here.') -- cgit v1.3