3 from abc import ABC, abstractmethod
7 from typing import Any, Callable, List, Optional, Set
10 logger = logging.getLogger(__name__)
13 ACL_ORDER_ALLOW_DENY = 1
14 ACL_ORDER_DENY_ALLOW = 2
18 """A simple Access Control List interface."""
23 order_to_check_allow_deny: int,
26 if order_to_check_allow_deny not in (
27 ACL_ORDER_ALLOW_DENY, ACL_ORDER_DENY_ALLOW
30 'order_to_check_allow_deny must be ACL_ORDER_ALLOW_DENY or ' +
31 'ACL_ORDER_DENY_ALLOW')
32 self.order_to_check_allow_deny = order_to_check_allow_deny
33 self.default_answer = default_answer
35 def __call__(self, x: Any) -> bool:
36 """Returns True if x is allowed, False otherwise."""
37 logger.debug(f'SimpleACL checking {x}')
38 if self.order_to_check_allow_deny == ACL_ORDER_ALLOW_DENY:
39 logger.debug('Checking allowed first...')
40 if self.check_allowed(x):
41 logger.debug(f'{x} was allowed explicitly.')
43 logger.debug('Checking denied next...')
44 if self.check_denied(x):
45 logger.debug(f'{x} was denied explicitly.')
47 elif self.order_to_check_allow_deny == ACL_ORDER_DENY_ALLOW:
48 logger.debug('Checking denied first...')
49 if self.check_denied(x):
50 logger.debug(f'{x} was denied explicitly.')
52 if self.check_allowed(x):
53 logger.debug(f'{x} was allowed explicitly.')
57 f'{x} was not explicitly allowed or denied; ' +
58 f'using default answer ({self.default_answer})'
60 return self.default_answer
63 def check_allowed(self, x: Any) -> bool:
64 """Return True if x is explicitly allowed, False otherwise."""
68 def check_denied(self, x: Any) -> bool:
69 """Return True if x is explicitly denied, False otherwise."""
73 class SetBasedACL(SimpleACL):
74 """An ACL that allows or denies based on membership in a set."""
77 allow_set: Optional[Set[Any]] = None,
78 deny_set: Optional[Set[Any]] = None,
79 order_to_check_allow_deny: int,
80 default_answer: bool) -> None:
82 order_to_check_allow_deny=order_to_check_allow_deny,
83 default_answer=default_answer
85 self.allow_set = allow_set
86 self.deny_set = deny_set
88 def check_allowed(self, x: Any) -> bool:
89 if self.allow_set is None:
91 return x in self.allow_set
93 def check_denied(self, x: Any) -> bool:
94 if self.deny_set is None:
96 return x in self.deny_set
99 class PredicateListBasedACL(SimpleACL):
100 """An ACL that allows or denies by applying predicates."""
103 allow_predicate_list: List[Callable[[Any], bool]] = None,
104 deny_predicate_list: List[Callable[[Any], bool]] = None,
105 order_to_check_allow_deny: int,
106 default_answer: bool) -> None:
108 order_to_check_allow_deny=order_to_check_allow_deny,
109 default_answer=default_answer
111 self.allow_predicate_list = allow_predicate_list
112 self.deny_predicate_list = deny_predicate_list
114 def check_allowed(self, x: Any) -> bool:
115 if self.allow_predicate_list is None:
117 return any(predicate(x) for predicate in self.allow_predicate_list)
119 def check_denied(self, x: Any) -> bool:
120 if self.deny_predicate_list is None:
122 return any(predicate(x) for predicate in self.deny_predicate_list)
125 class StringWildcardBasedACL(PredicateListBasedACL):
126 """An ACL that allows or denies based on string glob (*, ?) patterns."""
129 allowed_patterns: Optional[List[str]] = None,
130 denied_patterns: Optional[List[str]] = None,
131 order_to_check_allow_deny: int,
132 default_answer: bool) -> None:
133 allow_predicates = []
134 if allowed_patterns is not None:
135 for pattern in allowed_patterns:
136 allow_predicates.append(
137 lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
139 deny_predicates = None
140 if denied_patterns is not None:
142 for pattern in denied_patterns:
143 deny_predicates.append(
144 lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
148 allow_predicate_list=allow_predicates,
149 deny_predicate_list=deny_predicates,
150 order_to_check_allow_deny=order_to_check_allow_deny,
151 default_answer=default_answer,
155 class StringREBasedACL(PredicateListBasedACL):
156 """An ACL that allows or denies by applying regexps."""
159 allowed_regexs: Optional[List[re.Pattern]] = None,
160 denied_regexs: Optional[List[re.Pattern]] = None,
161 order_to_check_allow_deny: int,
162 default_answer: bool) -> None:
163 allow_predicates = None
164 if allowed_regexs is not None:
165 allow_predicates = []
166 for pattern in allowed_regexs:
167 allow_predicates.append(
168 lambda x, pattern=pattern: pattern.match(x) is not None
170 deny_predicates = None
171 if denied_regexs is not None:
173 for pattern in denied_regexs:
174 deny_predicates.append(
175 lambda x, pattern=pattern: pattern.match(x) is not None
178 allow_predicate_list=allow_predicates,
179 deny_predicate_list=deny_predicates,
180 order_to_check_allow_deny=order_to_check_allow_deny,
181 default_answer=default_answer,
185 class AnyCompoundACL(object):
186 """An ACL that allows if any of its subacls allow."""
187 def __init__(self, subacls: List[SimpleACL]):
188 assert subacls is not None
189 self.subacls = subacls
191 def __call__(self, x: Any):
192 return any(acl(x) for acl in self.subacls)
195 class AllCompoundACL(object):
196 """An ACL that allows if all of its subacls allow."""
197 def __init__(self, subacls: List[SimpleACL]):
198 assert subacls is not None
199 self.subacls = subacls
201 def __call__(self, x: Any):
202 return all(acl(x) for acl in self.subacls)