Mercurial > farol > farolluz
annotate farolluz/producttree.py @ 47:652f59fbea3a
Docstring update + use a set where due
author | Benoît Allard <benoit.allard@greenbone.net> |
---|---|
date | Tue, 30 Dec 2014 12:29:07 +0100 |
parents | 1b7f3f4f1238 |
children |
rev | line source |
---|---|
0 | 1 # -*- coding: utf-8 -*- |
2 # | |
3 # Authors: | |
4 # BenoƮt Allard <benoit.allard@greenbone.net> | |
5 # | |
6 # Copyright: | |
7 # Copyright (C) 2014 Greenbone Networks GmbH | |
8 # | |
9 # This program is free software; you can redistribute it and/or | |
10 # modify it under the terms of the GNU General Public License | |
11 # as published by the Free Software Foundation; either version 2 | |
12 # of the License, or (at your option) any later version. | |
13 # | |
14 # This program is distributed in the hope that it will be useful, | |
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 # GNU General Public License for more details. | |
18 # | |
19 # You should have received a copy of the GNU General Public License | |
20 # along with this program; if not, write to the Free Software | |
21 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
22 | |
23 """\ | |
26
809db989cac5
Reorganize the code in smaller mpodules
Benoît Allard <benoit.allard@greenbone.net>
parents:
22
diff
changeset
|
24 Product Tree Objects related to CVRF Documents |
0 | 25 """ |
26 | |
26
809db989cac5
Reorganize the code in smaller mpodules
Benoît Allard <benoit.allard@greenbone.net>
parents:
22
diff
changeset
|
27 from .common import ValidationError |
0 | 28 |
29 class CVRFProductTree(object): | |
30 def __init__(self): | |
31 # All the branches, they can be order with their `parent` attribute | |
32 self._branches = [] | |
33 self._groups = [] | |
34 self._relationships = [] | |
35 self._products = [] | |
36 self._groups = [] | |
37 | |
38 def addProduct(self, product): | |
15
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
39 """ Add to the product list """ |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
40 self._products.append(product) |
0 | 41 |
42 def addRelationship(self, rel): | |
43 self._relationships.append(rel) | |
44 | |
45 def addGroup(self, group): | |
46 self._groups.append(group) | |
47 | |
48 def getProductForID(self, productid): | |
49 for product in self._products: | |
50 if product._productid == productid: | |
51 return product | |
52 raise KeyError(productid) | |
53 | |
43
b87f2a6e613a
Add CVE parsing (from OpenVAS GSA)
Benoît Allard <benoit.allard@greenbone.net>
parents:
26
diff
changeset
|
54 def getProductForCPE(self, cpe): |
b87f2a6e613a
Add CVE parsing (from OpenVAS GSA)
Benoît Allard <benoit.allard@greenbone.net>
parents:
26
diff
changeset
|
55 for product in self._products: |
b87f2a6e613a
Add CVE parsing (from OpenVAS GSA)
Benoît Allard <benoit.allard@greenbone.net>
parents:
26
diff
changeset
|
56 if product._cpe == cpe: |
b87f2a6e613a
Add CVE parsing (from OpenVAS GSA)
Benoît Allard <benoit.allard@greenbone.net>
parents:
26
diff
changeset
|
57 return product |
b87f2a6e613a
Add CVE parsing (from OpenVAS GSA)
Benoît Allard <benoit.allard@greenbone.net>
parents:
26
diff
changeset
|
58 raise KeyError(cpe) |
b87f2a6e613a
Add CVE parsing (from OpenVAS GSA)
Benoît Allard <benoit.allard@greenbone.net>
parents:
26
diff
changeset
|
59 |
0 | 60 def getGroupForID(self, groupid): |
61 for group in self._groups: | |
62 if group._groupid == groupid: | |
63 return group | |
64 raise KeyError(groupid) | |
65 | |
66 def decomposeProduct(self, productid): | |
67 """ In case of product defined as a relationship (product X installed | |
68 on OS Y), this gives us the following tuple: (OS, product). """ | |
69 product = self.getProductForID(productid) | |
70 parent = product._parent | |
71 if parent is None: | |
72 return (None, None) | |
73 if not isinstance(parent, CVRFRelationship): | |
74 return (None, None) | |
75 relationtype = parent._relationtype.replace(' ', '').lower() | |
76 if relationtype not in ('defaultcomponentof', 'installedon'): | |
77 return (None, None) | |
78 return ( | |
79 self.getProductForID(parent._relatestoproductreference), | |
80 self.getProductForID(parent._productreference) | |
81 ) | |
82 | |
83 def getBranch(self, path): | |
47
652f59fbea3a
Docstring update + use a set where due
Benoît Allard <benoit.allard@greenbone.net>
parents:
46
diff
changeset
|
84 """ path is a tuple of indexes """ |
0 | 85 if len(path) == 0: |
86 return self | |
87 branches = self._branches | |
88 node = None | |
89 for idx in path: | |
90 node = branches[idx] | |
91 branches = node._childs | |
92 return node | |
93 | |
94 def getBranches(self): | |
95 for branch in self._branches: | |
96 yield branch | |
97 for sub_branch in branch.getBranches(): | |
98 yield sub_branch | |
99 | |
100 def getPath(self): | |
101 return () | |
102 | |
103 def getNameOfRelationship(self, relationship): | |
104 if relationship is None: | |
105 return '' | |
106 return ' '.join((self.getProductForID(relationship._productreference)._name, 'as', | |
107 relationship._relationtype.lower(), | |
108 self.getProductForID(relationship._relatestoproductreference)._name)) | |
109 | |
110 def getOrphanedBranches(self, product=None): | |
111 """ The branches that could accept `product` as Product Definition """ | |
112 white_list = [] | |
113 if product is not None: | |
114 white_list = [product._parent] | |
115 for branch in self.getBranches(): | |
116 if (branch in white_list) or branch.isOrphaned(): | |
117 yield branch | |
118 | |
119 def getNotTerminalBranches(self, b2=None): | |
120 """\ | |
121 The branches that could accept `b2` as new sub-branches | |
122 Note that b2 and all its sub-branches cannot be listed | |
123 """ | |
47
652f59fbea3a
Docstring update + use a set where due
Benoît Allard <benoit.allard@greenbone.net>
parents:
46
diff
changeset
|
124 black_list = set() |
0 | 125 if b2 is not None: |
47
652f59fbea3a
Docstring update + use a set where due
Benoît Allard <benoit.allard@greenbone.net>
parents:
46
diff
changeset
|
126 black_list.add(b2) |
652f59fbea3a
Docstring update + use a set where due
Benoît Allard <benoit.allard@greenbone.net>
parents:
46
diff
changeset
|
127 black_list.update(b2.getBranches()) |
0 | 128 for branch in self.getBranches(): |
129 if branch in black_list: | |
130 continue | |
131 if branch._product is None: | |
132 yield branch | |
133 | |
134 def getOrphanedRelationships(self, product=None): | |
135 """ The relationships that need a product defninition """ | |
136 white_list = [] | |
137 if product is not None: | |
138 white_list = [product.getCurrentRelationship()] | |
139 for i, relationship in enumerate(self._relationships): | |
140 if (relationship in white_list) or relationship.isOrphaned(): | |
141 yield (i, relationship) | |
142 | |
143 def nbProducts(self): | |
144 """ Amount of 'raw' Products """ | |
145 return len([p for p in self._products if p._parent is self]) | |
146 | |
46
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
147 def importTree(self, tree): |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
148 """ tree is a list of tuple (type, value) like the one generated by |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
149 getTree() """ |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
150 if len(tree) == 0: |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
151 return self |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
152 found = None |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
153 for branch in self._branches: |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
154 if branch.getTree() == tree[:1]: |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
155 found = branch |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
156 if found is None: |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
157 found = CVRFProductBranch(tree[0][0], tree[0][1], self) |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
158 return found.importTree(tree, 1) |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
159 |
0 | 160 def validate(self): |
161 for branch in self._branches: | |
162 branch.validate() | |
163 productids = set() | |
164 for product in self._products: | |
165 product.validate() | |
166 if product._productid in productids: | |
167 raise ValidationError('Each ProductID must be unique (%s)' % product._productid) | |
168 productids.add(product._productid) | |
169 for relationship in self._relationships: | |
170 relationship.validate() | |
171 for productid in (relationship._productreference, | |
172 relationship._relatestoproductreference): | |
173 if productid not in productids: | |
174 raise ValidationError('ProductID %s is unknown' % productid) | |
175 groupids = set() | |
176 for group in self._groups: | |
177 group.validate() | |
178 if group._groupid in groupids: | |
179 raise ValidationError('Duplicated GroupID: %s' % group._groupid) | |
180 groupids.add(group._groupid) | |
181 for productid in group._productids: | |
182 if productid not in productids: | |
183 raise ValidationError('ProductID %s is unknown' % productid) | |
184 return productids, groupids | |
185 | |
186 def __str__(self): | |
187 return 'Products: %s' % '\n'.join(str(p) for p in self._products) | |
188 | |
189 | |
190 class CVRFProductBranch(object): | |
191 TYPES = ('Vendor', 'Product Family', 'Product Name', 'Product Version', | |
192 'Patch Level', 'Service Pack', 'Architecture', 'Language', | |
193 'Legacy', 'Specification') | |
194 def __init__(self, _type, name, parentbranch): | |
195 self._type = _type | |
196 self._name = name | |
197 self._childs = [] | |
198 self._product = None | |
15
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
199 self.link(parentbranch) |
0 | 200 |
201 def getParent(self): | |
202 return self._parentbranch | |
203 | |
204 def getPath(self, string=False): | |
47
652f59fbea3a
Docstring update + use a set where due
Benoît Allard <benoit.allard@greenbone.net>
parents:
46
diff
changeset
|
205 """ return the path to that branch element as a tuple or as '/' |
652f59fbea3a
Docstring update + use a set where due
Benoît Allard <benoit.allard@greenbone.net>
parents:
46
diff
changeset
|
206 separated string """ |
0 | 207 if self.isRoot(): |
208 for i, b in enumerate(self._parentbranch._branches): | |
209 if b is self: | |
210 if string: | |
211 return '%d' % i | |
212 return (i, ) | |
213 else: | |
214 for i, b in enumerate(self._parentbranch._childs): | |
215 if b is self: | |
216 if string: | |
217 return '/'.join([self._parentbranch.getPath(string), '%d' % i]) | |
218 return self._parentbranch.getPath(string) + (i,) | |
219 if string: | |
220 return '' | |
221 return () | |
222 | |
223 def getTree(self): | |
224 """ this returns a list of tuples (type, name) leading to here""" | |
225 if self.isRoot(): | |
226 return [(self._type, self._name)] | |
227 return self._parentbranch.getTree() + [(self._type, self._name)] | |
228 | |
229 def getName(self): | |
230 return ' / '.join("%s: %s" % (type_, name) for type_, name in self.getTree()) | |
231 | |
232 def getParentPath(self): | |
233 """ return as string the path to the parent """ | |
234 return '/'.join('%s' % p for p in self.getPath()[:-1]) | |
235 | |
236 def isRoot(self): | |
237 return isinstance(self._parentbranch, CVRFProductTree) | |
238 | |
239 def isOrphaned(self): | |
240 """ Has no childs and no product """ | |
241 return len(self._childs) == 0 and (self._product is None) | |
242 | |
243 def getBranches(self): | |
244 for branch in self._childs: | |
245 yield branch | |
246 for sub_branch in branch.getBranches(): | |
247 yield sub_branch | |
248 | |
249 def unlink(self): | |
250 """ Unset our _parent, and remove us from the _parent._childs """ | |
251 if self.isRoot(): | |
252 self.getParent()._branches.remove(self) | |
253 else: | |
254 self.getParent()._childs.remove(self) | |
255 self._parentbranch = None | |
256 | |
15
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
257 def link(self, parent): |
47
652f59fbea3a
Docstring update + use a set where due
Benoît Allard <benoit.allard@greenbone.net>
parents:
46
diff
changeset
|
258 """ Set the parent, and add ourself to our parent's childs """ |
15
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
259 self._parentbranch = parent |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
260 if self.isRoot(): |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
261 parent._branches.append(self) |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
262 else: |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
263 parent._childs.append(self) |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
264 |
46
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
265 def importTree(self, tree, index): |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
266 """ tree is a list of tuple (type, value) like the one generated by |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
267 getTree() """ |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
268 if len(tree) == index: |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
269 return self |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
270 found = None |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
271 for branch in self._childs: |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
272 if branch.getTree() == tree[:index + 1]: |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
273 found = branch |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
274 if found is None: |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
275 found = CVRFProductBranch(tree[index][0], tree[index][1], self) |
1b7f3f4f1238
Add the possibility to batch-import trees
Benoît Allard <benoit.allard@greenbone.net>
parents:
43
diff
changeset
|
276 return found.importTree(tree, index + 1) |
15
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
277 |
0 | 278 def validate(self): |
279 if not self._type: | |
280 raise ValidationError('A Branch must have a Type') | |
281 if self._type not in self.TYPES: | |
282 raise ValidationError('A Branch Type must be one of %s' % ', '.join(self.TYPES)) | |
283 if not self._name: | |
284 raise ValidationError('A Branch must have a Name') | |
285 for branch in self._childs: | |
286 branch.validate() | |
287 if self.isOrphaned(): | |
288 raise ValidationError('A Branch must have at least a sub-product or sub-branches') | |
289 | |
290 def __str__(self): | |
291 return "%s: %s" % (self._type, self._name) | |
292 | |
293 | |
294 class CVRFFullProductName(object): | |
295 def __init__(self, productid, name, parent, cpe=None): | |
296 self._productid = productid | |
297 self._name = name | |
15
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
298 self._cpe = cpe |
0 | 299 # Can be None (directly under the tree), a ProductBranch, or a |
300 # Relationship | |
15
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
301 self.link(parent) |
0 | 302 |
303 def isRoot(self): | |
304 return isinstance(self._parent, CVRFProductTree) | |
305 | |
306 def isRelationship(self): | |
307 return isinstance(self._parent, CVRFRelationship) | |
308 | |
309 def getTree(self): | |
310 if not isinstance(self._parent, CVRFProductBranch): | |
311 return [] | |
312 return self._parent.getTree() | |
313 | |
314 def getParentPath(self): | |
315 if self.isRoot() or self.isRelationship(): | |
316 return '' | |
317 return self._parent.getPath(True) | |
318 | |
319 def getCurrentRelationship(self): | |
320 if self.isRelationship(): | |
321 return self._parent | |
322 return None | |
323 | |
324 def unlink(self): | |
15
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
325 """ Unset our _parent, and remove us from the _parent._childs |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
326 We are still in the product list. |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
327 """ |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
328 if not self.isRoot(): |
0 | 329 self._parent._product = None |
330 self._parent = None | |
331 | |
15
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
332 def link(self, parent): |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
333 self._parent = parent |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
334 if not self.isRoot(): |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
335 parent._product = self |
dcc946b30343
Consolidate productTree edition
Benoît Allard <benoit.allard@greenbone.net>
parents:
14
diff
changeset
|
336 |
0 | 337 def validate(self): |
338 if not self._productid: | |
339 raise ValidationError('A Product must have a ProductID') | |
340 if not self._name: | |
341 raise ValidationError('A Product must have a Name') | |
342 | |
343 def __str__(self): | |
344 return "%s (%s)" % (self._productid, self._name) | |
345 | |
346 | |
347 class CVRFRelationship(object): | |
348 TYPES = ('Default Component Of', 'Optional Component Of', | |
349 'External Component Of', 'Installed On', 'Installed With') | |
350 def __init__(self, productref, reltype, relatestoproductref): | |
351 self._productreference = productref | |
352 self._relationtype = reltype | |
353 self._relatestoproductreference = relatestoproductref | |
354 self._product = None | |
355 | |
356 def getParent(self): | |
357 """ All parent element of a FullProductName should implement that | |
358 method """ | |
359 return None | |
360 | |
361 def isOrphaned(self): | |
362 return self._product is None | |
363 | |
364 def validate(self): | |
365 if not self._productreference: | |
366 raise ValidationError('A Relationship must have a Product Reference') | |
367 if not self._relationtype: | |
368 raise ValidationError('A Relationship must have a Relation Type') | |
369 if self._relationtype not in self.TYPES: | |
370 raise ValidationError('Relation Type must be one of %s' % ', '.join(self.TYPES)) | |
371 if not self._relatestoproductreference: | |
372 raise ValidationError('A Relationship must have a "Relates To product Reference"') | |
373 if self._productreference == self._relatestoproductreference: | |
374 raise ValidationError('A Relationship cannot reference twice the same Product') | |
375 | |
376 | |
377 class CVRFGroup(object): | |
378 def __init__(self, groupid): | |
379 self._groupid = groupid | |
380 self._description = None | |
381 self._productids = [] | |
382 | |
383 def setDescription(self, description): | |
384 self._description = description | |
385 | |
386 def addProductID(self, productid): | |
387 self._productids.append(productid) | |
388 | |
389 def getTitle(self): | |
390 if self._description: | |
391 return "%s (%d products)" % (self._description, len(self._productids)) | |
392 return "#%s (%d products)" % (self._groupid, len(self._productids)) | |
393 | |
394 def validate(self): | |
395 if not self._groupid: | |
396 raise ValidationError('A Group must have a GroupID') | |
397 if not self._productids or len(self._productids) < 2: | |
398 raise ValidationError('A Group must contain at least two products') | |
399 |