3 from abc import ABC, abstractmethod
8 from typing import Any, Callable, List, Optional, Set
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 PredicateListBasedACL(SimpleACL):
134 """An ACL that allows or denies by applying predicates."""
137 allow_predicate_list: List[Callable[[Any], bool]] = None,
138 deny_predicate_list: List[Callable[[Any], bool]] = None,
139 order_to_check_allow_deny: Order,
140 default_answer: bool) -> None:
142 order_to_check_allow_deny=order_to_check_allow_deny,
143 default_answer=default_answer
145 self.allow_predicate_list = allow_predicate_list
146 self.deny_predicate_list = deny_predicate_list
148 def check_allowed(self, x: Any) -> bool:
149 if self.allow_predicate_list is None:
151 return any(predicate(x) for predicate in self.allow_predicate_list)
153 def check_denied(self, x: Any) -> bool:
154 if self.deny_predicate_list is None:
156 return any(predicate(x) for predicate in self.deny_predicate_list)
159 class StringWildcardBasedACL(PredicateListBasedACL):
160 """An ACL that allows or denies based on string glob (*, ?) patterns."""
163 allowed_patterns: Optional[List[str]] = None,
164 denied_patterns: Optional[List[str]] = None,
165 order_to_check_allow_deny: Order,
166 default_answer: bool) -> None:
167 allow_predicates = []
168 if allowed_patterns is not None:
169 for pattern in allowed_patterns:
170 allow_predicates.append(
171 lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
173 deny_predicates = None
174 if denied_patterns is not None:
176 for pattern in denied_patterns:
177 deny_predicates.append(
178 lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
182 allow_predicate_list=allow_predicates,
183 deny_predicate_list=deny_predicates,
184 order_to_check_allow_deny=order_to_check_allow_deny,
185 default_answer=default_answer,
189 class StringREBasedACL(PredicateListBasedACL):
190 """An ACL that allows or denies by applying regexps."""
193 allowed_regexs: Optional[List[re.Pattern]] = None,
194 denied_regexs: Optional[List[re.Pattern]] = None,
195 order_to_check_allow_deny: Order,
196 default_answer: bool) -> None:
197 allow_predicates = None
198 if allowed_regexs is not None:
199 allow_predicates = []
200 for pattern in allowed_regexs:
201 allow_predicates.append(
202 lambda x, pattern=pattern: pattern.match(x) is not None
204 deny_predicates = None
205 if denied_regexs is not None:
207 for pattern in denied_regexs:
208 deny_predicates.append(
209 lambda x, pattern=pattern: pattern.match(x) is not None
212 allow_predicate_list=allow_predicates,
213 deny_predicate_list=deny_predicates,
214 order_to_check_allow_deny=order_to_check_allow_deny,
215 default_answer=default_answer,
219 class AnyCompoundACL(SimpleACL):
220 """An ACL that allows if any of its subacls allow."""
223 subacls: Optional[List[SimpleACL]] = None,
224 order_to_check_allow_deny: Order,
225 default_answer: bool) -> None:
227 order_to_check_allow_deny = order_to_check_allow_deny,
228 default_answer = default_answer
230 self.subacls = subacls
232 def check_allowed(self, x: Any) -> bool:
233 if self.subacls is None:
235 return any(acl(x) for acl in self.subacls)
237 def check_denied(self, x: Any) -> bool:
238 if self.subacls is None:
240 return any(not acl(x) for acl in self.subacls)
243 class AllCompoundACL(SimpleACL):
244 """An ACL that allows if all of its subacls allow."""
247 subacls: Optional[List[SimpleACL]] = None,
248 order_to_check_allow_deny: Order,
249 default_answer: bool) -> None:
251 order_to_check_allow_deny = order_to_check_allow_deny,
252 default_answer = default_answer
254 self.subacls = subacls
256 def check_allowed(self, x: Any) -> bool:
257 if self.subacls is None:
259 return all(acl(x) for acl in self.subacls)
261 def check_denied(self, x: Any) -> bool:
262 if self.subacls is None:
264 return any(not acl(x) for acl in self.subacls)