be02dfd9d5203279d873020f7c8ba8c4413b1e48
[gentoo-keys.git] / gkeyldap / actions.py
1 #
2 #-*- coding:utf-8 -*-
3
4 """
5     Gentoo-keys - gkeyldap/actions.py
6
7     Primary api interface module
8
9     @copyright: 2012 by Brian Dolbec <dol-sen@gentoo.org>
10     @license: GNU GPL2, see COPYING for details.
11 """
12
13 import os
14 import re
15
16 from gkeys.config import GKEY, KEYID, LONGKEYID, FINGERPRINT
17 from gkeys.seed import Seeds
18 from gkeyldap.search import (LdapSearch, UID, gkey2ldap_map, gkey2SEARCH)
19
20
21 # set some defaults
22 KEY_LEN = {
23     'keyid': 8,
24     'longkeyid': 16,
25 }
26
27
28 Avialable_Actions = ['ldapsearch', 'updateseeds']
29
30
31 def get_key_ids(key, info):
32     '''Small utility function to return only keyid (short)
33     or longkeyid's
34
35     @param key: string, the key length desired
36     @param info: list of keysid's to process
37     @return list of the desired key length id's
38     '''
39     result = []
40     for x in info:
41         if x.startswith('0x'):
42             mylen = KEY_LEN[key] + 2
43         else:
44             mylen = KEY_LEN[key]
45         if len(x) == mylen:
46             result.append(x)
47     return result
48
49
50 class Actions(object):
51
52
53     def __init__(self, config, output=None, logger=None):
54         self.config = config
55         self.output = output
56         self.logger = logger
57         self.seeds = None
58         self.fingerprint_re = re.compile('[0-9A-Fa-f]{40}')
59
60
61     def ldapsearch(self, args):
62         l = LdapSearch()
63         self.logger.info("Search...establishing connection")
64         self.output("Search...establishing connection")
65         if not l.connect():
66             self.logger.info("Aborting Search...Connection failed")
67             self.output("Aborting Search...Connection failed")
68             return False
69         self.logger.debug("MAIN: _action_ldapsearch; args = %s" % str(args))
70         x, target, search_field = self.get_args(args)
71         results = l.search(target, search_field)
72         devs = l.result2dict(results, gkey2ldap_map[x])
73         for dev in sorted(devs):
74             self.output(dev, devs[dev])
75         self.output("============================================")
76         self.output("Total number of devs in results:", len(devs))
77         self.logger.info("============================================")
78         self.logger.info("Total number of devs in results: %d" % len(devs))
79         return True
80
81
82     def updateseeds(self, args):
83         self.logger.info("Beginning ldap search...")
84         self.output("Beginning ldap search...")
85         l = LdapSearch()
86         if not l.connect():
87             self.output("Aborting Update...Connection failed")
88             self.logger.info("Aborting Update...Connection failed")
89             return False
90         results = l.search('*', UID)
91         info = l.result2dict(results, 'uid')
92         self.logger.debug(
93             "MAIN: _action_updateseeds; got results :) converted to info")
94         if not self.create_seedfile(info):
95             self.logger.error("Dev seed file update failure: "
96                 "Original seed file is intact & untouched.")
97         filename = self.config['dev-seedfile']
98         old = filename + '.old'
99         try:
100             self.output("Backing up existing file...")
101             self.logger.info("Backing up existing file...")
102             if os.path.exists(old):
103                 self.logger.debug(
104                     "MAIN: _action_updateseeds; Removing 'old' seed file: %s"
105                     % old)
106                 os.unlink(old)
107             if os.path.exists(filename):
108                 self.logger.debug(
109                     "MAIN: _action_updateseeds; Renaming current seed file to: "
110                     "%s" % old)
111                 os.rename(filename, old)
112             self.logger.debug(
113                 "MAIN: _action_updateseeds; Renaming '.new' seed file to: %s"
114                 % filename)
115             os.rename(filename + '.new', filename)
116         except IOError:
117             raise
118         self.output("Developer Seed file updated")
119         return True
120
121
122     def create_seedfile(self, devs):
123         self.output("Creating seeds from ldap data...")
124         filename = self.config['dev-seedfile'] + '.new'
125         self.seeds = Seeds(filename)
126         count = 0
127         for dev in sorted(devs):
128             if devs[dev]['gentooStatus'][0] not in ['active']:
129                 continue
130             #self.logger.debug("create_seedfile, dev = "
131             #   "%s, %s" % (str(dev), str(devs[dev])))
132             keyinfo = self.build_gkeylist(devs[dev])
133             if keyinfo:
134                 new_gkey = GKEY._make(keyinfo)
135                 self.seeds.add(new_gkey)
136                 count += 1
137         self.output("Total number of seeds created:", count)
138         self.output("Seeds created...saving file: %s" % filename)
139         self.logger.info("Total number of seeds created: %d" % count)
140         self.logger.info("Seeds created...saving file: %s" % filename)
141         return self.seeds.save()
142
143
144     @staticmethod
145     def get_args(args):
146         for x in ['nick', 'name', 'gpgkey', 'fingerprint', 'status']:
147             if x:
148                 target = getattr(args, x)
149                 search_field = gkey2SEARCH[x]
150                 break
151         return (x, target, search_field)
152
153
154
155     def build_gkeydict(self, info):
156         keyinfo = {}
157         for x in GKEY._fields:
158             field = gkey2ldap_map[x]
159             if not field:
160                 continue
161             try:
162                 # strip errant line feeds
163                 values = [y.strip('\n') for y in info[field]]
164                 if values and values in ['uid', 'cn' ]:
165                     value = values[0]
166                 # separate out short/long key id's
167                 elif values and x in ['keyid', 'longkeyid']:
168                     value = get_key_ids(x, values)
169                 else:
170                     value = values
171                 if 'undefined' in values:
172                     self.logger.error('%s = "undefined" for %s, %s'
173                         %(field, info['uid'][0], info['cn'][0]))
174                 if value:
175                     keyinfo[x] = value
176             except KeyError:
177                 pass
178         return keyinfo
179
180
181     def build_gkeylist(self, info):
182         keyinfo = []
183         keyid_found = False
184         keyid_missing = False
185         # assume it's good until found an error is found
186         is_good = True
187         #self.logger.debug("MAIN: build_gkeylist; info = %s" % str(info))
188         for x in GKEY._fields:
189             field = gkey2ldap_map[x]
190             if not field:
191                 keyinfo.append(None)
192                 continue
193             try:
194                 # strip errant line feeds
195                 values = [y.strip('\n') for y in info[field]]
196                 if values and field in ['uid', 'cn' ]:
197                     value = values[0]
198                 # separate out short/long key id's
199                 elif values and x in ['keyid', 'longkeyid']:
200                     value = get_key_ids(x, values)
201                     if len(value):
202                         keyid_found = True
203                 elif values and x in ['fingerprint']:
204                     value = [v.replace(' ', '') for v in values]
205                 else:
206                     value = values
207                 if 'undefined' in values:
208                     self.logger.error('ERROR in ldap info for: %s, %s'
209                         %(info['uid'][0],info['cn'][0]))
210                     self.logger.error('  %s = "undefined"' %(field))
211                     is_good = False
212                 keyinfo.append(value)
213             except KeyError:
214                 self.logger.debug('Ldap info for: %s, %s'
215                     %(info['uid'][0],info['cn'][0]))
216                 self.logger.debug('  MISSING or EMPTY ldap field ' +
217                     '[%s] GPGKey field [%s]' %(field, x))
218                 if x in ['keyid', 'longkeyid']:
219                     keyid_missing = True
220                 else:
221                     is_good = False
222                 keyinfo.append(None)
223         if not keyid_found and keyid_missing:
224             fingerprint = None
225             try:
226                 fingerprint = info[gkey2ldap_map['fingerprint']]
227                 self.logger.debug('  Generate gpgkey, Found ldap fingerprint field')
228             except KeyError:
229                 gpgkey = 'Missing fingerprint from ldap info'
230                 self.logger.debug('  Generate gpgkey, ldap fingerprint KeyError')
231             if fingerprint:
232                 values = [y.strip('\n') for y in fingerprint]
233                 value = [v.replace(' ', '') for v in values]
234                 # assign it to gpgkey to prevent a possible
235                 # "gpgkey" undefined error
236                 gpgkey = ['0x' + x[-KEY_LEN['longkeyid']:] for x in value]
237                 keyinfo[LONGKEYID] = gpgkey
238                 self.logger.debug('  Generate gpgkey, NEW keyinfo[LONGKEYID] = %s'
239                     % str(keyinfo[LONGKEYID]))
240             else:
241                 gpgkey = 'Missing or Bad fingerprint from ldap info'
242                 is_good = False
243             if not keyinfo[LONGKEYID]:
244                 self.logger.error('ERROR in ldap info for: %s, %s'
245                     %(info['uid'][0],info['cn'][0]))
246                 self.logger.error('  A valid keyid, longkeyid or fingerprint '
247                     'was not found for %s : gpgkey = %s' %(info['cn'][0], gpgkey))
248                 is_good = False
249         if is_good:
250             if keyinfo[FINGERPRINT]: # fingerprints exist check
251                 is_ok = self._check_fingerprint_integrity(info, keyinfo)
252                 is_match = self._check_id_fingerprint_match(info, keyinfo)
253                 if not is_ok or not is_match:
254                     is_good = False
255         if is_good:
256             return keyinfo
257         return None
258
259
260     def _check_id_fingerprint_match(self, info, keyinfo):
261         # assume it's good until found an error is found
262         is_good = True
263         for x in [KEYID, LONGKEYID]:
264             # skip blank id field
265             if not keyinfo[x]:
266                 continue
267             for y in keyinfo[x]:
268                 index = len(y.lstrip('0x'))
269                 if y.lstrip('0x').upper() not in \
270                         [x[-index:].upper() for x in keyinfo[FINGERPRINT]]:
271                     self.logger.error('ERROR in ldap info for: %s, %s'
272                         %(info['uid'][0],info['cn'][0]))
273                     self.logger.error('  ' + str(keyinfo))
274                     self.logger.error('  GPGKey id %s not found in the '
275                         % y.lstrip('0x') + 'listed fingerprint(s)')
276                     is_good = False
277         return is_good
278
279
280     def _check_fingerprint_integrity(self, info, keyinfo):
281         # assume it's good until found an error is found
282         is_good = True
283         for x in keyinfo[FINGERPRINT]:
284             # check fingerprint integrity
285             if len(x) != 40:
286                 self.logger.error('ERROR in ldap info for: %s, %s'
287                     %(info['uid'][0],info['cn'][0]))
288                 self.logger.error('  GPGKey incorrect fingerprint ' +
289                     'length (%s) for fingerprint: %s' %(len(x), x))
290                 is_good = False
291                 continue
292             if not self.fingerprint_re.match(x):
293                 self.logger.error('ERROR in ldap info for: %s, %s'
294                     %(info['uid'][0],info['cn'][0]))
295                 self.logger.error('  GPGKey: Non hexadecimal digits in ' +
296                     'fingerprint for fingerprint: ' + x)
297                 is_good = False
298         return is_good