39129ce417206f7bc41dae80f92618de5a512c74
[python_utils.git] / simple_acl.py
1 #!/usr/bin/env python3
2
3 from abc import ABC, abstractmethod
4 import fnmatch
5 import logging
6 import re
7 from typing import Any, Callable, List, Optional, Set
8
9
10 logger = logging.getLogger(__name__)
11
12
13 ACL_ORDER_ALLOW_DENY = 1
14 ACL_ORDER_DENY_ALLOW = 2
15
16
17 class SimpleACL(ABC):
18     """A simple Access Control List interface."""
19
20     def __init__(
21         self,
22         *,
23         order_to_check_allow_deny: int,
24         default_answer: bool
25     ):
26         if order_to_check_allow_deny not in (
27                 ACL_ORDER_ALLOW_DENY, ACL_ORDER_DENY_ALLOW
28         ):
29             raise Exception(
30                 'order_to_check_allow_deny must be ACL_ORDER_ALLOW_DENY or ' +
31                 'ACL_ORDER_DENY_ALLOW')
32         self.order_to_check_allow_deny = order_to_check_allow_deny
33         self.default_answer = default_answer
34
35     def __call__(self, x: Any) -> bool:
36         """Returns True if x is allowed, False otherwise."""
37         if self.order_to_check_allow_deny == ACL_ORDER_ALLOW_DENY:
38             if self.check_allowed(x):
39                 return True
40             if self.check_denied(x):
41                 return False
42             return self.default_answer
43         elif self.order_to_check_allow_deny == ACL_ORDER_DENY_ALLOW:
44             if self.check_denied(x):
45                 return False
46             if self.check_allowed(x):
47                 return True
48             return self.default_answer
49         raise Exception('Should never get here.')
50
51     @abstractmethod
52     def check_allowed(self, x: Any) -> bool:
53         """Return True if x is allowed, False otherwise."""
54         pass
55
56     @abstractmethod
57     def check_denied(self, x: Any) -> bool:
58         """Return True if x is denied, False otherwise."""
59         pass
60
61
62 class SetBasedACL(SimpleACL):
63     def __init__(self,
64                  *,
65                  allow_set: Optional[Set[Any]] = None,
66                  deny_set: Optional[Set[Any]] = None,
67                  order_to_check_allow_deny: int,
68                  default_answer: bool) -> None:
69         super().__init__(
70             order_to_check_allow_deny=order_to_check_allow_deny,
71             default_answer=default_answer
72         )
73         self.allow_set = allow_set
74         self.deny_set = deny_set
75
76     def check_allowed(self, x: Any) -> bool:
77         if self.allow_set is None:
78             return False
79         return x in self.allow_set
80
81     def check_denied(self, x: Any) -> bool:
82         if self.deny_set is None:
83             return False
84         return x in self.deny_set
85
86
87 class PredicateListBasedACL(SimpleACL):
88     def __init__(self,
89                  *,
90                  allow_predicate_list: List[Callable[[Any], bool]] = None,
91                  deny_predicate_list: List[Callable[[Any], bool]] = None,
92                  order_to_check_allow_deny: int,
93                  default_answer: bool) -> None:
94         super().__init__(
95             order_to_check_allow_deny=order_to_check_allow_deny,
96             default_answer=default_answer
97         )
98         self.allow_predicate_list = allow_predicate_list
99         self.deny_predicate_list = deny_predicate_list
100
101     def check_allowed(self, x: Any) -> bool:
102         if self.allow_predicate_list is None:
103             return False
104         return any(predicate(x) for predicate in self.allow_predicate_list)
105
106     def check_denied(self, x: Any) -> bool:
107         if self.deny_predicate_list is None:
108             return False
109         return any(predicate(x) for predicate in self.deny_predicate_list)
110
111
112 class StringWildcardBasedACL(PredicateListBasedACL):
113     def __init__(self,
114                  *,
115                  allowed_patterns: Optional[List[str]] = None,
116                  denied_patterns: Optional[List[str]] = None,
117                  order_to_check_allow_deny: int,
118                  default_answer: bool) -> None:
119         allow_predicates = []
120         if allowed_patterns is not None:
121             for pattern in allowed_patterns:
122                 allow_predicates.append(
123                     lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
124                 )
125         deny_predicates = None
126         if denied_patterns is not None:
127             deny_predicates = []
128             for pattern in denied_patterns:
129                 deny_predicates.append(
130                     lambda x, pattern=pattern: fnmatch.fnmatch(x, pattern)
131                 )
132
133         super().__init__(
134             allow_predicate_list=allow_predicates,
135             deny_predicate_list=deny_predicates,
136             order_to_check_allow_deny=order_to_check_allow_deny,
137             default_answer=default_answer,
138         )
139
140
141 class StringREBasedACL(PredicateListBasedACL):
142     def __init__(self,
143                  *,
144                  allowed_regexs: Optional[List[re.Pattern]] = None,
145                  denied_regexs: Optional[List[re.Pattern]] = None,
146                  order_to_check_allow_deny: int,
147                  default_answer: bool) -> None:
148         allow_predicates = None
149         if allowed_regexs is not None:
150             allow_predicates = []
151             for pattern in allowed_regexs:
152                 allow_predicates.append(
153                     lambda x, pattern=pattern: pattern.match(x) is not None
154                 )
155         deny_predicates = None
156         if denied_regexs is not None:
157             deny_predicates = []
158             for pattern in denied_regexs:
159                 deny_predicates.append(
160                     lambda x, pattern=pattern: pattern.match(x) is not None
161                 )
162         super().__init__(
163             allow_predicate_list=allow_predicates,
164             deny_predicate_list=deny_predicates,
165             order_to_check_allow_deny=order_to_check_allow_deny,
166             default_answer=default_answer,
167         )
168
169
170 class CompoundACL(object):
171     ANY = 1
172     ALL = 2
173
174     def __init__(
175             self,
176             *,
177             subacls: Optional[List[SimpleACL]],
178             match_requirement: int = ALL
179     ) -> None:
180         self.subacls = subacls
181         if match_requirement not in (CompoundACL.ANY, CompoundACL.ALL):
182             raise Exception(
183                 'match_requirement must be CompoundACL.ANY or CompoundACL.ALL'
184             )
185         self.match_requirement = match_requirement
186
187     def __call__(self, x: Any) -> bool:
188         if self.match_requirement == CompoundACL.ANY:
189             return any(acl(x) for acl in self.subacls)
190         elif self.match_requirement == CompoundACL.ALL:
191             return all(acl(x) for acl in self.subacls)
192         raise Exception('Should never get here.')