3 from abc import ABC, abstractmethod
8 from typing import Any, Callable, List, Optional, Set, Sequence
10 # This module is commonly used by others in here and should avoid
11 # taking any unnecessary dependencies back on them.
13 logger = logging.getLogger(__name__)
16 class Order(enum.Enum):
17 """A helper to express the order of evaluation for allows/denies
18 in an Access Control List.
26 """A simple Access Control List interface."""
31 order_to_check_allow_deny: Order,
34 if order_to_check_allow_deny not in (
35 Order.ALLOW_DENY, Order.DENY_ALLOW
38 '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."""
85 allow_set: Optional[Set[Any]] = None,
86 deny_set: Optional[Set[Any]] = None,
87 order_to_check_allow_deny: Order,
88 default_answer: bool) -> None:
90 order_to_check_allow_deny=order_to_check_allow_deny,
91 default_answer=default_answer
93 self.allow_set = allow_set
94 self.deny_set = deny_set
96 def check_allowed(self, x: Any) -> bool:
97 if self.allow_set is None:
99 return x in self.allow_set
101 def check_denied(self, x: Any) -> bool:
102 if self.deny_set is None:
104 return x in self.deny_set
107 class AllowListACL(SetBasedACL):
108 """Convenience subclass for a list that only allows known items.
113 allow_set: Optional[Set[Any]]) -> None:
115 allow_set = allow_set,
116 order_to_check_allow_deny = Order.ALLOW_DENY,
117 default_answer = False)
120 class DenyListACL(SetBasedACL):
121 """Convenience subclass for a list that only disallows known items.
126 deny_set: Optional[Set[Any]]) -> None:
129 order_to_check_allow_deny = Order.ALLOW_DENY,
130 default_answer = True)
133 class BlockListACL(SetBasedACL):
134 """Convenience subclass for a list that only disallows known items.
139 deny_set: Optional[Set[Any]]) -> None:
142 order_to_check_allow_deny = Order.ALLOW_DENY,
143 default_answer = True)
146 class PredicateListBasedACL(SimpleACL):
147 """An ACL that allows or denies by applying predicates."""
150 allow_predicate_list: Sequence[Callable[[Any], bool]] = None,
151 deny_predicate_list: Sequence[Callable[[Any], bool]] = None,
152 order_to_check_allow_deny: Order,
153 default_answer: bool) -> None:
155 order_to_check_allow_deny=order_to_check_allow_deny,
156 default_answer=default_answer
158 self.allow_predicate_list = allow_predicate_list
159 self.deny_predicate_list = deny_predicate_list
161 def check_allowed(self, x: Any) -> bool:
162 if self.allow_predicate_list is None:
164 return any(predicate(x) for predicate in self.allow_predicate_list)
166 def check_denied(self, x: Any) -> bool:
167 if self.deny_predicate_list is None:
169 return any(predicate(x) for predicate in self.deny_predicate_list)
172 class StringWildcardBasedACL(PredicateListBasedACL):
173 """An ACL that allows or denies based on string glob (*, ?) patterns."""
176 allowed_patterns: Optional[List[str]] = None,
177 denied_patterns: Optional[List[str]] = None,
178 order_to_check_allow_deny: Order,
179 default_answer: bool) -> None:
180 allow_predicates = []
181 if allowed_patterns is not None:
182 for pattern in allowed_patterns:
183 allow_predicates.append(
184 lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
186 deny_predicates = None
187 if denied_patterns is not None:
189 for pattern in denied_patterns:
190 deny_predicates.append(
191 lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
195 allow_predicate_list=allow_predicates,
196 deny_predicate_list=deny_predicates,
197 order_to_check_allow_deny=order_to_check_allow_deny,
198 default_answer=default_answer,
202 class StringREBasedACL(PredicateListBasedACL):
203 """An ACL that allows or denies by applying regexps."""
206 allowed_regexs: Optional[List[re.Pattern]] = None,
207 denied_regexs: Optional[List[re.Pattern]] = None,
208 order_to_check_allow_deny: Order,
209 default_answer: bool) -> None:
210 allow_predicates = None
211 if allowed_regexs is not None:
212 allow_predicates = []
213 for pattern in allowed_regexs:
214 allow_predicates.append(
215 lambda x, pattern=pattern: pattern.match(x) is not None
217 deny_predicates = None
218 if denied_regexs is not None:
220 for pattern in denied_regexs:
221 deny_predicates.append(
222 lambda x, pattern=pattern: pattern.match(x) is not None
225 allow_predicate_list=allow_predicates,
226 deny_predicate_list=deny_predicates,
227 order_to_check_allow_deny=order_to_check_allow_deny,
228 default_answer=default_answer,
232 class AnyCompoundACL(SimpleACL):
233 """An ACL that allows if any of its subacls allow."""
236 subacls: Optional[List[SimpleACL]] = None,
237 order_to_check_allow_deny: Order,
238 default_answer: bool) -> None:
240 order_to_check_allow_deny = order_to_check_allow_deny,
241 default_answer = default_answer
243 self.subacls = subacls
245 def check_allowed(self, x: Any) -> bool:
246 if self.subacls is None:
248 return any(acl(x) for acl in self.subacls)
250 def check_denied(self, x: Any) -> bool:
251 if self.subacls is None:
253 return any(not acl(x) for acl in self.subacls)
256 class AllCompoundACL(SimpleACL):
257 """An ACL that allows if all of its subacls allow."""
260 subacls: Optional[List[SimpleACL]] = None,
261 order_to_check_allow_deny: Order,
262 default_answer: bool) -> None:
264 order_to_check_allow_deny = order_to_check_allow_deny,
265 default_answer = default_answer
267 self.subacls = subacls
269 def check_allowed(self, x: Any) -> bool:
270 if self.subacls is None:
272 return all(acl(x) for acl in self.subacls)
274 def check_denied(self, x: Any) -> bool:
275 if self.subacls is None:
277 return any(not acl(x) for acl in self.subacls)