Merge pull request #2 from gentoo/week1_json_switch
[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, KEYLEN_MAP
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 = KEYLEN_MAP[key_len]
36         if keyid.startswith('0x'):
37             target_len = KEYLEN_MAP[key_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         gkeyattr_dev = defaultdict()
122         for dev in sorted(devs):
123             if devs[dev]['gentooStatus'][0] not in ['active']:
124                 continue
125             #self.logger.debug("create_seedfile, dev = "
126             #   "%s, %s" % (str(dev), str(devs[dev])))
127             developer_attrs = self.build_gkeydict(devs[dev])
128             if developer_attrs:
129                 gkeyattr_dev[dev] = developer_attrs
130                 self.seeds.add(gkeyattr_dev)
131                 count += 1
132             else:
133                 error_count += 1
134         self.output("Total number of seeds created:", count)
135         self.output("Seeds created... Saving file: %s" % filename)
136         self.output("Total number of Dev's with GPG errors:", error_count)
137         self.logger.info("Total number of seeds created: %d" % count)
138         self.logger.info("Seeds created... Saving file: %s" % filename)
139         self.logger.info("Total number of Dev's with GPG errors: %d" % error_count)
140         return self.seeds.save()
141
142
143     @staticmethod
144     def get_args(args):
145         for attr in ['nick', 'name', 'gpgkey', 'fingerprint', 'status']:
146             if attr:
147                 target = getattr(args, attr)
148                 search_field = gkey2SEARCH[attr]
149                 break
150         return (attr, target, search_field)
151
152
153     def build_gkeydict(self, info):
154         keyinfo = defaultdict()
155         keyid_found = False
156         keyid_missing = False
157         # assume it's good until an error is found
158         is_good = True
159         #self.logger.debug("Actions: build_gkeylist; info = %s" % str(info))
160         for attr in GKEY._fields:
161             field = gkey2ldap_map[attr]
162             if not field:
163                 keyinfo[attr] = None
164                 continue
165             try:
166                 values = info[field]
167                 # strip errant line feeds
168                 values = [y.strip('\n') for y in values]
169                 # separate out short/long key id's
170                 if values and attr in ['keyid', 'longkeyid']:
171                     if len(get_key_ids(attr, values)):
172                         keyid_found = True
173                 elif values and attr in ['fingerprint']:
174                     values = [v.replace(' ', '') for v in values]
175                 if 'undefined' in values:
176                     self.logger.error('ERROR in LDAP info for: %s, %s'
177                         % (info['uid'][0],info['cn'][0]))
178                     self.logger.error('  %s = "undefined"' % (field))
179                     is_good = False
180                 keyinfo[attr] = values
181             except KeyError:
182                 self.logger.debug('LDAP info for: %s, %s'
183                     % (info['uid'][0],info['cn'][0]))
184                 self.logger.debug('  MISSING or EMPTY LDAP field ' +
185                     '[%s] GPGKey field [%s]' % (field, attr))
186                 if attr in ['keyid', 'longkeyid']:
187                     keyid_missing = True
188                 else:
189                     is_good = False
190                 keyinfo[attr] = None
191         if not keyid_found and keyid_missing:
192             fingerprint = None
193             try:
194                 fingerprint = info[gkey2ldap_map['fingerprint']]
195                 self.logger.debug('  Generate gpgkey, Found LDAP fingerprint field')
196             except KeyError:
197                 gpgkey = 'Missing fingerprint from LDAP info'
198                 self.logger.debug('  Generate gpgkey, LDAP fingerprint KeyError')
199             if fingerprint:
200                 values = [y.strip('\n') for y in fingerprint]
201                 values = [v.replace(' ', '') for v in values]
202                 # assign it to gpgkey to prevent a possible
203                 # "gpgkey" undefined error
204                 gpgkey = ['0x' + x[-KEYLEN_MAP['longkeyid']:] for x in values]
205                 keyinfo['longkeyid'] = gpgkey
206                 self.logger.debug('  Generate gpgkey, NEW keyinfo[\'fingerprint\'] = %s'
207                     % str(keyinfo['longkeyid']))
208             else:
209                 gpgkey = 'Missing or Bad fingerprint from LDAP info'
210                 is_good = False
211             if not keyinfo['longkeyid']:
212                 self.logger.error('ERROR in ldap info for: %s, %s'
213                     %(info['uid'][0],info['cn'][0]))
214                 self.logger.error('  A valid keyid, longkeyid or fingerprint '
215                     'was not found for %s : gpgkey = %s' %(info['cn'][0], gpgkey))
216                 is_good = False
217         if is_good:
218             if keyinfo['fingerprint']: # fingerprints exist check
219                 is_ok = self._check_fingerprint_integrity(info, keyinfo)
220                 is_match = self._check_id_fingerprint_match(info, keyinfo)
221                 if not is_ok or not is_match:
222                     is_good = False
223         if is_good:
224             return keyinfo
225         return None
226
227
228     def _check_id_fingerprint_match(self, info, keyinfo):
229         # assume it's good until found an error is found
230         is_good = True
231         for attr in ['keyid', 'longkeyid']:
232             # skip blank id field
233             if not keyinfo[attr]:
234                 continue
235             for y in keyinfo[attr]:
236                 index = len(y.lstrip('0x'))
237                 if y.lstrip('0x').upper() not in \
238                         [x[-index:].upper() for x in keyinfo['fingerprint']]:
239                     self.logger.error('ERROR in LDAP info for: %s, %s'
240                         %(info['uid'][0],info['cn'][0]))
241                     self.logger.error('  ' + str(keyinfo))
242                     self.logger.error('  GPGKey id %s not found in the '
243                         % y.lstrip('0x') + 'listed fingerprint(s)')
244                     is_good = False
245         return is_good
246
247
248     def _check_fingerprint_integrity(self, info, keyinfo):
249         # assume it's good until found an error is found
250         is_good = True
251         for fingerprint in keyinfo['fingerprint']:
252             # check fingerprint integrity
253             if len(fingerprint) != 40:
254                 self.logger.error('ERROR in LDAP info for: %s, %s'
255                     %(info['uid'][0],info['cn'][0]))
256                 self.logger.error('  GPGKey incorrect fingerprint ' +
257                     'length (%s) for fingerprint: %s' %(len(fingerprint), fingerprint))
258                 is_good = False
259                 continue
260             if not self.fingerprint_re.match(fingerprint):
261                 self.logger.error('ERROR in LDAP info for: %s, %s'
262                     % (info['uid'][0],info['cn'][0]))
263                 self.logger.error('  GPGKey: Non hexadecimal digits in ' +
264                     'fingerprint for fingerprint: ' + fingerprint)
265                 is_good = False
266         return is_good