#!/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.')