import sys from sha import sha import base64 try: import ldap import ldap.async import ldap.modlist import ldap.filter except ImportError: print >> sys.stderr, 'Missing LDAP module' ldap = None from qommon import get_publisher from qommon import emails from qommon.admin.emails import EmailsDirectory def utf8toiso(s): return unicode(s, 'utf-8').encode('iso-8859-1') def isotoutf8(s): return unicode(s, 'iso-8859-1').encode('utf-8') class UnconfiguredError(Exception): pass def get_store(): store = get_publisher().store if not hasattr(store, 'pratic_ldap_url'): raise UnconfiguredError() return store class Collectivity: dn = None ou = None cn = None postalAddress = None telephoneNumber = None facsimileTelephoneNumber = None mail = None cdg59URL = None cdg59collectivityId = None ldap_entry = None def __init__(self, entry): self.ldap_entry = entry self.dn = entry[0] data = entry[1] for attribute in ('ou', 'cn', 'postalAddress', 'telephoneNumber', 'facsimileTelephoneNumber', 'mail', 'cdg59URL', 'cdg59collectivityId'): v = data.get(attribute) if v: setattr(self, attribute, utf8toiso(data.get(attribute)[0])) def save(self, changed): if not changed: return new_entry = self.ldap_entry[1].copy() for attribute in changed: new_entry[attribute] = getattr(self, attribute) if not new_entry[attribute]: new_entry[attribute] = [] mod_list = ldap.modlist.modifyModlist(self.ldap_entry[1], new_entry) store = get_store() store.ldap_conn.modify_s(self.ldap_entry[0], mod_list) def delete(self): store = get_store() store.ldap_conn.delete_s(self.ldap_entry[0]) def get_attrs(self): return [ ('cn', N_('Name'), {'required': True}), ('postalAddress', N_('Geopostal Address'), {}), ('telephoneNumber', N_('Telephone Number'), {}), ('facsimileTelephoneNumber', N_('Fax Number'), {}), ('mail', N_('Mail'), {}), ('cdg59URL', N_('Website'), {}), ('cdg59collectivityId', N_('Collectivity Code'), {}) ] def get_collectivities(): store = get_store() result = store.ldap_conn.search_s(store.pratic_ldap_base, ldap.SCOPE_SUBTREE, 'objectClass=cdg59collectivity') return [Collectivity(x) for x in result] def get_collectivity(ou): store = get_store() result = store.ldap_conn.search_s(store.pratic_ldap_base, ldap.SCOPE_SUBTREE, ldap.filter.filter_format('(&(ou=%s)(objectclass=cdg59collectivity))', (ou,))) if not result: raise KeyError() return Collectivity(result[0]) def get_sorted_collectivities(): result = get_collectivities() def cmp_collectivities(x, y): if x.dn == y.dn: return 0 if x.ou == 'admin': return 1 if y.ou == 'admin': return -1 return cmp(x.cn, y.cn) result.sort(cmp_collectivities) return result class Agent: uid = None cn = None mail = None cdg59isAdmin = False cdg59isDisabled = False sn = None givenName = None cdg59direction = None ou = None employeeType = None postalAddress = None telephoneNumber = None facsimileTelephoneNumber = None mobile = None cdg59agentSirhCode = None cdg59serviceAccesses = None cdg59lastConnectionTime = None cdg59lastConnectionDuration = None ldap_entry = None def __init__(self, entry): self.ldap_entry = entry self.dn = entry[0] data = entry[1] for attribute in ('cn', 'mail', 'sn', 'givenName', 'cdg59direction', 'ou', 'employeeType', 'postalAddress', 'telephoneNumber', 'facsimileTelephoneNumber', 'mobile', 'cdg59isAdmin', 'cdg59isDisabled', 'cdg59agentSirhCode'): v = data.get(attribute) if v: setattr(self, attribute, utf8toiso(data.get(attribute)[0])) self.cdg59isAdmin = (self.cdg59isAdmin == 'TRUE') self.cdg59isDisabled = (self.cdg59isDisabled == 'TRUE') if data.get('cdg59serviceAccesses'): self.cdg59serviceAccesses = [utf8toiso(x) for x in data.get('cdg59serviceAccesses')] else: self.cdg59serviceAccesses = [] if data.get('cdg59lastConnectionTime'): self.cdg59lastConnectionTime = int(data.get('cdg59lastConnectionTime')[0]) if data.get('cdg59lastConnectionDuration'): self.cdg59lastConnectionDuration = int(data.get('cdg59lastConnectionDuration')[0]) self.username = data.get('uid')[0] def save(self, changed): if not changed: return new_entry = self.ldap_entry[1].copy() for attribute in changed: new_entry[attribute] = getattr(self, attribute) if type(new_entry[attribute]) is int: new_entry[attribute] = [str(new_entry[attribute])] if type(new_entry[attribute]) is str: new_entry[attribute] = unicode(new_entry[attribute], 'iso-8859-1').encode('utf-8') if new_entry[attribute] is None: new_entry[attribute] = [] if 'cdg59agentSirhCode' in changed: # check for an existing agent with such SIRH code try: get_agent_by_sirh(self.cdg59agentSirhCode) except KeyError: pass else: raise ldap.ALREADY_EXISTS('cdg59agentSirhCode') if 'mail' in changed: # check for an existing agent with such SIRH code try: get_agent_by_email(self.mail) except KeyError: pass else: raise ldap.ALREADY_EXISTS('mail') if 'cdg59isAdmin' in changed: if self.cdg59isAdmin: new_entry['cdg59isAdmin'] = 'TRUE' else: new_entry['cdg59isAdmin'] = '' if 'cdg59isDisabled' in changed: if self.cdg59isDisabled: new_entry['cdg59isDisabled'] = 'TRUE' else: new_entry['cdg59isDisabled'] = '' if 'sn' in changed or 'givenName' in changed: # change common name too new_entry['cn'] = '%s %s' % (self.givenName, self.sn) new_entry['cn'] = unicode(new_entry['cn'], 'iso-8859-1').encode('utf-8') mod_list = ldap.modlist.modifyModlist(self.ldap_entry[1], new_entry) store = get_store() store.ldap_conn.modify_s(self.ldap_entry[0], mod_list) def change_password(self, password): userPassword = ['{sha}%s' % base64.encodestring(sha(password).digest()).strip()] mod_list = [(ldap.MOD_REPLACE, 'userPassword', userPassword)] store = get_store() store.ldap_conn.modify_s(self.ldap_entry[0], mod_list) def delete(self): store = get_store() store.ldap_conn.delete_s(self.ldap_entry[0]) _collectivity = None def get_collectivity(self): if self._collectivity: return self._collectivity self._collectivity = self.dn.split(',')[1].split('=')[1] return self._collectivity def get_admin_agents(self): '''Get this agent "superiors"''' store = get_store() if self.cdg59isAdmin: request_str = '(objectclass=cdg59agent)' base_dn = 'ou=admin,' + store.pratic_ldap_base else: request_str = '(&(cdg59isAdmin=TRUE)(objectclass=cdg59agent))' base_dn = 'ou=%s,%s' % (self.get_collectivity(), store.pratic_ldap_base) result = store.ldap_conn.search_s(base_dn, ldap.SCOPE_SUBTREE, request_str) agents = [Agent(x) for x in result] return agents def notify_access_changes(self): if not self.mail: return all_services = get_services() services = [] for service in all_services: if service.cdg59sid in self.cdg59serviceAccesses: services.append(service.cn) services.sort() services = ' - ' + '\n - '.join(services) data = { 'agent': self.cn, 'services': services, } emails.custom_ezt_email('pratic-agent-access-changes', data, [self.mail], fire_and_forget = True) def notify_enabled(self): if not self.mail: return data = { 'agent': self.cn } emails.custom_ezt_email('pratic-agent-enabled', data, [self.mail], fire_and_forget = True) def notify_disabled(self): if not self.mail: return data = { 'agent': self.cn } emails.custom_ezt_email('pratic-agent-disabled', data, [self.mail], fire_and_forget = True) def search_agents(collectivity, search, sorted = False): store = get_store() request_str = ldap.filter.filter_format('(&(|(sn=%s*)(cn=%s*)(cdg59agentSirhCode=%s))(objectclass=cdg59agent))', (search, search, search)) if collectivity: base_dn = collectivity.dn else: store = get_store() base_dn = store.pratic_ldap_base result = store.ldap_conn.search_s(base_dn, ldap.SCOPE_SUBTREE, request_str) agents = [Agent(x) for x in result] if sorted: agents.sort(lambda x,y: cmp(x.sn.lower(), y.sn.lower())) return agents def get_agents(collectivity, first_letter = None, sorted = False): store = get_store() if first_letter: request_str = ldap.filter.filter_format('(&(sn=%s*)(objectclass=cdg59agent))', (first_letter,)) else: request_str = 'objectClass=cdg59agent' result = store.ldap_conn.search_s(collectivity.dn, ldap.SCOPE_SUBTREE, request_str) agents = [Agent(x) for x in result] if sorted: agents.sort(lambda x,y: cmp(x.sn.lower(), y.sn.lower())) return agents def get_agent(collectivity, username): store = get_store() result = store.ldap_conn.search_s(collectivity.dn, ldap.SCOPE_SUBTREE, ldap.filter.filter_format('(&(uid=%s)(objectclass=cdg59agent))', (username,))) if not result: raise KeyError() return Agent(result[0]) def get_agent_by_sirh(sirh): store = get_store() result = store.ldap_conn.search_s(store.pratic_ldap_base, ldap.SCOPE_SUBTREE, ldap.filter.filter_format('(&(cdg59agentSirhCode=%s)(objectclass=cdg59agent))', (sirh,))) if not result: raise KeyError() return Agent(result[0]) def get_agent_by_email(email): store = get_store() result = store.ldap_conn.search_s(store.pratic_ldap_base, ldap.SCOPE_SUBTREE, ldap.filter.filter_format('(&(mail=%s)(objectclass=cdg59agent))', (email,))) if not result: raise KeyError() return Agent(result[0]) def get_collectivity_name(collectivity): store = get_store() result = store.ldap_conn.search_s(store.pratic_ldap_base, ldap.SCOPE_SUBTREE, ldap.filter.filter_format('(&(ou=%s)(objectClass=cdg59collectivity))', (collectivity,))) if not result: raise KeyError() return utf8toiso(result[0][1].get('cn')[0]) def add_agent(collectivity, data): uid = data.get('uid') dn = 'uid=%s, %s' % (data.get('uid'), collectivity.dn) password = None if data.get('password'): password = data.get('password') del data['password'] data['cn'] = '%s %s' % (data['givenName'], data['sn']) for k in data: data[k] = [isotoutf8(data[k])] data['objectClass'] = 'cdg59agent' if data.get('cdg59agentSirhCode'): # check for an existing agent with such SIRH code try: get_agent_by_sirh(data.get('cdg59agentSirhCode')[0]) except KeyError: pass else: raise ldap.ALREADY_EXISTS('cdg59agentSirhCode') if data.get('mail'): # check for an existing agent with such SIRH code try: get_agent_by_email(data.get('mail')[0]) except KeyError: pass else: raise ldap.ALREADY_EXISTS('mail') store = get_store() mod_list = ldap.modlist.addModlist(data) store.ldap_conn.add_s(dn, mod_list) if password: agent = get_agent(collectivity, uid) agent.change_password(password) def add_service(data): store = get_store() dn = 'cdg59sid=%s,%s' % (data.get('cdg59sid'), store.pratic_ldap_base) for k in data: if k == 'cdg59isGlobal': if data[k]: data['cdg59isGlobal'] = 'TRUE' else: data['cdg59isGlobal'] = 'FALSE' else: data[k] = [isotoutf8(data[k])] data['objectClass'] = 'cdg59service' mod_list = ldap.modlist.addModlist(data) store.ldap_conn.add_s(dn, mod_list) class ServiceInstance: dn = None cdg59siid = None cdg59URL = None cdg59metadataURL = None cdg59serviceType = None ldap_entry = None def __init__(self, entry = None): if not entry: return self.ldap_entry = entry self.dn = entry[0] data = entry[1] for attribute in ('cdg59siid', 'cdg59URL', 'cdg59metadataURL', 'cdg59serviceType'): v = data.get(attribute) if v: setattr(self, attribute, utf8toiso(data.get(attribute)[0])) def save(self, changed): if not changed: return new_entry = self.ldap_entry[1].copy() for attribute in changed: new_entry[attribute] = getattr(self, attribute) if not new_entry[attribute]: new_entry[attribute] = [] mod_list = ldap.modlist.modifyModlist(self.ldap_entry[1], new_entry) store = get_store() store.ldap_conn.modify_s(self.ldap_entry[0], mod_list) _name = None def get_name(self): if self._name: return self._name store = get_store() result = store.ldap_conn.search_s(store.pratic_ldap_base, ldap.SCOPE_SUBTREE, ldap.filter.filter_format('(&(cdg59sid=%s)(objectClass=cdg59service))', (self.cdg59siid,))) try: self._name = utf8toiso(result[0][1]['cn'][0]) except IndexError: self._name = '%s (e)' % self.cdg59siid return self._name name = property(get_name) def delete(self): store = get_store() store.ldap_conn.delete_s(self.ldap_entry[0]) def add(self, collectivity): data = {} dn = 'cdg59siid=%s, %s' % (self.cdg59siid, collectivity.dn) data['objectClass'] = 'cdg59serviceInstance' data['cdg59siid'] = self.cdg59siid data['cdg59serviceType'] = self.cdg59serviceType if self.cdg59URL: data['cdg59URL'] = self.cdg59URL if self.cdg59metadataURL: data['cdg59metadataURL'] = self.cdg59metadataURL mod_list = ldap.modlist.addModlist(data) store = get_store() store.ldap_conn.add_s(dn, mod_list) def get_service_instances(collectivity): store = get_store() result = store.ldap_conn.search_s(collectivity.dn, ldap.SCOPE_SUBTREE, 'objectClass=cdg59serviceInstance') service_instances = [ServiceInstance(x) for x in result] # fill missing fields for global services services = get_services() for si in service_instances: if not si.cdg59metadataURL and not si.cdg59URL: s = get_service(si.cdg59serviceType) if s and s.cdg59isGlobal: si.cdg59metadataURL = s.cdg59metadataURL si.cdg59URL = s.cdg59URL return service_instances def get_service_instance(collectivity, siid): store = get_store() result = store.ldap_conn.search_s(collectivity.dn, ldap.SCOPE_SUBTREE, ldap.filter.filter_format('(&(cdg59siid=%s)(objectclass=cdg59serviceInstance))', (siid,))) if not result: raise KeyError() si = ServiceInstance(result[0]) # fix missing field values for global services if not si.cdg59metadataURL and not si.cdg59URL: s = get_service(si.cdg59serviceType) if s and s.cdg59isGlobal: si.cdg59metadataURL = s.cdg59metadataURL si.cdg59URL = s.cdg59URL return si class Service: dn = None cn = None cdg59sid = None description = None cdg59isGlobal = False cdg59URL = None cdg59metadataURL = None ldap_entry = None def __init__(self, entry): self.ldap_entry = entry self.dn = entry[0] data = entry[1] for attribute in ('cn', 'cdg59sid', 'description', 'cdg59isGlobal', 'cdg59URL', 'cdg59metadataURL'): v = data.get(attribute) if v: setattr(self, attribute, utf8toiso(data.get(attribute)[0])) self.cdg59isGlobal = (self.cdg59isGlobal == 'TRUE') def delete(self): store = get_store() store.ldap_conn.delete_s(self.ldap_entry[0]) def save(self, changed): if not changed: return mod_list = [] for attribute in changed: value = getattr(self, attribute) if value: if attribute == 'cdg59isGlobal': if value: value = 'TRUE' else: value = 'FALSE' else: value = isotoutf8(value) if value: mod_list.append((ldap.MOD_REPLACE, attribute, value)) else: mod_list.append((ldap.MOD_DELETE, attribute, None)) store = get_store() store.ldap_conn.modify_s(self.ldap_entry[0], mod_list) def get_service(sid): store = get_store() result = store.ldap_conn.search_s(store.pratic_ldap_base, ldap.SCOPE_SUBTREE, ldap.filter.filter_format('(&(cdg59sid=%s)(objectclass=cdg59service))', (sid,))) if not result: raise KeyError() return Service(result[0]) def get_services(): store = get_store() result = store.ldap_conn.search_s(store.pratic_ldap_base, ldap.SCOPE_SUBTREE, 'objectClass=cdg59service') return [Service(x) for x in result] EmailsDirectory.register('pratic-agent-access-changes', N_('Pr@tic Agent Access Changes'), N_('Available variables: agent, services'), default_subject = N_('Change of access'), default_body = N_('''\ Hello [agent], There were changes to your service authorizations, you may now access the followind services: [services] ''')) EmailsDirectory.register('pratic-agent-enabled', N_('Pr@tic Agent Enabled'), N_('Available variables: agent'), default_subject = N_('Pr@tic Account Enabled'), default_body = N_('''\ Hello [agent], Your Pr@tic account has been enabled. ''')) EmailsDirectory.register('pratic-agent-disabled', N_('Pr@tic Agent Disabled'), N_('Available variables: agent'), default_subject = N_('Pr@tic Account Disabled'), default_body = N_('''\ Hello [agent], Your Pr@tic account has been disabled. '''))