From 9eb63aa69668eefce6ee6aef7b895bebfb39c27b Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Mon, 9 May 2016 14:13:40 +0200 Subject: [PATCH] first commit --- README.rst | 9 ++ curie/__init__.py | 0 curie/curie2supann.py | 195 ++++++++++++++++++++++++++++++++++++++++ debian/changelog | 5 ++ debian/clean | 1 + debian/compat | 1 + debian/control | 14 +++ debian/copyright | 25 ++++++ debian/pydist-overrides | 1 + debian/rules | 6 ++ setup.py | 62 +++++++++++++ 11 files changed, 319 insertions(+) create mode 100644 README.rst create mode 100644 curie/__init__.py create mode 100755 curie/curie2supann.py create mode 100644 debian/changelog create mode 100644 debian/clean create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/pydist-overrides create mode 100755 debian/rules create mode 100755 setup.py diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..fb83c44 --- /dev/null +++ b/README.rst @@ -0,0 +1,9 @@ +Outils LDAP pour l'institu Curie +-------------------------------- + +Pour convertir des exports LDIF AD ou SUN: + + curie2supann ad.ldif sun.ldif >supann.ldif + +Ne sont exportés que les entrées pour lesquelles des enregistrements coté AD et +SUN sont trouvés. diff --git a/curie/__init__.py b/curie/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/curie/curie2supann.py b/curie/curie2supann.py new file mode 100755 index 0000000..ff736b6 --- /dev/null +++ b/curie/curie2supann.py @@ -0,0 +1,195 @@ +#!env python +# -*- coding: utf-8 -*- + +# Mode d'emploi: +# exporter l'AD +# ldapsearch -E pr=2147483647/noprompt -H ldap://ad-server -D user -w password -b 'dc=curie,dc=lan' '(&(objectClass=user)(sAMAccountType=805306368))' >curie.ldif +# convertir vers SUPANN +# ./ldif_ad2supann.py curie2.ldif + +import sys +import unicodedata +import collections + +import ldif +import ldap.dn + +DC = 'curie' +O = 'Institut curie' +UAI = '{UAI}ATROUVER' +BASE_DN = 'dc=%s,dc=fr' % ldap.dn.escape_dn_chars(DC) + + +def lowercase_keys(d): + return dict((k.lower(), v) for k, v in d.iteritems()) + + +def strip_accents(s): + s = unicode(s, 'utf-8') + return ''.join(c for c in unicodedata.normalize('NFD', s) + if unicodedata.category(c) != 'Mn').encode('utf-8') + + +class Error(Exception): + def __init__(self, dn, *args): + self.dn = dn + self.args = args + + def __str__(self): + return '%s: %s' % (self.dn, ' '.join(map(str, self.args))) + + +class CurieLdifParser(ldif.LDIFParser): + '''Conversion LDAP Institut Curie vers SUPANN + + Il faut aggréger les données venant de l'annuaire AD et de l'annuaire LDAP Sun + + Attributs obligatoires: + + uid <- AD employeeNumber ou LDAP uid @curie + givenname <- LDAP ICPrenomNaissance + sn <- LDAP ICNomNaissance + supannAliasLogin <- AD samAccountName ou LDAP ICLogin + userPassword <- {SASL}[samAccountName]@curie + supannListeRouge <- FALSE + + Attribut optionnel: + + telephoneNumber <- telephoneNumber + + si LDAP.ICEntite == 'Recherche': + suppanEntiteAffectationPrincipale = LDAP.ICEquipeRecherche[0]['OU'] + supannEntiteAffectation = LDAP.ICEntite + sinon si LDAP.ICEntite == 'Hopital' + supannEntiteAffectation = LDAP.ICEntite + sinon si LDAP.ICEneite == 'SI' + supannEntiteAffectation = LDAP.ICEntite + sinon: + error + + Les erreurs seront disponible sur la sortie erreur. + ''' + + errors = None + users = None + + def __init__(self, *args, **kwargs): + self.errors = [] + self.users = kwargs.pop('users', None) or collections.defaultdict(lambda: {}) + ldif.LDIFParser.__init__(self, *args, **kwargs) + + + def assert_sv_attribute(self, entry, name): + assert name in entry, 'attribut %s manquant' % name + assert len(entry[name]) == 1, 'plus d\'un attribut %s' % name + return entry[name][0] + + def handle(self, dn, entry): + entry = lowercase_keys(entry) + try: + if 'employeenumber' in entry: + self.handle_ad(dn, entry) + elif 'ICPersonne' in entry['objectclass']: + self.handle_sun(dn, entry) + else: + raise Error(dn, 'entrée ignorée, car absence d\'attribut employeeNumber ou ' + 'objectClass=ICPersonne') + except AssertionError, e: + self.errors.append(Error(dn, str(e))) + + def handle_ad(self, dn, entry): + uid = self.assert_sv_attribute(entry, 'employeenumber') + supann_alias_login = self.assert_sv_attribute(entry, 'samaccountname') + user_password = '{SASL}' + supann_alias_login + '@curie' + supann_liste_rouge = 'FALSE' + + self.users[uid].update({ + 'uid': uid, + 'supannAliasLogin': supann_alias_login, + 'userPassword': user_password, + 'supannListeRouge': supann_liste_rouge, + }) + self.users[uid].setdefault('_source', set()).add('ad') + + def handle_sun(self, dn, entry): + uid = self.assert_sv_attribute(entry, 'uid') + try: + ic_entite = self.assert_sv_attribute(entry, 'icentite') + except AssertionError: + ic_entite = None + else: + assert ic_entite in ('Recherche', 'Hopital', 'SI'), \ + 'valeur d\'ICEntite inconnue: %s' % ic_entite + supann_entite_affectation = ic_entite + prenom = self.assert_sv_attribute(entry, 'icprenomnaissance') + nom = self.assert_sv_attribute(entry, 'icnomnaissance') + telephone = entry.get('telephoneNumber', []) + if ic_entite == 'Recherche': + ic_equipe_recherche = self.assert_sv_attribute(entry, 'icequiperecherche') + ic_equipe_recherche_dn = ldap.dn.str2dn(ic_equipe_recherche) + assert ic_equipe_recherche[0][0][0].lower() == 'ou', ('ICEquipeRecherche ne contient ' + 'pas le DN d\'une OU: %s' + % ic_equipe_recherche) + supann_entite_affectation_principale = ic_equipe_recherche_dn[0][0][1] + else: + supann_entite_affectation_principale = None + d = { + 'uid': uid, + 'sn': nom, + 'givenName': prenom, + 'cn': strip_accents('%s %s' % (prenom, nom)).strip(), + } + if supann_entite_affectation: + d['supannEntiteAffectation'] = supann_entite_affectation + if telephone: + d['telephoneNumber'] = telephone + if supann_entite_affectation: + d['supannEntiteAffectationPrincipale'] = supann_entite_affectation_principale + self.users[uid].update(d) + self.users[uid].setdefault('_source', set()).add('sun') + + +def main(): + users = None + if not sys.argv[1:]: + print 'Utilisation : curie2supann LDIF_FILE [LDIF_FILE..]' + print + print 'Le nouveau fichier LDIF est émis sur la sortie standard et les erreurs sur la sortie' + print 'erreur.' + sys.exit(1) + for path in sys.argv[1:]: + parser = CurieLdifParser(open(path), users=users) + parser.parse() + users = parser.users + + if parser.errors: + print >>sys.stderr, path, 'ERRORS:', len(parser.errors) + for error in parser.errors: + print >>sys.stderr, ' -', error + else: + print >>sys.stderr, path, 'OK' + writer = ldif.LDIFWriter(sys.stdout) + for uid in users: + d = users[uid] + if d['_source'] != set(['ad', 'sun']): + msg = 'uid=%s uniquement dans l\'annuaire %s' % (uid, list(d['_source'])[0]) + print >>sys.stderr, msg + else: + # make it globally unique + d['uid'] = d['uid'] + '@curie' + dn = [[('uid', d['uid'], 1)], + [('ou', 'people', 1)], + [('dc', DC, 1)], + [('dc', 'fr', 1)]] + entry = {} + for k, v in d.items(): + if k.startswith('_'): + continue + if isinstance(v, list): + entry[k] = v + else: + entry[k] = [v] + writer.unparse(ldap.dn.dn2str(dn), entry) + +if __name__ == '__main__': + main() diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..2ea87f3 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +python-curie (0.0.0-1) jessie; urgency=low + + * Initial package, targetting jessie. + + -- Benjamin Dauvergne Mon, 9 May 2016 13:59:28 +0200 diff --git a/debian/clean b/debian/clean new file mode 100644 index 0000000..e9a74b6 --- /dev/null +++ b/debian/clean @@ -0,0 +1 @@ +ldaptools.egg-info/* diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..391814b --- /dev/null +++ b/debian/control @@ -0,0 +1,14 @@ +Source: python-curie +Section: python +Priority: optional +Maintainer: Benjamin Dauvergne +Build-Depends: python-setuptools (>= 0.6b3), python-all (>= 2.6), debhelper (>= 7.4.3) +Standards-Version: 3.9.1 +X-Python-Version: >= 2.7 +Homepage: http://dev.entrouvert.org/projects/gi-psl/ + +Package: python-curie +Architecture: all +Depends: ${python:Depends} +XB-Python-Version: ${python:Versions} +Description: LDAP tools for Curie institute diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..36f07ec --- /dev/null +++ b/debian/copyright @@ -0,0 +1,25 @@ +This package was debianized by Benjamin Dauvergne on +Mon, 11 Feb 2016 21:00:09 +0200 + +Upstream Author: Benjamin Dauvergne + +Copyright (c) 2011 Entr'ouvert; + +License is GNU GPL v3. + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 59 Temple +Place - Suite 330, Boston, MA 02111-1307, USA. + +On Debian GNU/Linux systems, the complete text of the GNU General Public +License can be found in `/usr/share/common-licenses/GPL'. diff --git a/debian/pydist-overrides b/debian/pydist-overrides new file mode 100644 index 0000000..6a8ca01 --- /dev/null +++ b/debian/pydist-overrides @@ -0,0 +1 @@ +python-ldap python-ldap diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..46f5b5d --- /dev/null +++ b/debian/rules @@ -0,0 +1,6 @@ +#!/usr/bin/make -f + +%: + dh $@ --with python2 + + diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..df739ab --- /dev/null +++ b/setup.py @@ -0,0 +1,62 @@ +#! /usr/bin/env python + +import subprocess +import os + +from setuptools import setup, find_packages +from setuptools.command.sdist import sdist + + +class eo_sdist(sdist): + def run(self): + print "creating VERSION file" + if os.path.exists('VERSION'): + os.remove('VERSION') + version = get_version() + version_file = open('VERSION', 'w') + version_file.write(version) + version_file.close() + sdist.run(self) + print "removing VERSION file" + if os.path.exists('VERSION'): + os.remove('VERSION') + + +def get_version(): + '''Use the VERSION, if absent generates a version with git describe, if not + tag exists, take 0.0.0- and add the length of the commit log. + ''' + if os.path.exists('VERSION'): + with open('VERSION', 'r') as v: + return v.read() + if os.path.exists('.git'): + p = subprocess.Popen(['git', 'describe', '--dirty', '--match=v*'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + result = p.communicate()[0] + if p.returncode == 0: + result = result.split()[0][1:] + else: + result = '0.0.0-%s' % len(subprocess.check_output( + ['git', 'rev-list', 'HEAD']).splitlines()) + return result.replace('-', '.').replace('.g', '+g') + return '0.0.0' + + +setup(name="curie", + version=get_version(), + license="AGPLv3+", + description="LDAP tools for Curie institute", + long_description=open('README.rst').read(), + url="http://dev.entrouvert.org/projects/ldaptools/", + author="Entr'ouvert", + author_email="authentic@listes.entrouvert.com", + maintainer="Benjamin Dauvergne", + maintainer_email="bdauvergne@entrouvert.com", + packages=['curie'], + include_package_data=True, + install_requires=['setuptools', 'python-ldap'], + entry_points={ + 'console_scripts': ['curie2supann=curie.curie2supann:main'], + }, + zip_safe=False, + cmdclass={'sdist': eo_sdist})