7 from abc import ABC, abstractmethod
8 from typing import Any, Callable, List, Optional, Sequence, Set
10 from overrides import overrides
12 # This module is commonly used by others in here and should avoid
13 # taking any unnecessary dependencies back on them.
15 logger = logging.getLogger(__name__)
18 class Order(enum.Enum):
19 """A helper to express the order of evaluation for allows/denies
20 in an Access Control List.
29 """A simple Access Control List interface."""
31 def __init__(self, *, order_to_check_allow_deny: Order, default_answer: bool):
32 if order_to_check_allow_deny not in (
37 'order_to_check_allow_deny must be Order.ALLOW_DENY or ' + 'Order.DENY_ALLOW'
39 self.order_to_check_allow_deny = order_to_check_allow_deny
40 self.default_answer = default_answer
42 def __call__(self, x: Any) -> bool:
43 """Returns True if x is allowed, False otherwise."""
44 logger.debug(f'SimpleACL checking {x}')
45 if self.order_to_check_allow_deny == Order.ALLOW_DENY:
46 logger.debug('Checking allowed first...')
47 if self.check_allowed(x):
48 logger.debug(f'{x} was allowed explicitly.')
50 logger.debug('Checking denied next...')
51 if self.check_denied(x):
52 logger.debug(f'{x} was denied explicitly.')
54 elif self.order_to_check_allow_deny == Order.DENY_ALLOW:
55 logger.debug('Checking denied first...')
56 if self.check_denied(x):
57 logger.debug(f'{x} was denied explicitly.')
59 if self.check_allowed(x):
60 logger.debug(f'{x} was allowed explicitly.')
64 f'{x} was not explicitly allowed or denied; '
65 + f'using default answer ({self.default_answer})'
67 return self.default_answer
70 def check_allowed(self, x: Any) -> bool:
71 """Return True if x is explicitly allowed, False otherwise."""
75 def check_denied(self, x: Any) -> bool:
76 """Return True if x is explicitly denied, False otherwise."""
80 class SetBasedACL(SimpleACL):
81 """An ACL that allows or denies based on membership in a set."""
86 allow_set: Optional[Set[Any]] = None,
87 deny_set: Optional[Set[Any]] = None,
88 order_to_check_allow_deny: Order,
92 order_to_check_allow_deny=order_to_check_allow_deny,
93 default_answer=default_answer,
95 self.allow_set = allow_set
96 self.deny_set = deny_set
99 def check_allowed(self, x: Any) -> bool:
100 if self.allow_set is None:
102 return x in self.allow_set
105 def check_denied(self, x: Any) -> bool:
106 if self.deny_set is None:
108 return x in self.deny_set
111 class AllowListACL(SetBasedACL):
112 """Convenience subclass for a list that only allows known items.
116 def __init__(self, *, allow_set: Optional[Set[Any]]) -> None:
119 order_to_check_allow_deny=Order.ALLOW_DENY,
120 default_answer=False,
124 class DenyListACL(SetBasedACL):
125 """Convenience subclass for a list that only disallows known items.
129 def __init__(self, *, deny_set: Optional[Set[Any]]) -> None:
132 order_to_check_allow_deny=Order.ALLOW_DENY,
137 class BlockListACL(SetBasedACL):
138 """Convenience subclass for a list that only disallows known items.
142 def __init__(self, *, deny_set: Optional[Set[Any]]) -> None:
145 order_to_check_allow_deny=Order.ALLOW_DENY,
150 class PredicateListBasedACL(SimpleACL):
151 """An ACL that allows or denies by applying predicates."""
156 allow_predicate_list: Sequence[Callable[[Any], bool]] = None,
157 deny_predicate_list: Sequence[Callable[[Any], bool]] = None,
158 order_to_check_allow_deny: Order,
159 default_answer: bool,
162 order_to_check_allow_deny=order_to_check_allow_deny,
163 default_answer=default_answer,
165 self.allow_predicate_list = allow_predicate_list
166 self.deny_predicate_list = deny_predicate_list
169 def check_allowed(self, x: Any) -> bool:
170 if self.allow_predicate_list is None:
172 return any(predicate(x) for predicate in self.allow_predicate_list)
175 def check_denied(self, x: Any) -> bool:
176 if self.deny_predicate_list is None:
178 return any(predicate(x) for predicate in self.deny_predicate_list)
181 class StringWildcardBasedACL(PredicateListBasedACL):
182 """An ACL that allows or denies based on string glob (*, ?) patterns."""
187 allowed_patterns: Optional[List[str]] = None,
188 denied_patterns: Optional[List[str]] = None,
189 order_to_check_allow_deny: Order,
190 default_answer: bool,
192 allow_predicates = []
193 if allowed_patterns is not None:
194 for pattern in allowed_patterns:
195 allow_predicates.append(lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern))
196 deny_predicates = None
197 if denied_patterns is not None:
199 for pattern in denied_patterns:
200 deny_predicates.append(lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern))
203 allow_predicate_list=allow_predicates,
204 deny_predicate_list=deny_predicates,
205 order_to_check_allow_deny=order_to_check_allow_deny,
206 default_answer=default_answer,
210 class StringREBasedACL(PredicateListBasedACL):
211 """An ACL that allows or denies by applying regexps."""
216 allowed_regexs: Optional[List[re.Pattern]] = None,
217 denied_regexs: Optional[List[re.Pattern]] = None,
218 order_to_check_allow_deny: Order,
219 default_answer: bool,
221 allow_predicates = None
222 if allowed_regexs is not None:
223 allow_predicates = []
224 for pattern in allowed_regexs:
225 allow_predicates.append(lambda x, pattern=pattern: pattern.match(x) is not None)
226 deny_predicates = None
227 if denied_regexs is not None:
229 for pattern in denied_regexs:
230 deny_predicates.append(lambda x, pattern=pattern: pattern.match(x) is not None)
232 allow_predicate_list=allow_predicates,
233 deny_predicate_list=deny_predicates,
234 order_to_check_allow_deny=order_to_check_allow_deny,
235 default_answer=default_answer,
239 class AnyCompoundACL(SimpleACL):
240 """An ACL that allows if any of its subacls allow."""
245 subacls: Optional[List[SimpleACL]] = None,
246 order_to_check_allow_deny: Order,
247 default_answer: bool,
250 order_to_check_allow_deny=order_to_check_allow_deny,
251 default_answer=default_answer,
253 self.subacls = subacls
256 def check_allowed(self, x: Any) -> bool:
257 if self.subacls is None:
259 return any(acl(x) for acl in self.subacls)
262 def check_denied(self, x: Any) -> bool:
263 if self.subacls is None:
265 return any(not acl(x) for acl in self.subacls)
268 class AllCompoundACL(SimpleACL):
269 """An ACL that allows if all of its subacls allow."""
274 subacls: Optional[List[SimpleACL]] = None,
275 order_to_check_allow_deny: Order,
276 default_answer: bool,
279 order_to_check_allow_deny=order_to_check_allow_deny,
280 default_answer=default_answer,
282 self.subacls = subacls
285 def check_allowed(self, x: Any) -> bool:
286 if self.subacls is None:
288 return all(acl(x) for acl in self.subacls)
291 def check_denied(self, x: Any) -> bool:
292 if self.subacls is None:
294 return any(not acl(x) for acl in self.subacls)