3 from abc import ABC, abstractmethod
8 from typing import Any, Callable, List, Optional, Set, Sequence
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."""
32 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 '
42 self.order_to_check_allow_deny = order_to_check_allow_deny
43 self.default_answer = default_answer
45 def __call__(self, x: Any) -> bool:
46 """Returns True if x is allowed, False otherwise."""
47 logger.debug(f'SimpleACL checking {x}')
48 if self.order_to_check_allow_deny == Order.ALLOW_DENY:
49 logger.debug('Checking allowed first...')
50 if self.check_allowed(x):
51 logger.debug(f'{x} was allowed explicitly.')
53 logger.debug('Checking denied next...')
54 if self.check_denied(x):
55 logger.debug(f'{x} was denied explicitly.')
57 elif self.order_to_check_allow_deny == Order.DENY_ALLOW:
58 logger.debug('Checking denied first...')
59 if self.check_denied(x):
60 logger.debug(f'{x} was denied explicitly.')
62 if self.check_allowed(x):
63 logger.debug(f'{x} was allowed explicitly.')
67 f'{x} was not explicitly allowed or denied; '
68 + f'using default answer ({self.default_answer})'
70 return self.default_answer
73 def check_allowed(self, x: Any) -> bool:
74 """Return True if x is explicitly allowed, False otherwise."""
78 def check_denied(self, x: Any) -> bool:
79 """Return True if x is explicitly denied, False otherwise."""
83 class SetBasedACL(SimpleACL):
84 """An ACL that allows or denies based on membership in a set."""
89 allow_set: Optional[Set[Any]] = None,
90 deny_set: Optional[Set[Any]] = None,
91 order_to_check_allow_deny: Order,
95 order_to_check_allow_deny=order_to_check_allow_deny,
96 default_answer=default_answer,
98 self.allow_set = allow_set
99 self.deny_set = deny_set
102 def check_allowed(self, x: Any) -> bool:
103 if self.allow_set is None:
105 return x in self.allow_set
108 def check_denied(self, x: Any) -> bool:
109 if self.deny_set is None:
111 return x in self.deny_set
114 class AllowListACL(SetBasedACL):
115 """Convenience subclass for a list that only allows known items.
119 def __init__(self, *, allow_set: Optional[Set[Any]]) -> None:
122 order_to_check_allow_deny=Order.ALLOW_DENY,
123 default_answer=False,
127 class DenyListACL(SetBasedACL):
128 """Convenience subclass for a list that only disallows known items.
132 def __init__(self, *, deny_set: Optional[Set[Any]]) -> None:
135 order_to_check_allow_deny=Order.ALLOW_DENY,
140 class BlockListACL(SetBasedACL):
141 """Convenience subclass for a list that only disallows known items.
145 def __init__(self, *, deny_set: Optional[Set[Any]]) -> None:
148 order_to_check_allow_deny=Order.ALLOW_DENY,
153 class PredicateListBasedACL(SimpleACL):
154 """An ACL that allows or denies by applying predicates."""
159 allow_predicate_list: Sequence[Callable[[Any], bool]] = None,
160 deny_predicate_list: Sequence[Callable[[Any], bool]] = None,
161 order_to_check_allow_deny: Order,
162 default_answer: bool,
165 order_to_check_allow_deny=order_to_check_allow_deny,
166 default_answer=default_answer,
168 self.allow_predicate_list = allow_predicate_list
169 self.deny_predicate_list = deny_predicate_list
172 def check_allowed(self, x: Any) -> bool:
173 if self.allow_predicate_list is None:
175 return any(predicate(x) for predicate in self.allow_predicate_list)
178 def check_denied(self, x: Any) -> bool:
179 if self.deny_predicate_list is None:
181 return any(predicate(x) for predicate in self.deny_predicate_list)
184 class StringWildcardBasedACL(PredicateListBasedACL):
185 """An ACL that allows or denies based on string glob (*, ?) patterns."""
190 allowed_patterns: Optional[List[str]] = None,
191 denied_patterns: Optional[List[str]] = None,
192 order_to_check_allow_deny: Order,
193 default_answer: bool,
195 allow_predicates = []
196 if allowed_patterns is not None:
197 for pattern in allowed_patterns:
198 allow_predicates.append(
199 lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
201 deny_predicates = None
202 if denied_patterns is not None:
204 for pattern in denied_patterns:
205 deny_predicates.append(
206 lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
210 allow_predicate_list=allow_predicates,
211 deny_predicate_list=deny_predicates,
212 order_to_check_allow_deny=order_to_check_allow_deny,
213 default_answer=default_answer,
217 class StringREBasedACL(PredicateListBasedACL):
218 """An ACL that allows or denies by applying regexps."""
223 allowed_regexs: Optional[List[re.Pattern]] = None,
224 denied_regexs: Optional[List[re.Pattern]] = None,
225 order_to_check_allow_deny: Order,
226 default_answer: bool,
228 allow_predicates = None
229 if allowed_regexs is not None:
230 allow_predicates = []
231 for pattern in allowed_regexs:
232 allow_predicates.append(
233 lambda x, pattern=pattern: pattern.match(x) is not None
235 deny_predicates = None
236 if denied_regexs is not None:
238 for pattern in denied_regexs:
239 deny_predicates.append(
240 lambda x, pattern=pattern: pattern.match(x) is not None
243 allow_predicate_list=allow_predicates,
244 deny_predicate_list=deny_predicates,
245 order_to_check_allow_deny=order_to_check_allow_deny,
246 default_answer=default_answer,
250 class AnyCompoundACL(SimpleACL):
251 """An ACL that allows if any of its subacls allow."""
256 subacls: Optional[List[SimpleACL]] = None,
257 order_to_check_allow_deny: Order,
258 default_answer: bool,
261 order_to_check_allow_deny=order_to_check_allow_deny,
262 default_answer=default_answer,
264 self.subacls = subacls
267 def check_allowed(self, x: Any) -> bool:
268 if self.subacls is None:
270 return any(acl(x) for acl in self.subacls)
273 def check_denied(self, x: Any) -> bool:
274 if self.subacls is None:
276 return any(not acl(x) for acl in self.subacls)
279 class AllCompoundACL(SimpleACL):
280 """An ACL that allows if all of its subacls allow."""
285 subacls: Optional[List[SimpleACL]] = None,
286 order_to_check_allow_deny: Order,
287 default_answer: bool,
290 order_to_check_allow_deny=order_to_check_allow_deny,
291 default_answer=default_answer,
293 self.subacls = subacls
296 def check_allowed(self, x: Any) -> bool:
297 if self.subacls is None:
299 return all(acl(x) for acl in self.subacls)
302 def check_denied(self, x: Any) -> bool:
303 if self.subacls is None:
305 return any(not acl(x) for acl in self.subacls)