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