import repository from arizona
[raven.git] / apps / stork / storktpqualify.py
1 #!/usr/bin/env python
2 """
3 <Program Name>
4    storktpqualify.py
5
6 <Author>
7
8 <Purpose>
9    Given a set of package metadata, check them against the trusted packages file
10    and produce a list of which packages are accepted. The order of the list of
11    packages returned is not specified.
12
13    The main entrypoint is:
14         qualify_packages(package_list, tags, ignore_mantags = False)
15 """
16
17 import fnmatch
18
19 import arizonacrypt
20 import arizonageneral
21 import arizonareport
22 import storkexception
23 import storkrepolist
24 import storktrustedpackagesparse
25
26 glo_tpfiles = None
27 glo_root_tpfilename = None
28 glo_root_prefixdict = None
29
30 def check_provides(package, provides_str):
31    filter_provides = provides_str.split(",")
32    if ("*" in filter_provides):
33        return True
34
35    pack_provides = package.get("provides", [])
36
37    for item in pack_provides:
38       found = False
39       for pattern in filter_provides:
40          if fnmatch.fnmatch(item, pattern):
41             found = True
42
43       if (not found):
44           arizonareport.send_out(4, "tp_reject: rejecting provide of" + item)
45           return False
46
47    return True
48
49 def usermatch(tpentry, package):
50    """
51    <Purpose>
52       See if a package matches a tpentry
53
54    <Arguments>
55       tpentry - a trusted packages dictionary entry
56       package - a package dictionary
57       tags - list of tags that the package must have
58
59    <Returns>
60       True if tpentry matches pack, False if it does not
61    """
62    pack_name = package["filename"]
63    pack_hash = package["_metadatahash"]
64
65    # check the pattern
66    if not fnmatch.fnmatch(pack_name, tpentry['pattern']):
67       # this message would overwhelm the user on --ultraverbose
68       #arizonareport.send_out(4, "tp reject: " + pack_name + \
69       #                      " didn't match pattern " + tpentry['pattern'])
70       return False
71
72    # check the provides
73    if tpentry['provides']:
74       if not check_provides(package, tpentry['provides']):
75          arizonareport.send_out(4, "tp reject: " + package["filename"] + \
76                                     " provides dependencies that are" + \
77                                     " disallowed")
78          return False
79
80    return True
81
82 def tpmatch(tpentry, package, tags=None, ignore_hash=False, ignore_mantags=False):
83    """
84    <Purpose>
85       See if a package matches a tpentry
86
87    <Arguments>
88       tpentry - a trusted packages dictionary entry
89       package - a package dictionary
90       tags - list of tags that the package must have
91
92    <Returns>
93       True if tpentry matches pack, False if it does not
94    """
95    pack_name = package["filename"]
96    pack_hash = package["_metadatahash"]
97
98    # check the pattern
99    if not fnmatch.fnmatch(pack_name, tpentry['pattern']):
100       # this message would overwhelm the user on --ultraverbose
101       #arizonareport.send_out(4, "tp reject: " + pack_name + \
102       #                      " didn't match pattern " + tpentry['pattern'])
103       return False
104
105    # check the hash
106    if not ignore_hash:
107       hash = tpentry.get("hash", None)
108       if hash and (not fnmatch.fnmatch(pack_hash, hash)):
109          arizonareport.send_out(4, "tp reject: " + package["filename"] + \
110                                  " didn't match hash " + hash)
111          return False
112
113    # check the provides
114    if tpentry['provides']:
115       if not check_provides(package, tpentry['provides']):
116          arizonareport.send_out(4, "tp reject: " + package["filename"] + \
117                                     " provides dependencies that are" + \
118                                     " disallowed")
119          return False
120
121    # check to see if package contains tags the user wants
122    if tags:
123       if (tpentry['action'] == 'deny') and (not tepentry['tags']):
124          # if a deny rule does not contain any tags, then we apply it regardless
125          # of what tags the user asks for.
126          pass
127       else:
128          for tag in tags:
129             if not tag in tpentry['tags']:
130                arizonareport.send_out(4, "tp reject: " + package["filename"] + \
131                                       " does not contain tag: '" + tag + "'")
132                return False
133
134    # check to see if user specified tags the package wants
135    if (not ignore_mantags) and ('mantags' in tpentry):
136       for tag in tpentry['mantags']:
137          if (not tags) or (not tag in tags):
138              arizonareport.send_out(4, "tp reject: " + package["filename"] + \
139                                     " requires mandatory tag: '" + tag + "'")
140              return False
141
142    arizonareport.send_out(4, "tp match: " + package["filename"] + " action: " + tpentry['action'])
143
144    return True
145
146
147 def find_matching_tpentry(fn, package, tags, ignore_mantags = False):
148     """
149     <Purpose>
150         Find the first tpentry rule that matches a package
151
152     <Arguments>
153         fn - filename of tpfile
154         package - package dictionary
155         tags - set of tags to search for
156         ignore-mantags - if true, then mandatory tag rules will be ignored
157
158     <Returns>
159         tpentry - if a tpentry was found
160         None - otherwise
161     """
162     global glo_tpfiles
163
164     # XXX - unicode arizonageneral.check_type_simple(fn, "fn", str, "storktpqualify.find_matching_tpentry")
165     arizonageneral.check_type_simple(package, "package", dict, "storktpqualify.find_matching_tpentry")
166     arizonageneral.check_type_simple(tags, "tags", list, "storktpqualify.find_matching_tpentry")
167
168     if not fn in glo_tpfiles:
169         print "XXX not in glo_tpfiles:", fn
170         return None
171
172     for tpentry in glo_tpfiles[fn]:
173         # 'PACKAGE' entries specify trusted packages
174         if tpentry["kind"] == "PACKAGE":
175             if tpmatch(tpentry, package, tags, ignore_mantags=ignore_mantags):
176                 return tpentry
177         elif tpentry["kind"] == "USER":
178             if usermatch(tpentry, package):
179                 child_tpentry = find_matching_tpentry(tpentry['tpfilename'], package, tags, ignore_mantags)
180                 if (child_tpentry != None):
181                     if (tpentry["action"] == "any") or (tpentry["action"] == child_tpentry["action"]):
182                         return child_tpentry
183
184     return None
185
186 def parse_tpfiles():
187     global glo_tpfiles, glo_root_tpfilename, glo_root_prefixdict
188     if (glo_tpfiles == None):
189         (tpfilename, tprepo, tpprefixdict) = storkrepolist.find_file_kind("tpfiles", "tpfile")
190         if not tpfilename:
191           arizonareport.send_error(0, "Failed to locate trusted package file")
192           raise storkexception.StorkNoTPFileException()
193
194         glo_tpfiles = storktrustedpackagesparse.TrustedPackagesFileParse(None, # DTD
195                                     tpfilename,
196                                     tprepo,
197                                     arizonacrypt.PublicKey(sl=tpprefixdict["key"]["sl"]).string,
198                                     tpprefixdict["prefix"],
199                                     [])
200
201         glo_root_tpfilename = tpfilename
202         glo_root_prefixdict = tpprefixdict
203
204 def list_tp_entries(fn):
205     tp_entry_list = []
206
207     if not fn in glo_tpfiles:
208         print "XXX not in glo_tpfiles:", fn
209         return []
210
211     for tpentry in glo_tpfiles[fn]:
212         # 'PACKAGE' entries specify trusted packages
213         if tpentry["kind"] == "PACKAGE":
214             tp_entry_list.append(tpentry)
215         elif tpentry["kind"] == "USER":
216             child_list = list_tp_entries(tpentry['tpfilename'])
217             tp_entry_list.extend(child_list)
218
219     return tp_entry_list
220
221 def list_tp_hierarchy():
222     parse_tpfiles()
223
224     return list_tp_entries(glo_root_prefixdict["prefix"])
225
226 def qualify_packages(package_list, tags="", ignore_mantags = False):
227     """
228     <Purpose>
229         Find the first tpentry rule that matches each package in the list
230
231     <Arguments>
232         package_list - list of package dictionaries
233         tags - set of tags to search for
234         ignore-mantags - if true, then mandatory tag rules will be ignored
235
236     <Returns>
237         (allow_list, deny_list, unspecified_list) -
238             each is a list of packages
239             for allow_list and deny_list, pacakge['tpentry'] will be the entry that matched
240             unspecified_list is the list of packages that did not match any rules
241     """
242
243     global glo_tpfiles, glo_root_tpfilename, glo_root_prefixdict
244
245     arizonageneral.check_type_simple(package_list, "package_list", list, "storktpqualify.qualify_packages")
246     arizonageneral.check_type_simple(tags, "tags", str, "storktpqualify.qualify_packages")
247
248     # storktrustedpackagesparse expects this; it was in TrustedPackagesOrder()
249     storktrustedpackagesparse.glo_tpcacheentry = None
250
251     # convert tags from a list to a string
252     # these probably came from the packagename#tag syntax from storkdependency
253     if tags:
254         tags = tags.lower().strip().split(",")
255         tags = [x.strip() for x in tags]
256     else:
257         tags = []
258
259     parse_tpfiles()
260
261     allow_list = []
262     deny_list = []
263     unspecified_list = []
264
265     for package in package_list:
266         tpentry = find_matching_tpentry(glo_root_prefixdict["prefix"], package, tags, ignore_mantags)
267         if (tpentry == None):
268             unspecified_list.append(package)
269         else:
270             package["tpentry"] = tpentry
271             if tpentry["action"] == "allow":
272                 allow_list.append(package)
273             else:
274                 deny_list.append(package)
275
276     return (allow_list, deny_list, unspecified_list)
277
278