summaryrefslogtreecommitdiff
path: root/acl.py
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2021-07-08 20:08:22 -0700
committerScott Gasch <[email protected]>2021-07-08 20:08:22 -0700
commite516059c716537259c601c022cc3bad44025385e (patch)
tree62d3cf9404cbae373cdd4786634281433bb4a7c5 /acl.py
parentc086317533f8fdc53cd9b77863e7061b4b765f92 (diff)
Rename simple_acl -> acl
Diffstat (limited to 'acl.py')
-rw-r--r--acl.py202
1 files changed, 202 insertions, 0 deletions
diff --git a/acl.py b/acl.py
new file mode 100644
index 0000000..5040304
--- /dev/null
+++ b/acl.py
@@ -0,0 +1,202 @@
+#!/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."""
+ logger.debug(f'SimpleACL checking {x}')
+ if self.order_to_check_allow_deny == ACL_ORDER_ALLOW_DENY:
+ logger.debug('Checking allowed first...')
+ if self.check_allowed(x):
+ logger.debug(f'{x} was allowed explicitly.')
+ return True
+ logger.debug('Checking denied next...')
+ 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:
+ logger.debug('Checking denied first...')
+ if self.check_denied(x):
+ logger.debug(f'{x} was denied explicitly.')
+ return False
+ if self.check_allowed(x):
+ logger.debug(f'{x} was allowed explicitly.')
+ return True
+
+ logger.debug(
+ f'{x} was not explicitly allowed or denied; ' +
+ f'using default answer ({self.default_answer})'
+ )
+ return self.default_answer
+
+ @abstractmethod
+ def check_allowed(self, x: Any) -> bool:
+ """Return True if x is explicitly allowed, False otherwise."""
+ pass
+
+ @abstractmethod
+ def check_denied(self, x: Any) -> bool:
+ """Return True if x is explicitly denied, False otherwise."""
+ pass
+
+
+class SetBasedACL(SimpleACL):
+ """An ACL that allows or denies based on membership in a set."""
+ 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):
+ """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,
+ 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):
+ """An ACL that allows or denies based on string glob (*, ?) patterns."""
+ 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):
+ """An ACL that allows or denies by applying regexps."""
+ 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 AnyCompoundACL(object):
+ """An ACL that allows if any of its subacls allow."""
+ def __init__(self, subacls: List[SimpleACL]):
+ assert subacls is not None
+ self.subacls = subacls
+
+ def __call__(self, x: Any):
+ return any(acl(x) for acl in self.subacls)
+
+
+class AllCompoundACL(object):
+ """An ACL that allows if all of its subacls allow."""
+ def __init__(self, subacls: List[SimpleACL]):
+ assert subacls is not None
+ self.subacls = subacls
+
+ def __call__(self, x: Any):
+ return all(acl(x) for acl in self.subacls)