3 """This module defines various flavors of Access Control Lists."""
9 from abc import ABC, abstractmethod
10 from typing import Any, Callable, List, Optional, Sequence, Set
12 from overrides import overrides
14 # This module is commonly used by others in here and should avoid
15 # taking any unnecessary dependencies back on them.
17 logger = logging.getLogger(__name__)
20 class Order(enum.Enum):
21 """A helper to express the order of evaluation for allows/denies
22 in an Access Control List.
31 """A simple Access Control List interface."""
33 def __init__(self, *, order_to_check_allow_deny: Order, default_answer: bool):
34 if order_to_check_allow_deny not in (
39 'order_to_check_allow_deny must be Order.ALLOW_DENY or ' + 'Order.DENY_ALLOW'
41 self.order_to_check_allow_deny = order_to_check_allow_deny
42 self.default_answer = default_answer
44 def __call__(self, x: Any) -> bool:
45 """Returns True if x is allowed, False otherwise."""
46 logger.debug('SimpleACL checking %s', x)
47 if self.order_to_check_allow_deny == Order.ALLOW_DENY:
48 logger.debug('Checking allowed first...')
49 if self.check_allowed(x):
50 logger.debug('%s was allowed explicitly.', x)
52 logger.debug('Checking denied next...')
53 if self.check_denied(x):
54 logger.debug('%s was denied explicitly.', x)
56 elif self.order_to_check_allow_deny == Order.DENY_ALLOW:
57 logger.debug('Checking denied first...')
58 if self.check_denied(x):
59 logger.debug('%s was denied explicitly.', x)
61 if self.check_allowed(x):
62 logger.debug('%s was allowed explicitly.', x)
66 f'{x} was not explicitly allowed or denied; '
67 + f'using default answer ({self.default_answer})'
69 return self.default_answer
72 def check_allowed(self, x: Any) -> bool:
73 """Return True if x is explicitly allowed, False otherwise."""
77 def check_denied(self, x: Any) -> bool:
78 """Return True if x is explicitly denied, False otherwise."""
82 class SetBasedACL(SimpleACL):
83 """An ACL that allows or denies based on membership in a set."""
88 allow_set: Optional[Set[Any]] = None,
89 deny_set: Optional[Set[Any]] = None,
90 order_to_check_allow_deny: Order,
94 order_to_check_allow_deny=order_to_check_allow_deny,
95 default_answer=default_answer,
97 self.allow_set = allow_set
98 self.deny_set = deny_set
101 def check_allowed(self, x: Any) -> bool:
102 if self.allow_set is None:
104 return x in self.allow_set
107 def check_denied(self, x: Any) -> bool:
108 if self.deny_set is None:
110 return x in self.deny_set
113 class AllowListACL(SetBasedACL):
114 """Convenience subclass for a list that only allows known items.
118 def __init__(self, *, allow_set: Optional[Set[Any]]) -> None:
121 order_to_check_allow_deny=Order.ALLOW_DENY,
122 default_answer=False,
126 class DenyListACL(SetBasedACL):
127 """Convenience subclass for a list that only disallows known items.
131 def __init__(self, *, deny_set: Optional[Set[Any]]) -> None:
134 order_to_check_allow_deny=Order.ALLOW_DENY,
139 class BlockListACL(SetBasedACL):
140 """Convenience subclass for a list that only disallows known items.
144 def __init__(self, *, deny_set: Optional[Set[Any]]) -> None:
147 order_to_check_allow_deny=Order.ALLOW_DENY,
152 class PredicateListBasedACL(SimpleACL):
153 """An ACL that allows or denies by applying predicates."""
158 allow_predicate_list: Sequence[Callable[[Any], bool]] = None,
159 deny_predicate_list: Sequence[Callable[[Any], bool]] = None,
160 order_to_check_allow_deny: Order,
161 default_answer: bool,
164 order_to_check_allow_deny=order_to_check_allow_deny,
165 default_answer=default_answer,
167 self.allow_predicate_list = allow_predicate_list
168 self.deny_predicate_list = deny_predicate_list
171 def check_allowed(self, x: Any) -> bool:
172 if self.allow_predicate_list is None:
174 return any(predicate(x) for predicate in self.allow_predicate_list)
177 def check_denied(self, x: Any) -> bool:
178 if self.deny_predicate_list is None:
180 return any(predicate(x) for predicate in self.deny_predicate_list)
183 class StringWildcardBasedACL(PredicateListBasedACL):
184 """An ACL that allows or denies based on string glob (*, ?) patterns."""
189 allowed_patterns: Optional[List[str]] = None,
190 denied_patterns: Optional[List[str]] = None,
191 order_to_check_allow_deny: Order,
192 default_answer: bool,
194 allow_predicates = []
195 if allowed_patterns is not None:
196 for pattern in allowed_patterns:
197 allow_predicates.append(lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern))
198 deny_predicates = None
199 if denied_patterns is not None:
201 for pattern in denied_patterns:
202 deny_predicates.append(lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern))
205 allow_predicate_list=allow_predicates,
206 deny_predicate_list=deny_predicates,
207 order_to_check_allow_deny=order_to_check_allow_deny,
208 default_answer=default_answer,
212 class StringREBasedACL(PredicateListBasedACL):
213 """An ACL that allows or denies by applying regexps."""
218 allowed_regexs: Optional[List[re.Pattern]] = None,
219 denied_regexs: Optional[List[re.Pattern]] = None,
220 order_to_check_allow_deny: Order,
221 default_answer: bool,
223 allow_predicates = None
224 if allowed_regexs is not None:
225 allow_predicates = []
226 for pattern in allowed_regexs:
227 allow_predicates.append(lambda x, pattern=pattern: pattern.match(x) is not None)
228 deny_predicates = None
229 if denied_regexs is not None:
231 for pattern in denied_regexs:
232 deny_predicates.append(lambda x, pattern=pattern: pattern.match(x) is not None)
234 allow_predicate_list=allow_predicates,
235 deny_predicate_list=deny_predicates,
236 order_to_check_allow_deny=order_to_check_allow_deny,
237 default_answer=default_answer,
241 class AnyCompoundACL(SimpleACL):
242 """An ACL that allows if any of its subacls allow."""
247 subacls: Optional[List[SimpleACL]] = None,
248 order_to_check_allow_deny: Order,
249 default_answer: bool,
252 order_to_check_allow_deny=order_to_check_allow_deny,
253 default_answer=default_answer,
255 self.subacls = subacls
258 def check_allowed(self, x: Any) -> bool:
259 if self.subacls is None:
261 return any(acl(x) for acl in self.subacls)
264 def check_denied(self, x: Any) -> bool:
265 if self.subacls is None:
267 return any(not acl(x) for acl in self.subacls)
270 class AllCompoundACL(SimpleACL):
271 """An ACL that allows if all of its subacls allow."""
276 subacls: Optional[List[SimpleACL]] = None,
277 order_to_check_allow_deny: Order,
278 default_answer: bool,
281 order_to_check_allow_deny=order_to_check_allow_deny,
282 default_answer=default_answer,
284 self.subacls = subacls
287 def check_allowed(self, x: Any) -> bool:
288 if self.subacls is None:
290 return all(acl(x) for acl in self.subacls)
293 def check_denied(self, x: Any) -> bool:
294 if self.subacls is None:
296 return any(not acl(x) for acl in self.subacls)