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."""
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 '
40 self.order_to_check_allow_deny = order_to_check_allow_deny
41 self.default_answer = default_answer
43 def __call__(self, x: Any) -> bool:
44 """Returns True if x is allowed, False otherwise."""
45 logger.debug(f'SimpleACL checking {x}')
46 if self.order_to_check_allow_deny == Order.ALLOW_DENY:
47 logger.debug('Checking allowed first...')
48 if self.check_allowed(x):
49 logger.debug(f'{x} was allowed explicitly.')
51 logger.debug('Checking denied next...')
52 if self.check_denied(x):
53 logger.debug(f'{x} was denied explicitly.')
55 elif self.order_to_check_allow_deny == Order.DENY_ALLOW:
56 logger.debug('Checking denied first...')
57 if self.check_denied(x):
58 logger.debug(f'{x} was denied explicitly.')
60 if self.check_allowed(x):
61 logger.debug(f'{x} was allowed explicitly.')
65 f'{x} was not explicitly allowed or denied; '
66 + f'using default answer ({self.default_answer})'
68 return self.default_answer
71 def check_allowed(self, x: Any) -> bool:
72 """Return True if x is explicitly allowed, False otherwise."""
76 def check_denied(self, x: Any) -> bool:
77 """Return True if x is explicitly denied, False otherwise."""
81 class SetBasedACL(SimpleACL):
82 """An ACL that allows or denies based on membership in a set."""
87 allow_set: Optional[Set[Any]] = None,
88 deny_set: Optional[Set[Any]] = None,
89 order_to_check_allow_deny: Order,
93 order_to_check_allow_deny=order_to_check_allow_deny,
94 default_answer=default_answer,
96 self.allow_set = allow_set
97 self.deny_set = deny_set
100 def check_allowed(self, x: Any) -> bool:
101 if self.allow_set is None:
103 return x in self.allow_set
106 def check_denied(self, x: Any) -> bool:
107 if self.deny_set is None:
109 return x in self.deny_set
112 class AllowListACL(SetBasedACL):
113 """Convenience subclass for a list that only allows known items.
117 def __init__(self, *, allow_set: Optional[Set[Any]]) -> None:
120 order_to_check_allow_deny=Order.ALLOW_DENY,
121 default_answer=False,
125 class DenyListACL(SetBasedACL):
126 """Convenience subclass for a list that only disallows known items.
130 def __init__(self, *, deny_set: Optional[Set[Any]]) -> None:
133 order_to_check_allow_deny=Order.ALLOW_DENY,
138 class BlockListACL(SetBasedACL):
139 """Convenience subclass for a list that only disallows known items.
143 def __init__(self, *, deny_set: Optional[Set[Any]]) -> None:
146 order_to_check_allow_deny=Order.ALLOW_DENY,
151 class PredicateListBasedACL(SimpleACL):
152 """An ACL that allows or denies by applying predicates."""
157 allow_predicate_list: Sequence[Callable[[Any], bool]] = None,
158 deny_predicate_list: Sequence[Callable[[Any], bool]] = None,
159 order_to_check_allow_deny: Order,
160 default_answer: bool,
163 order_to_check_allow_deny=order_to_check_allow_deny,
164 default_answer=default_answer,
166 self.allow_predicate_list = allow_predicate_list
167 self.deny_predicate_list = deny_predicate_list
170 def check_allowed(self, x: Any) -> bool:
171 if self.allow_predicate_list is None:
173 return any(predicate(x) for predicate in self.allow_predicate_list)
176 def check_denied(self, x: Any) -> bool:
177 if self.deny_predicate_list is None:
179 return any(predicate(x) for predicate in self.deny_predicate_list)
182 class StringWildcardBasedACL(PredicateListBasedACL):
183 """An ACL that allows or denies based on string glob (*, ?) patterns."""
188 allowed_patterns: Optional[List[str]] = None,
189 denied_patterns: Optional[List[str]] = None,
190 order_to_check_allow_deny: Order,
191 default_answer: bool,
193 allow_predicates = []
194 if allowed_patterns is not None:
195 for pattern in allowed_patterns:
196 allow_predicates.append(
197 lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
199 deny_predicates = None
200 if denied_patterns is not None:
202 for pattern in denied_patterns:
203 deny_predicates.append(
204 lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
208 allow_predicate_list=allow_predicates,
209 deny_predicate_list=deny_predicates,
210 order_to_check_allow_deny=order_to_check_allow_deny,
211 default_answer=default_answer,
215 class StringREBasedACL(PredicateListBasedACL):
216 """An ACL that allows or denies by applying regexps."""
221 allowed_regexs: Optional[List[re.Pattern]] = None,
222 denied_regexs: Optional[List[re.Pattern]] = None,
223 order_to_check_allow_deny: Order,
224 default_answer: bool,
226 allow_predicates = None
227 if allowed_regexs is not None:
228 allow_predicates = []
229 for pattern in allowed_regexs:
230 allow_predicates.append(
231 lambda x, pattern=pattern: pattern.match(x) is not None
233 deny_predicates = None
234 if denied_regexs is not None:
236 for pattern in denied_regexs:
237 deny_predicates.append(
238 lambda x, pattern=pattern: pattern.match(x) is not None
241 allow_predicate_list=allow_predicates,
242 deny_predicate_list=deny_predicates,
243 order_to_check_allow_deny=order_to_check_allow_deny,
244 default_answer=default_answer,
248 class AnyCompoundACL(SimpleACL):
249 """An ACL that allows if any of its subacls allow."""
254 subacls: Optional[List[SimpleACL]] = None,
255 order_to_check_allow_deny: Order,
256 default_answer: bool,
259 order_to_check_allow_deny=order_to_check_allow_deny,
260 default_answer=default_answer,
262 self.subacls = subacls
265 def check_allowed(self, x: Any) -> bool:
266 if self.subacls is None:
268 return any(acl(x) for acl in self.subacls)
271 def check_denied(self, x: Any) -> bool:
272 if self.subacls is None:
274 return any(not acl(x) for acl in self.subacls)
277 class AllCompoundACL(SimpleACL):
278 """An ACL that allows if all of its subacls allow."""
283 subacls: Optional[List[SimpleACL]] = None,
284 order_to_check_allow_deny: Order,
285 default_answer: bool,
288 order_to_check_allow_deny=order_to_check_allow_deny,
289 default_answer=default_answer,
291 self.subacls = subacls
294 def check_allowed(self, x: Any) -> bool:
295 if self.subacls is None:
297 return all(acl(x) for acl in self.subacls)
300 def check_denied(self, x: Any) -> bool:
301 if self.subacls is None:
303 return any(not acl(x) for acl in self.subacls)