1 # Copyright 2015, Tresys Technology, LLC 2 # 3 # This file is part of SETools. 4 # 5 # SETools is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Lesser General Public License as 7 # published by the Free Software Foundation, either version 2.1 of 8 # the License, or (at your option) any later version. 9 # 10 # SETools is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU Lesser General Public License for more details. 14 # 15 # You should have received a copy of the GNU Lesser General Public 16 # License along with SETools. If not, see 17 # <http://www.gnu.org/licenses/>. 18 # 19 """ 20 SETools descriptors. 21 22 These classes override how a class's attributes are get/set/deleted. 23 This is how the @property decorator works. 24 25 See https://docs.python.org/3/howto/descriptor.html 26 for more details. 27 """ 28 29 import re 30 from collections import defaultdict 31 from weakref import WeakKeyDictionary 32 33 # 34 # Query criteria descriptors 35 # 36 # Implementation note: if the name_regex attribute value 37 # is changed the criteria must be reset. 38 # 39 40 41 class CriteriaDescriptor(object): 42 43 """ 44 Single item criteria descriptor. 45 46 Parameters: 47 name_regex The name of instance's regex setting attribute; 48 used as name_regex below. If unset, 49 regular expressions will never be used. 50 lookup_function The name of the SELinuxPolicy lookup function, 51 e.g. lookup_type or lookup_boolean. 52 default_value The default value of the criteria. The default 53 is None. 54 55 Read-only instance attribute use (obj parameter): 56 policy The instance of SELinuxPolicy 57 name_regex This attribute is read to determine if 58 the criteria should be looked up or 59 compiled into a regex. If the attribute 60 does not exist, False is assumed. 61 """ 62 63 def __init__(self, name_regex=None, lookup_function=None, default_value=None): 64 assert name_regex or lookup_function, "A simple attribute should be used if there is " \ 65 "no regex nor lookup function." 66 self.regex = name_regex 67 self.default_value = default_value 68 self.lookup_function = lookup_function 69 70 # use weak references so instances can be 71 # garbage collected, rather than unnecessarily 72 # kept around due to this descriptor. 73 self.instances = WeakKeyDictionary() 74 75 def __get__(self, obj, objtype=None): 76 if obj is None: 77 return self 78 79 return self.instances.setdefault(obj, self.default_value) 80 81 def __set__(self, obj, value): 82 if not value: 83 self.instances[obj] = None 84 elif self.regex and getattr(obj, self.regex, False): 85 self.instances[obj] = re.compile(value) 86 elif self.lookup_function: 87 lookup = getattr(obj.policy, self.lookup_function) 88 self.instances[obj] = lookup(value) 89 else: 90 self.instances[obj] = value 91 92 93 class CriteriaSetDescriptor(CriteriaDescriptor): 94 95 """Descriptor for a set of criteria.""" 96 97 def __set__(self, obj, value): 98 if not value: 99 self.instances[obj] = None 100 elif self.regex and getattr(obj, self.regex, False): 101 self.instances[obj] = re.compile(value) 102 elif self.lookup_function: 103 lookup = getattr(obj.policy, self.lookup_function) 104 self.instances[obj] = set(lookup(v) for v in value) 105 else: 106 self.instances[obj] = set(value) 107 108 109 # 110 # NetworkX Graph Descriptors 111 # 112 # These descriptors are used to simplify all 113 # of the dictionary use in the NetworkX graph. 114 # 115 116 117 class NetworkXGraphEdgeDescriptor(object): 118 119 """ 120 Descriptor base class for NetworkX graph edge attributes. 121 122 Parameter: 123 name The edge property name 124 125 Instance class attribute use (obj parameter): 126 G The NetworkX graph 127 source The edge's source node 128 target The edge's target node 129 """ 130 131 def __init__(self, propname): 132 self.name = propname 133 134 def __get__(self, obj, objtype=None): 135 if obj is None: 136 return self 137 138 return obj.G[obj.source][obj.target][self.name] 139 140 def __set__(self, obj, value): 141 raise NotImplementedError 142 143 def __delete__(self, obj): 144 raise NotImplementedError 145 146 147 class EdgeAttrDict(NetworkXGraphEdgeDescriptor): 148 149 """A descriptor for edge attributes that are dictionaries.""" 150 151 def __set__(self, obj, value): 152 # None is a special value to initialize the attribute 153 if value is None: 154 obj.G[obj.source][obj.target][self.name] = defaultdict(list) 155 else: 156 raise ValueError("{0} dictionaries should not be assigned directly".format(self.name)) 157 158 def __delete__(self, obj): 159 obj.G[obj.source][obj.target][self.name].clear() 160 161 162 class EdgeAttrIntMax(NetworkXGraphEdgeDescriptor): 163 164 """ 165 A descriptor for edge attributes that are non-negative integers that always 166 keep the max assigned value until re-initialized. 167 """ 168 169 def __set__(self, obj, value): 170 # None is a special value to initialize 171 if value is None: 172 obj.G[obj.source][obj.target][self.name] = 0 173 else: 174 current_value = obj.G[obj.source][obj.target][self.name] 175 obj.G[obj.source][obj.target][self.name] = max(current_value, value) 176 177 178 class EdgeAttrList(NetworkXGraphEdgeDescriptor): 179 180 """A descriptor for edge attributes that are lists.""" 181 182 def __set__(self, obj, value): 183 # None is a special value to initialize 184 if value is None: 185 obj.G[obj.source][obj.target][self.name] = [] 186 else: 187 raise ValueError("{0} lists should not be assigned directly".format(self.name)) 188 189 def __delete__(self, obj): 190 # in Python3 a .clear() function was added for lists 191 # keep this implementation for Python 2 compat 192 del obj.G[obj.source][obj.target][self.name][:] 193