1 # Copyright (C) 2015 Ipsilon project Contributors, for licensee see COPYING
3 from ipsilon.util.log import Log
10 def __init__(self, mappings=None, allowed=None):
11 """ A Policy engine to filter attributes.
12 Mappings is a list of lists where the first value ia a list itself
13 and the second value is an attribute name or a list if the values
14 should go in a sub dictionary.
15 Note that mappings is a list and not a dictionary as this allows
16 to map the same original attribute to different resulting attributes
17 if wanted, by simply repeating the 'key list' with different values
20 Example: [[['extras', 'shoes'], 'shoeNumber']]
22 A '*' can be used to allow any attribute.
24 The default mapping is [[['*'], '*']]
25 This copies all attributes without transformation.
27 Allowed is a list of allowed attributes.
28 Normally mapping should be called before filtering, this means
29 allowed attributes should name the mapped attributes.
30 Allowed attributes can be multi-element lists
32 Example: ['fullname', ['groups', 'domain users']]
34 Allowed is '*' by default.
39 if not isinstance(mappings, list):
40 raise ValueError("Mappings should be a list not '%s'" %
43 if not isinstance(el, list):
44 raise ValueError("Mappings must be lists, not '%s'" %
47 raise ValueError("Mappings must contain 2 elements, "
49 if isinstance(el[0], list) and len(el[0]) > 2:
50 raise ValueError("1st Mapping element can contain at "
51 "most 2 values, found %d" % len(el[0]))
52 if isinstance(el[1], list) and len(el[1]) > 2:
53 raise ValueError("2nd Mapping element can contain at "
54 "most 2 values, found %d" % len(el[1]))
55 self.mappings = mappings
57 # default mapping, return all userdata and groups
59 self.mappings = [['*', '*']]
63 if not isinstance(allowed, list):
64 raise ValueError("Allowed should be a list not '%s'" %
66 self.allowed = allowed
68 def map_attributes(self, attributes):
70 if not isinstance(attributes, dict):
71 raise ValueError("Attributes must be dictionary, not %s" %
74 not_mapped = copy.deepcopy(attributes)
77 for (key, value) in self.mappings:
78 if not isinstance(key, list):
87 if not isinstance(value, list):
97 if prefix in attributes:
98 attr = attributes[prefix]
100 # '*' in a prefix matches nothing
106 if isinstance(attr, list):
108 if mapprefix not in mapped:
109 mapped[mapprefix] = list()
110 mapped[mapprefix].append(mapname)
112 if prefix in not_mapped:
113 while name in not_mapped[prefix]:
114 not_mapped[prefix].remove(name)
116 if mapname not in mapped:
117 mapped[mapname] = list()
118 mapped[mapname].append(attr[name])
120 if prefix in not_mapped:
121 del not_mapped[prefix]
123 mapin = copy.deepcopy(attr[name])
127 if mapprefix not in mapped:
128 mapped[mapprefix] = dict()
129 mapped[mapprefix].update({mapname: mapin})
131 mapped.update({mapname: mapin})
134 if prefix in not_mapped:
135 if name in not_mapped[prefix]:
136 del not_mapped[prefix][name]
137 elif name in not_mapped:
140 mapin = copy.deepcopy(attr)
141 # mapname is ignored if name == '*'
143 if mapprefix not in mapped:
144 mapped[mapprefix] = mapin
146 mapped[mapprefix].update(mapin)
150 if prefix in not_mapped:
151 del not_mapped[prefix]
157 return mapped, not_mapped
159 def filter_attributes(self, attributes, whitelist=True):
163 for name in self.allowed:
164 if isinstance(name, list):
167 if key in attributes:
168 attr = attributes[key]
171 elif isinstance(attr, dict):
172 if key not in filtered:
173 filtered[key] = dict()
175 filtered[key][value] = attr[value]
176 elif isinstance(attr, list):
177 if key not in filtered:
178 filtered[key] = list()
180 filtered[key].append(value)
184 if name in attributes:
185 filtered[name] = attributes[name]
187 filtered = attributes
192 # filtered contains the blacklisted
193 allowed = copy.deepcopy(attributes)
194 for lvl1 in filtered:
195 attr = filtered[lvl1]
196 if isinstance(attr, dict):
198 del allowed[lvl1][lvl2]
199 elif isinstance(attr, list):
201 allowed[lvl1].remove(lvl2)
204 if len(allowed[lvl1]) == 0:
210 if __name__ == '__main__':
215 t_attributes = {'onenameone': 'onevalueone',
216 'onenametwo': 'onevaluetwo',
217 'two': {'twonameone': 'twovalueone',
218 'twonametwo': 'twovaluetwo'},
219 'three': {'threenameone': 'threevalueone',
220 'threenametwo': 'threevaluetwo'},
221 'four': {'fournameone': 'fourvalueone',
222 'fournametwo': 'fourvaluetwo'},
223 'five': ['one', 'two', 'three'],
224 'six': ['one', 'two', 'three']}
226 # test defaults first
229 print 'Default attribute mapping'
230 m, n = p.map_attributes(t_attributes)
231 if m == t_attributes and n is None:
235 print 'FAIL: Expected %s\nObtained %s' % (t_attributes, m)
237 print 'Default attribute filtering'
238 f = p.filter_attributes(t_attributes)
239 if f == t_attributes:
243 print 'Expected %s\nObtained %s' % (t_attributes, f)
245 # test custom mappings and filters
246 t_mappings = [[['onenameone'], 'onemappedone'],
247 [['onenametwo'], 'onemappedtwo'],
249 [['three', 'threenameone'], 'threemappedone'],
250 [['three', 'threenameone'], 'threemappedbis'],
251 [['four', '*'], ['four', '*']],
252 [['five'], 'listfive'],
253 [['six', 'one'], ['six', 'mapone']]]
255 m_result = {'onemappedone': 'onevalueone',
256 'onemappedtwo': 'onevaluetwo',
257 'twonameone': 'twovalueone',
258 'twonametwo': 'twovaluetwo',
259 'threemappedone': 'threevalueone',
260 'threemappedbis': 'threevalueone',
261 'four': {'fournameone': 'fourvalueone',
262 'fournametwo': 'fourvaluetwo'},
263 'listfive': ['one', 'two', 'three'],
266 n_result = {'three': {'threenametwo': 'threevaluetwo'},
267 'six': ['two', 'three']}
269 t_allowed = ['twonameone',
270 ['four', 'fournametwo'],
271 ['listfive', 'three'],
274 f_result = {'twonameone': 'twovalueone',
275 'four': {'fournametwo': 'fourvaluetwo'},
276 'listfive': ['three'],
279 p = Policy(t_mappings, t_allowed)
281 print 'Custom attribute mapping'
282 m, n = p.map_attributes(t_attributes)
283 if m == m_result and n == n_result:
287 print 'Expected %s\nObtained %s' % (m_result, m)
289 print 'Custom attribute filtering'
290 f = p.filter_attributes(m)
295 print 'Expected %s\nObtained %s' % (f_result, f)
297 t2_allowed = ['onemappedone', 'twonametwo', 'threemappedone',
300 f2_result = {'onemappedtwo': 'onevaluetwo',
301 'twonameone': 'twovalueone',
302 'threemappedbis': 'threevalueone',
303 'four': {'fournameone': 'fourvalueone',
304 'fournametwo': 'fourvaluetwo'},
305 'listfive': ['one', 'three'],
308 p = Policy(t_mappings, t2_allowed)
310 print 'Custom attribute filtering 2'
311 m, _ = p.map_attributes(t_attributes)
312 f = p.filter_attributes(m, whitelist=False)
317 print 'Expected %s\nObtained %s' % (f2_result, f)