From 24de6fbcf1f5077ee0a3a074b7ae49c7b76ef285 Mon Sep 17 00:00:00 2001 From: bdauvergne Date: Mon, 16 Mar 2015 09:42:44 +0100 Subject: [PATCH 4/4] add new application authentic2_provisionning Goal is to allow synchronizing users from the authentic2 db to an LDAP directory. The application also contain helper class to do unittest against an OpenLDAP server, for this OpenLDAP must be installed on your computer. --- setup.py | 1 + src/authentic2_provisionning/__init__.py | 3 + src/authentic2_provisionning/app_settings.py | 24 + src/authentic2_provisionning/ldap_utils.py | 307 +++++++++++ .../management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/provision.py | 184 +++++++ src/authentic2_provisionning/tests/__init__.py | 0 src/authentic2_provisionning/tests/core.ldif | 600 +++++++++++++++++++++ src/authentic2_provisionning/tests/cosine.ldif | 200 +++++++ .../tests/inetorgperson.ldif | 69 +++ src/authentic2_provisionning/tests/test_ldap.py | 82 +++ 12 files changed, 1470 insertions(+) create mode 100644 src/authentic2_provisionning/__init__.py create mode 100644 src/authentic2_provisionning/app_settings.py create mode 100644 src/authentic2_provisionning/ldap_utils.py create mode 100644 src/authentic2_provisionning/management/__init__.py create mode 100644 src/authentic2_provisionning/management/commands/__init__.py create mode 100644 src/authentic2_provisionning/management/commands/provision.py create mode 100644 src/authentic2_provisionning/tests/__init__.py create mode 100644 src/authentic2_provisionning/tests/core.ldif create mode 100644 src/authentic2_provisionning/tests/cosine.ldif create mode 100644 src/authentic2_provisionning/tests/inetorgperson.ldif create mode 100644 src/authentic2_provisionning/tests/test_ldap.py diff --git a/setup.py b/setup.py index f17c76a..2613166 100755 --- a/setup.py +++ b/setup.py @@ -142,11 +142,12 @@ setup(name="authentic2", 'compile_translations': compile_translations, 'sdist': eo_sdist}, entry_points={ 'authentic2.plugin': [ 'authentic2-auth-ssl = authentic2.auth2_auth.auth2_ssl:Plugin', 'authentic2-idp-saml2 = authentic2.idp.saml:Plugin', 'authentic2-idp-openid = authentic2_idp_openid:Plugin', 'authentic2-idp-cas = authentic2_idp_cas:Plugin', + 'authentic2-provisionning = authentic2_provisionning:Plugin', ], }, ) diff --git a/src/authentic2_provisionning/__init__.py b/src/authentic2_provisionning/__init__.py new file mode 100644 index 0000000..16906d9 --- /dev/null +++ b/src/authentic2_provisionning/__init__.py @@ -0,0 +1,3 @@ +class Plugin(object): + def get_apps(self): + return [__name__] diff --git a/src/authentic2_provisionning/app_settings.py b/src/authentic2_provisionning/app_settings.py new file mode 100644 index 0000000..d15d526 --- /dev/null +++ b/src/authentic2_provisionning/app_settings.py @@ -0,0 +1,24 @@ +class AppSettings(object): + __DEFAULTS = { + 'RESSOURCES': {}, + } + + def __init__(self, prefix): + self.prefix = prefix + + def _setting(self, name, dflt): + from django.conf import settings + return getattr(settings, self.prefix + name, dflt) + + def __getattr__(self, name): + if name not in self.__DEFAULTS: + raise AttributeError(name) + return self._setting(name, self.__DEFAULTS[name]) + + +# Ugly? Guido recommends this himself ... +# http://mail.python.org/pipermail/python-ideas/2012-May/014969.html +import sys +app_settings = AppSettings('A2_PROVISIONNING_') +app_settings.__name__ = __name__ +sys.modules[__name__] = app_settings diff --git a/src/authentic2_provisionning/ldap_utils.py b/src/authentic2_provisionning/ldap_utils.py new file mode 100644 index 0000000..36ed6f3 --- /dev/null +++ b/src/authentic2_provisionning/ldap_utils.py @@ -0,0 +1,307 @@ +import socket +import time +import tempfile +import shutil +import subprocess +import os +import ldap +import ldap.modlist +from ldap.ldapobject import ReconnectLDAPObject +import ldap.sasl +from ldap.controls import SimplePagedResultsControl +import ldif +import StringIO + + +SLAPD_PATH = None +SLAPADD_PATH = None +SLAPD_PATHS = ['/bin', '/usr/bin', '/sbin', '/usr/sbin', '/usr/local/bin', '/usr/local/sbin'] + + +def has_slapd(): + global SLAPD_PATH, SLAPADD_PATH, PATHS + if not SLAPD_PATH or not SLAPADD_PATH: + for path in SLAPD_PATHS: + slapd_path = os.path.join(path, 'slapd') + if os.path.exists(slapd_path): + SLAPD_PATH = slapd_path + slapadd_path = os.path.join(path, 'slapadd') + if os.path.exists(slapd_path): + SLAPADD_PATH = slapadd_path + return not (SLAPD_PATH is None or SLAPADD_PATH is None) + +class ListLDIFParser(ldif.LDIFParser): + def __init__(self, *args, **kwargs): + self.entries = [] + ldif.LDIFParser.__init__(self, *args, **kwargs) + + def handle(self, dn, entry): + self.entries.append((dn, entry)) + + def add(self, conn): + for dn, entry in self.entries: + conn.add_s(dn, ldap.modlist.addModlist(entry)) + +class Slapd(object): + '''Initiliaze an OpenLDAP server with just one database containing branch + o=orga and loading the core schema. ACL are very permissive. + ''' + config_ldif = '''dn: cn=config +objectClass: olcGlobal +cn: config +olcToolThreads: 1 +olcLogLevel: none + +dn: cn=module{{0}},cn=config +objectClass: olcModuleList +cn: module{{0}} +olcModulePath: /usr/lib/ldap +olcModuleLoad: {{0}}back_hdb +olcModuleLoad: {{1}}back_monitor +olcModuleLoad: {{2}}back_mdb +olcModuleLoad: {{3}}accesslog +olcModuleLoad: {{4}}unique +olcModuleLoad: {{5}}refint +olcModuleLoad: {{6}}constraint +olcModuleLoad: {{7}}syncprov + +dn: cn=schema,cn=config +objectClass: olcSchemaConfig +cn: schema + +dn: olcDatabase={{-1}}frontend,cn=config +objectClass: olcDatabaseConfig +objectClass: olcFrontendConfig +olcDatabase: {{-1}}frontend +olcAccess: {{0}}to * + by dn.exact=gidNumber={gid}+uidNumber={uid},cn=peercred,cn=external,cn=auth manage + by * break +olcAccess: {{1}}to dn.exact="" by * read +olcAccess: {{2}}to dn.base="cn=Subschema" by * read +olcSizeLimit: unlimited +olcTimeLimit: unlimited + +dn: olcDatabase={{0}}config,cn=config +objectClass: olcDatabaseConfig +olcDatabase: {{0}}config +olcRootDN: uid=admin,cn=config +olcRootPW: admin +olcAccess: {{0}}to * + by dn.exact=gidNumber={gid}+uidNumber={uid},cn=peercred,cn=external,cn=auth manage + by * break +''' + first_db_ldif = '''dn: olcDatabase={{1}}mdb,cn=config +objectClass: olcDatabaseConfig +objectClass: olcMdbConfig +olcDatabase: {{1}}mdb +olcSuffix: o=orga +olcDbDirectory: {path} +olcRootDN: uid=admin,o=orga +olcRootPW: admin +olcReadOnly: FALSE +# Index +olcAccess: {{0}}to * by manage + +dn: o=orga +objectClass: organization +o: orga +''' + process = None + data_dir_name = 'data' + schemas_ldif = [ + open(os.path.join(os.path.dirname(__file__), 'tests', 'core.ldif')).read(), + open(os.path.join(os.path.dirname(__file__), 'tests', 'cosine.ldif')).read(), + open(os.path.join(os.path.dirname(__file__), 'tests', 'inetorgperson.ldif')).read(), + ] + + def create_process(self, args): + return subprocess.Popen(args, stdin=subprocess.PIPE, + env=os.environ, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + def __init__(self, **kwargs): + assert has_slapd() + self.__dict__.update(kwargs) + self.checkpoints = [] + self.slapd_dir = tempfile.mkdtemp(prefix='a2-provision-slapd') + self.config_dir = os.path.join(self.slapd_dir, 'slapd.d') + os.mkdir(self.config_dir) + self.data_dir = os.path.join(self.slapd_dir, self.data_dir_name) + os.mkdir(self.data_dir) + self.socket = os.path.join(self.slapd_dir, 'socket') + self.ldapi_url = 'ldapi://%s' % self.socket.replace('/', '%2F') + self.slapadd(self.config_ldif) + for schema_ldif in self.schemas_ldif: + self.slapadd(schema_ldif, do_format=False) + self.start() + self.add_ldif(self.first_db_ldif, do_format=True) + + def slapadd(self, ldif, do_format=True): + if do_format: + ldif = ldif.format(path=self.data_dir, gid=os.getgid(), uid=os.getuid()) + slapadd = self.create_process([SLAPADD_PATH, '-v', '-n0', '-F', self.config_dir]) + stdout, stderr = slapadd.communicate(input=ldif) + assert slapadd.returncode == 0, 'slapadd failed: %s' % stderr + + def start(self): + '''Launch slapd''' + if self.process and self.process.returncode is None: + self.stop() + cmd = [SLAPD_PATH, + '-d0', # put slapd in foreground + '-F' + self.config_dir, + '-h', self.ldapi_url] + self.process = self.create_process(cmd) + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + # Detect slapd listening + while True: + try: + sock.connect(self.socket) + except socket.error: + # Yield so that slapd has time to initialize + time.sleep(0) + else: + break + + def stop(self): + '''Send SIGTERM to slapd''' + if self.process: + self.process.terminate() + self.process.wait() + self.process = None + + def checkpoint(self): + '''Stop slapd and save current data state''' + self.checkpoints.append(os.path.join(self.slapd_dir, 'checkpoint-%d' % len(self.checkpoints))) + self.stop() + shutil.copytree(self.data_dir, self.checkpoints[-1]) + self.start() + + def restore(self): + '''Stop slapd and restore last data state''' + assert self.checkpoints, 'no checkpoint exists' + self.stop() + shutil.rmtree(self.data_dir) + shutil.copytree(self.checkpoints[-1], self.data_dir) + shutil.rmtree(self.checkpoints[-1]) + self.checkpoints.pop() + self.start() + + # Clean behind us + def __del__(self): + self.clean() + + def clean(self): + '''Remove directory''' + import os.path # we are maybe in a desctructor, imported module may have vanished + if os.path.exists(self.slapd_dir): + self.stop() + shutil.rmtree(self.slapd_dir, ignore_errors=True) + + def __enter__(self): + return self + + def __exit__(self, type, value, tb): + self.clean() + + def add_ldif(self, ldif, do_format=False): + if do_format: + ldif = ldif.format(path=self.data_dir, gid=os.getgid(), uid=os.getuid()) + parser = ListLDIFParser(StringIO.StringIO(ldif)) + parser.parse() + conn = self.get_connection() + conn.simple_bind_s('uid=admin,cn=config', 'admin') + parser.add(conn) + + def get_connection(self): + return ldap.initialize(self.ldapi_url) + +class PagedResultsSearchObject: + page_size = 500 + + def paged_search_ext_s(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0): + """ + Behaves exactly like LDAPObject.search_ext_s() but internally uses the + simple paged results control to retrieve search results in chunks. + + This is non-sense for really large results sets which you would like + to process one-by-one + """ + + while True: # loop for reconnecting if necessary + + req_ctrl = SimplePagedResultsControl(True,size=self.page_size,cookie='') + + try: + + # Send first search request + msgid = self.search_ext( + base, + scope, + filterstr=filterstr, + attrlist=attrlist, + attrsonly=attrsonly, + serverctrls=(serverctrls or [])+[req_ctrl], + clientctrls=clientctrls, + timeout=timeout, + sizelimit=sizelimit + ) + + all_results = [] + + while True: + rtype, rdata, rmsgid, rctrls = self.result3(msgid) + for result in rdata: + yield result + all_results.extend(rdata) + # Extract the simple paged results response control + pctrls = [ + c + for c in rctrls + if c.controlType == SimplePagedResultsControl.controlType + ] + if pctrls: + if pctrls[0].cookie: + # Copy cookie from response control to request control + req_ctrl.cookie = pctrls[0].cookie + msgid = self.search_ext( + base, + scope, + filterstr=filterstr, + attrlist=attrlist, + attrsonly=attrsonly, + serverctrls=(serverctrls or [])+[req_ctrl], + clientctrls=clientctrls, + timeout=timeout, + sizelimit=sizelimit + ) + else: + break # no more pages available + + except ldap.SERVER_DOWN: + self.reconnect(self._uri) + else: + break + + +class PagedLDAPObject(ReconnectLDAPObject,PagedResultsSearchObject): + pass + +if __name__ == '__main__': + with Slapd() as slapd: + conn = slapd.get_connection() + conn.simple_bind_s('uid=admin,o=orga', 'admin') + assert conn.whoami_s() == 'dn:uid=admin,o=orga' + slapd.checkpoint() + slapd.add_ldif('''dn: uid=admin,o=orga +objectClass: person +objectClass: uidObject +uid: admin +cn: admin +sn: admin +''') + conn = slapd.get_connection() + print conn.search_s('o=orga', ldap.SCOPE_SUBTREE) + slapd.restore() + conn = slapd.get_connection() + print conn.search_s('o=orga', ldap.SCOPE_SUBTREE) + diff --git a/src/authentic2_provisionning/management/__init__.py b/src/authentic2_provisionning/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/authentic2_provisionning/management/commands/__init__.py b/src/authentic2_provisionning/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/authentic2_provisionning/management/commands/provision.py b/src/authentic2_provisionning/management/commands/provision.py new file mode 100644 index 0000000..0745f8c --- /dev/null +++ b/src/authentic2_provisionning/management/commands/provision.py @@ -0,0 +1,184 @@ +from optparse import make_option + +try: + import ldap + from ldap.dn import str2dn, dn2str + from ldap.filter import filter_format +except ImportError: + ldap = None + +from django.core.management.base import BaseCommand + +from authentic2.attributes_ng.engine import get_attributes +from authentic2 import compat, utils + +from authentic2_provisionning import app_settings, ldap_utils + +ADD = 1 +REPLACE = 2 +DELETE = 3 + + + +class Command(BaseCommand): + can_import_django_settings = True + output_transaction = True + requires_model_validation = True + option_list = BaseCommand.option_list + ( + make_option('--fake', + action='store_true', + default=False, + help='Do nothing, just simulate'), + make_option('--batch-size', + action='store', + type='int', + default=200, + help='Batch size'), + ) + + def handle(self, *args, **options): + ressources = app_settings.RESSOURCES + if args: + ressources = [ressource for ressource in ressources + if ressource.get('name') in args] + for ressource in ressources: + self.sync_ressource(ressource, **options) + + def sync_ressource(self, ressource, **options): + self.sync_ldap_ressource(ressource, **options) + + def add_values(self, ldap_attributes, ldap_attribute, values): + if not isinstance(values, (list, tuple)): + values = [values] + ldap_values = ldap_attributes.setdefault(ldap_attribute, []) + for value in values: + if isinstance(value, unicode): + value = value.encode('utf-8') + elif isinstance(value, str): + pass # must be well encoded + else: + raise NotImplementedError('value %r not supported' % value) + ldap_values.append(value) + + def build_dn_and_filter(self, ressource, ldap_attributes): + '''Build the target record dn''' + base_dn = ressource['base_dn'] + rdn_attributes = ressource['rdn_attributes'] + dn = str2dn(base_dn) + rdn = [] + for ldap_attribute in rdn_attributes: + values = ldap_attributes.get(ldap_attribute, []) + assert len(values) == 1, 'RDN attribute must have exactly one value %r %r' % \ + (rdn_attributes, ldap_attributes) + rdn.append((ldap_attribute, values[0], 1)) + dn = [rdn] + dn + return dn2str(dn), ('&', [(a,b) for a, b, c in rdn]) + + def format_filter(self, filters): + if isinstance(filters, basestring): + return filters + assert len(filters) == 2, 'filters %r' % (filters,) + if isinstance(filters[1], (list, tuple)): + return '(%s%s)' % (filters[0], ''.join(self.format_filter(x) for x in filters[1])) + else: + return filter_format('(%s=%%s)' % filters[0], (filters[1],)) + + def sync_ldap_ressource(self, ressource, **options): + verbosity = int(options['verbosity']) + fake = options['fake'] + # FIXME: Check ressource well formedness + conn = ldap_utils.PagedLDAPObject(ressource['url'], retry_max=10, + retry_delay=2) + base_dn = ressource['base_dn'] + use_tls = ressource.get('use_tls') + bind_dn = ressource.get('bind_dn') + bind_pw = ressource.get('bind_pw') + if use_tls: + conn.start_tls_s() + if bind_dn: + conn.simple_bind_s(bind_dn, bind_pw) + attribute_mapping = utils.lower_keys(ressource['attribute_mapping']) + static_attributes = utils.lower_keys(ressource.get('static_attributes', {})) + format_mapping = utils.lower_keys(ressource.get('format_mapping', {})) + attributes = set(attribute_mapping.keys()) | set(static_attributes.keys()) + default_ctx = ressource.get('attribute_context', {}) + ldap_filter = ressource.get('ldap_filter', '(objectclass=*)') + delete = ressource.get('delete', True) + User = compat.get_user_model() + qs = User.objects.filter(**ressource.get('a2_filter', {})) + todelete = set() + user_dns = set() + for batch in utils.batch(qs, options['batch_size']): + ldap_users = {} + filters = [] + for user in batch: + ctx = default_ctx.copy() + ctx['user'] = user + ctx = get_attributes(ctx) + ldap_attributes = {} + for ldap_attribute, a2_attributes in attribute_mapping.iteritems(): + if not isinstance(a2_attributes, (tuple, list)): + a2_attributes = [a2_attributes] + for a2_attribute in a2_attributes: + self.add_values(ldap_attributes, ldap_attribute, ctx.get(a2_attribute)) + for ldap_attribute, values in static_attributes.iteritems(): + self.add_values(ldap_attributes, ldap_attribute, values) + for ldap_attribute, fmt_tpls in format_mapping.iteritems(): + for fmt_tpl in fmt_tpls: + self.add_values(ldap_attributes, ldap_attribute, + [fmt_tpl.format(**ctx)]) + dn, filt = self.build_dn_and_filter(ressource, ldap_attributes) + user_dns.add(dn) + ldap_users[dn] = ldap_attributes + filters.append(filt) + batch_filter = ldap_filter + if filters: + batch_filter = self.format_filter(('&', (batch_filter, ('|', + filters)))) + existing_dn = set() + for dn, entry in conn.paged_search_ext_s(base_dn, + ldap.SCOPE_SUBTREE, + batch_filter, list(attributes)): + entry = utils.to_dict_of_set(utils.lower_keys(entry)) + if dn not in ldap_users: + todelete.add(dn) + continue + if entry == utils.to_dict_of_set(ldap_users[dn]): + # no need to update, entry is already ok + del ldap_users[dn] + continue + existing_dn.add(dn) + for dn, ldap_attributes in ldap_users.iteritems(): + if dn in existing_dn: + modlist = [] + for key, values in ldap_attributes: + modlist.append((ldap.MOD_REPLACE, key, values)) + if not fake: + conn.modify(dn, modlist) + if verbosity > 1: + print '- Replace %s values for %s' % (dn, ', '.join(ldap_attributes.keys())) + else: + if not fake: + conn.add(dn, ldap.modlist.addModlist(ldap_attributes)) + if verbosity > 1: + print '- Add %s with values for %s' % (dn, ', '.join(ldap_attributes.keys())) + # wait for results + if not fake: + for x in ldap_users: + conn.result() + for dn, entry in conn.paged_search_ext_s(base_dn, + ldap.SCOPE_SUBTREE, ldap_filter): + # ignore the basedn + if dn == base_dn: + continue + if dn not in user_dns and dn not in todelete: + if not fake: + todelete.add(dn) + if delete: + if verbosity > 1: + print '- Deleting:', ', '.join(todelete) + if not fake: + for dn in todelete: + conn.delete(dn) + for dn in todelete: + conn.result() diff --git a/src/authentic2_provisionning/tests/__init__.py b/src/authentic2_provisionning/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/authentic2_provisionning/tests/core.ldif b/src/authentic2_provisionning/tests/core.ldif new file mode 100644 index 0000000..822cf66 --- /dev/null +++ b/src/authentic2_provisionning/tests/core.ldif @@ -0,0 +1,600 @@ +# OpenLDAP Core schema +# $OpenLDAP: pkg/ldap/servers/slapd/schema/core.ldif,v 1.1.2.5 2007/01/02 21:44:09 kurt Exp $ +## This work is part of OpenLDAP Software . +## +## Copyright 1998-2007 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## . +# + +# The version of this file as distributed by the OpenLDAP Foundation +# contains text claiming copyright by the Internet Society and including +# the IETF RFC license, which does not meet Debian's Free Software +# Guidelines. However, apart from short and obvious comments, the text of +# this file is purely a functional interface specification, which is not +# subject to that license and is not copyrightable under US law. +# +# The license statement is retained below so as not to remove credit, but +# as best as we can determine, it is not applicable to the contents of +# this file. + +## Portions Copyright (C) The Internet Society (1997-2003). +## All Rights Reserved. +## +## This document and translations of it may be copied and furnished to +## others, and derivative works that comment on or otherwise explain it +## or assist in its implementation may be prepared, copied, published +## and distributed, in whole or in part, without restriction of any +## kind, provided that the above copyright notice and this paragraph are +## included on all such copies and derivative works. However, this +## document itself may not be modified in any way, such as by removing +## the copyright notice or references to the Internet Society or other +## Internet organizations, except as needed for the purpose of +## developing Internet standards in which case the procedures for +## copyrights defined in the Internet Standards process must be +## followed, or as required to translate it into languages other than +## English. +## +## The limited permissions granted above are perpetual and will not be +## revoked by the Internet Society or its successors or assigns. +## +## This document and the information contained herein is provided on an +## "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING +## TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING +## BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION +## HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF +## MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. +# +# +# +# Includes LDAPv3 schema items from: +# RFC 2252/2256 (LDAPv3) +# +# Select standard track schema items: +# RFC 1274 (uid/dc) +# RFC 2079 (URI) +# RFC 2247 (dc/dcObject) +# RFC 2587 (PKI) +# RFC 2589 (Dynamic Directory Services) +# +# Select informational schema items: +# RFC 2377 (uidObject) +# +# +# Standard attribute types from RFC 2256 +# +dn: cn=core,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: core +# +# system schema +#olcAttributeTypes: ( 2.5.4.0 NAME 'objectClass' +# DESC 'RFC2256: object classes of the entity' +# EQUALITY objectIdentifierMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 ) +# +# system schema +#olcAttributeTypes: ( 2.5.4.1 NAME ( 'aliasedObjectName' 'aliasedEntryName' ) +# DESC 'RFC2256: name of aliased object' +# EQUALITY distinguishedNameMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE ) +# +olcAttributeTypes: ( 2.5.4.2 NAME 'knowledgeInformation' + DESC 'RFC2256: knowledge information' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} ) +# +# system schema +#olcAttributeTypes: ( 2.5.4.3 NAME ( 'cn' 'commonName' ) +# DESC 'RFC2256: common name(s) for which the entity is known by' +# SUP name ) +# +olcAttributeTypes: ( 2.5.4.4 NAME ( 'sn' 'surname' ) + DESC 'RFC2256: last (family) name(s) for which the entity is known by' + SUP name ) +# +olcAttributeTypes: ( 2.5.4.5 NAME 'serialNumber' + DESC 'RFC2256: serial number of the entity' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{64} ) +# +olcAttributeTypes: ( 2.5.4.6 NAME ( 'c' 'countryName' ) + DESC 'RFC2256: ISO-3166 country 2-letter code' + SUP name SINGLE-VALUE ) +# +olcAttributeTypes: ( 2.5.4.7 NAME ( 'l' 'localityName' ) + DESC 'RFC2256: locality which this object resides in' + SUP name ) +# +olcAttributeTypes: ( 2.5.4.8 NAME ( 'st' 'stateOrProvinceName' ) + DESC 'RFC2256: state or province which this object resides in' + SUP name ) +# +olcAttributeTypes: ( 2.5.4.9 NAME ( 'street' 'streetAddress' ) + DESC 'RFC2256: street address of this object' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) +# +olcAttributeTypes: ( 2.5.4.10 NAME ( 'o' 'organizationName' ) + DESC 'RFC2256: organization this object belongs to' + SUP name ) +# +olcAttributeTypes: ( 2.5.4.11 NAME ( 'ou' 'organizationalUnitName' ) + DESC 'RFC2256: organizational unit this object belongs to' + SUP name ) +# +olcAttributeTypes: ( 2.5.4.12 NAME 'title' + DESC 'RFC2256: title associated with the entity' + SUP name ) +# +# system schema +#olcAttributeTypes: ( 2.5.4.13 NAME 'description' +# DESC 'RFC2256: descriptive information' +# EQUALITY caseIgnoreMatch +# SUBSTR caseIgnoreSubstringsMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} ) +# +# Deprecated by enhancedSearchGuide +olcAttributeTypes: ( 2.5.4.14 NAME 'searchGuide' + DESC 'RFC2256: search guide, deprecated by enhancedSearchGuide' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.25 ) +# +olcAttributeTypes: ( 2.5.4.15 NAME 'businessCategory' + DESC 'RFC2256: business category' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) +# +olcAttributeTypes: ( 2.5.4.16 NAME 'postalAddress' + DESC 'RFC2256: postal address' + EQUALITY caseIgnoreListMatch + SUBSTR caseIgnoreListSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 ) +# +olcAttributeTypes: ( 2.5.4.17 NAME 'postalCode' + DESC 'RFC2256: postal code' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{40} ) +# +olcAttributeTypes: ( 2.5.4.18 NAME 'postOfficeBox' + DESC 'RFC2256: Post Office Box' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{40} ) +# +olcAttributeTypes: ( 2.5.4.19 NAME 'physicalDeliveryOfficeName' + DESC 'RFC2256: Physical Delivery Office Name' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} ) +# +olcAttributeTypes: ( 2.5.4.20 NAME 'telephoneNumber' + DESC 'RFC2256: Telephone Number' + EQUALITY telephoneNumberMatch + SUBSTR telephoneNumberSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.50{32} ) +# +olcAttributeTypes: ( 2.5.4.21 NAME 'telexNumber' + DESC 'RFC2256: Telex Number' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.52 ) +# +olcAttributeTypes: ( 2.5.4.22 NAME 'teletexTerminalIdentifier' + DESC 'RFC2256: Teletex Terminal Identifier' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.51 ) +# +olcAttributeTypes: ( 2.5.4.23 NAME ( 'facsimileTelephoneNumber' 'fax' ) + DESC 'RFC2256: Facsimile (Fax) Telephone Number' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.22 ) +# +olcAttributeTypes: ( 2.5.4.24 NAME 'x121Address' + DESC 'RFC2256: X.121 Address' + EQUALITY numericStringMatch + SUBSTR numericStringSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{15} ) +# +olcAttributeTypes: ( 2.5.4.25 NAME 'internationaliSDNNumber' + DESC 'RFC2256: international ISDN number' + EQUALITY numericStringMatch + SUBSTR numericStringSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{16} ) +# +olcAttributeTypes: ( 2.5.4.26 NAME 'registeredAddress' + DESC 'RFC2256: registered postal address' + SUP postalAddress + SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 ) +# +olcAttributeTypes: ( 2.5.4.27 NAME 'destinationIndicator' + DESC 'RFC2256: destination indicator' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{128} ) +# +olcAttributeTypes: ( 2.5.4.28 NAME 'preferredDeliveryMethod' + DESC 'RFC2256: preferred delivery method' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.14 + SINGLE-VALUE ) +# +olcAttributeTypes: ( 2.5.4.29 NAME 'presentationAddress' + DESC 'RFC2256: presentation address' + EQUALITY presentationAddressMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.43 + SINGLE-VALUE ) +# +olcAttributeTypes: ( 2.5.4.30 NAME 'supportedApplicationContext' + DESC 'RFC2256: supported application context' + EQUALITY objectIdentifierMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 ) +# +olcAttributeTypes: ( 2.5.4.31 NAME 'member' + DESC 'RFC2256: member of a group' + SUP distinguishedName ) +# +olcAttributeTypes: ( 2.5.4.32 NAME 'owner' + DESC 'RFC2256: owner (of the object)' + SUP distinguishedName ) +# +olcAttributeTypes: ( 2.5.4.33 NAME 'roleOccupant' + DESC 'RFC2256: occupant of role' + SUP distinguishedName ) +# +# system schema +#olcAttributeTypes: ( 2.5.4.34 NAME 'seeAlso' +# DESC 'RFC2256: DN of related object' +# SUP distinguishedName ) +# +# system schema +#olcAttributeTypes: ( 2.5.4.35 NAME 'userPassword' +# DESC 'RFC2256/2307: password of user' +# EQUALITY octetStringMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{128} ) +# +# Must be transferred using ;binary +# with certificateExactMatch rule (per X.509) +olcAttributeTypes: ( 2.5.4.36 NAME 'userCertificate' + DESC 'RFC2256: X.509 user certificate, use ;binary' + EQUALITY certificateExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 ) +# +# Must be transferred using ;binary +# with certificateExactMatch rule (per X.509) +olcAttributeTypes: ( 2.5.4.37 NAME 'cACertificate' + DESC 'RFC2256: X.509 CA certificate, use ;binary' + EQUALITY certificateExactMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.8 ) +# +# Must be transferred using ;binary +olcAttributeTypes: ( 2.5.4.38 NAME 'authorityRevocationList' + DESC 'RFC2256: X.509 authority revocation list, use ;binary' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 ) +# +# Must be transferred using ;binary +olcAttributeTypes: ( 2.5.4.39 NAME 'certificateRevocationList' + DESC 'RFC2256: X.509 certificate revocation list, use ;binary' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 ) +# +# Must be stored and requested in the binary form +olcAttributeTypes: ( 2.5.4.40 NAME 'crossCertificatePair' + DESC 'RFC2256: X.509 cross certificate pair, use ;binary' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.10 ) +# +# 2.5.4.41 is defined above as it's used for subtyping +#olcAttributeTypes: ( 2.5.4.41 NAME 'name' +# EQUALITY caseIgnoreMatch +# SUBSTR caseIgnoreSubstringsMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} ) +# +olcAttributeTypes: ( 2.5.4.42 NAME ( 'givenName' 'gn' ) + DESC 'RFC2256: first name(s) for which the entity is known by' + SUP name ) +# +olcAttributeTypes: ( 2.5.4.43 NAME 'initials' + DESC 'RFC2256: initials of some or all of names, but not the surname(s).' + SUP name ) +# +olcAttributeTypes: ( 2.5.4.44 NAME 'generationQualifier' + DESC 'RFC2256: name qualifier indicating a generation' + SUP name ) +# +olcAttributeTypes: ( 2.5.4.45 NAME 'x500UniqueIdentifier' + DESC 'RFC2256: X.500 unique identifier' + EQUALITY bitStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.6 ) +# +olcAttributeTypes: ( 2.5.4.46 NAME 'dnQualifier' + DESC 'RFC2256: DN qualifier' + EQUALITY caseIgnoreMatch + ORDERING caseIgnoreOrderingMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 ) +# +olcAttributeTypes: ( 2.5.4.47 NAME 'enhancedSearchGuide' + DESC 'RFC2256: enhanced search guide' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.21 ) +# +olcAttributeTypes: ( 2.5.4.48 NAME 'protocolInformation' + DESC 'RFC2256: protocol information' + EQUALITY protocolInformationMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.42 ) +# +# 2.5.4.49 is defined above as it's used for subtyping +#olcAttributeTypes: ( 2.5.4.49 NAME 'distinguishedName' +# EQUALITY distinguishedNameMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) +# +olcAttributeTypes: ( 2.5.4.50 NAME 'uniqueMember' + DESC 'RFC2256: unique member of a group' + EQUALITY uniqueMemberMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.34 ) +# +olcAttributeTypes: ( 2.5.4.51 NAME 'houseIdentifier' + DESC 'RFC2256: house identifier' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} ) +# +# Must be transferred using ;binary +olcAttributeTypes: ( 2.5.4.52 NAME 'supportedAlgorithms' + DESC 'RFC2256: supported algorithms' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.49 ) +# +# Must be transferred using ;binary +olcAttributeTypes: ( 2.5.4.53 NAME 'deltaRevocationList' + DESC 'RFC2256: delta revocation list; use ;binary' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.9 ) +# +olcAttributeTypes: ( 2.5.4.54 NAME 'dmdName' + DESC 'RFC2256: name of DMD' + SUP name ) +# +olcAttributeTypes: ( 2.5.4.65 NAME 'pseudonym' + DESC 'X.520(4th): pseudonym for the object' + SUP name ) +# +# Standard object classes from RFC2256 +# +# system schema +#olcObjectClasses: ( 2.5.6.1 NAME 'alias' +# DESC 'RFC2256: an alias' +# SUP top STRUCTURAL +# MUST aliasedObjectName ) +# +olcObjectClasses: ( 2.5.6.2 NAME 'country' + DESC 'RFC2256: a country' + SUP top STRUCTURAL + MUST c + MAY ( searchGuide $ description ) ) +# +olcObjectClasses: ( 2.5.6.3 NAME 'locality' + DESC 'RFC2256: a locality' + SUP top STRUCTURAL + MAY ( street $ seeAlso $ searchGuide $ st $ l $ description ) ) +# +olcObjectClasses: ( 2.5.6.4 NAME 'organization' + DESC 'RFC2256: an organization' + SUP top STRUCTURAL + MUST o + MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $ + x121Address $ registeredAddress $ destinationIndicator $ + preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ + telephoneNumber $ internationaliSDNNumber $ + facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ + postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) ) +# +olcObjectClasses: ( 2.5.6.5 NAME 'organizationalUnit' + DESC 'RFC2256: an organizational unit' + SUP top STRUCTURAL + MUST ou + MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $ + x121Address $ registeredAddress $ destinationIndicator $ + preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ + telephoneNumber $ internationaliSDNNumber $ + facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ + postalAddress $ physicalDeliveryOfficeName $ st $ l $ description ) ) +# +olcObjectClasses: ( 2.5.6.6 NAME 'person' + DESC 'RFC2256: a person' + SUP top STRUCTURAL + MUST ( sn $ cn ) + MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) ) +# +olcObjectClasses: ( 2.5.6.7 NAME 'organizationalPerson' + DESC 'RFC2256: an organizational person' + SUP person STRUCTURAL + MAY ( title $ x121Address $ registeredAddress $ destinationIndicator $ + preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ + telephoneNumber $ internationaliSDNNumber $ + facsimileTelephoneNumber $ street $ postOfficeBox $ postalCode $ + postalAddress $ physicalDeliveryOfficeName $ ou $ st $ l ) ) +# +olcObjectClasses: ( 2.5.6.8 NAME 'organizationalRole' + DESC 'RFC2256: an organizational role' + SUP top STRUCTURAL + MUST cn + MAY ( x121Address $ registeredAddress $ destinationIndicator $ + preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ + telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ + seeAlso $ roleOccupant $ preferredDeliveryMethod $ street $ + postOfficeBox $ postalCode $ postalAddress $ + physicalDeliveryOfficeName $ ou $ st $ l $ description ) ) +# +olcObjectClasses: ( 2.5.6.9 NAME 'groupOfNames' + DESC 'RFC2256: a group of names (DNs)' + SUP top STRUCTURAL + MUST ( member $ cn ) + MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description ) ) +# +olcObjectClasses: ( 2.5.6.10 NAME 'residentialPerson' + DESC 'RFC2256: an residential person' + SUP person STRUCTURAL + MUST l + MAY ( businessCategory $ x121Address $ registeredAddress $ + destinationIndicator $ preferredDeliveryMethod $ telexNumber $ + teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ + facsimileTelephoneNumber $ preferredDeliveryMethod $ street $ + postOfficeBox $ postalCode $ postalAddress $ + physicalDeliveryOfficeName $ st $ l ) ) +# +olcObjectClasses: ( 2.5.6.11 NAME 'applicationProcess' + DESC 'RFC2256: an application process' + SUP top STRUCTURAL + MUST cn + MAY ( seeAlso $ ou $ l $ description ) ) +# +olcObjectClasses: ( 2.5.6.12 NAME 'applicationEntity' + DESC 'RFC2256: an application entity' + SUP top STRUCTURAL + MUST ( presentationAddress $ cn ) + MAY ( supportedApplicationContext $ seeAlso $ ou $ o $ l $ + description ) ) +# +olcObjectClasses: ( 2.5.6.13 NAME 'dSA' + DESC 'RFC2256: a directory system agent (a server)' + SUP applicationEntity STRUCTURAL + MAY knowledgeInformation ) +# +olcObjectClasses: ( 2.5.6.14 NAME 'device' + DESC 'RFC2256: a device' + SUP top STRUCTURAL + MUST cn + MAY ( serialNumber $ seeAlso $ owner $ ou $ o $ l $ description ) ) +# +olcObjectClasses: ( 2.5.6.15 NAME 'strongAuthenticationUser' + DESC 'RFC2256: a strong authentication user' + SUP top AUXILIARY + MUST userCertificate ) +# +olcObjectClasses: ( 2.5.6.16 NAME 'certificationAuthority' + DESC 'RFC2256: a certificate authority' + SUP top AUXILIARY + MUST ( authorityRevocationList $ certificateRevocationList $ + cACertificate ) MAY crossCertificatePair ) +# +olcObjectClasses: ( 2.5.6.17 NAME 'groupOfUniqueNames' + DESC 'RFC2256: a group of unique names (DN and Unique Identifier)' + SUP top STRUCTURAL + MUST ( uniqueMember $ cn ) + MAY ( businessCategory $ seeAlso $ owner $ ou $ o $ description ) ) +# +olcObjectClasses: ( 2.5.6.18 NAME 'userSecurityInformation' + DESC 'RFC2256: a user security information' + SUP top AUXILIARY + MAY ( supportedAlgorithms ) ) +# +olcObjectClasses: ( 2.5.6.16.2 NAME 'certificationAuthority-V2' + SUP certificationAuthority + AUXILIARY MAY ( deltaRevocationList ) ) +# +olcObjectClasses: ( 2.5.6.19 NAME 'cRLDistributionPoint' + SUP top STRUCTURAL + MUST ( cn ) + MAY ( certificateRevocationList $ authorityRevocationList $ + deltaRevocationList ) ) +# +olcObjectClasses: ( 2.5.6.20 NAME 'dmd' + SUP top STRUCTURAL + MUST ( dmdName ) + MAY ( userPassword $ searchGuide $ seeAlso $ businessCategory $ + x121Address $ registeredAddress $ destinationIndicator $ + preferredDeliveryMethod $ telexNumber $ teletexTerminalIdentifier $ + telephoneNumber $ internationaliSDNNumber $ facsimileTelephoneNumber $ + street $ postOfficeBox $ postalCode $ postalAddress $ + physicalDeliveryOfficeName $ st $ l $ description ) ) +# +# +# Object Classes from RFC 2587 +# +olcObjectClasses: ( 2.5.6.21 NAME 'pkiUser' + DESC 'RFC2587: a PKI user' + SUP top AUXILIARY + MAY userCertificate ) +# +olcObjectClasses: ( 2.5.6.22 NAME 'pkiCA' + DESC 'RFC2587: PKI certificate authority' + SUP top AUXILIARY + MAY ( authorityRevocationList $ certificateRevocationList $ + cACertificate $ crossCertificatePair ) ) +# +olcObjectClasses: ( 2.5.6.23 NAME 'deltaCRL' + DESC 'RFC2587: PKI user' + SUP top AUXILIARY + MAY deltaRevocationList ) +# +# +# Standard Track URI label schema from RFC 2079 +# system schema +#olcAttributeTypes: ( 1.3.6.1.4.1.250.1.57 NAME 'labeledURI' +# DESC 'RFC2079: Uniform Resource Identifier with optional label' +# EQUALITY caseExactMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +# +olcObjectClasses: ( 1.3.6.1.4.1.250.3.15 NAME 'labeledURIObject' + DESC 'RFC2079: object that contains the URI attribute type' + MAY ( labeledURI ) + SUP top AUXILIARY ) +# +# +# Derived from RFC 1274, but with new "short names" +# +#olcAttributeTypes: ( 0.9.2342.19200300.100.1.1 +# NAME ( 'uid' 'userid' ) +# DESC 'RFC1274: user identifier' +# EQUALITY caseIgnoreMatch +# SUBSTR caseIgnoreSubstringsMatch +# SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +# +olcAttributeTypes: ( 0.9.2342.19200300.100.1.3 + NAME ( 'mail' 'rfc822Mailbox' ) + DESC 'RFC1274: RFC822 Mailbox' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) +# +olcObjectClasses: ( 0.9.2342.19200300.100.4.19 NAME 'simpleSecurityObject' + DESC 'RFC1274: simple security object' + SUP top AUXILIARY + MUST userPassword ) +# +# RFC 1274 + RFC 2247 +olcAttributeTypes: ( 0.9.2342.19200300.100.1.25 + NAME ( 'dc' 'domainComponent' ) + DESC 'RFC1274/2247: domain component' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) +# +# RFC 2247 +olcObjectClasses: ( 1.3.6.1.4.1.1466.344 NAME 'dcObject' + DESC 'RFC2247: domain component object' + SUP top AUXILIARY MUST dc ) +# +# RFC 2377 +olcObjectClasses: ( 1.3.6.1.1.3.1 NAME 'uidObject' + DESC 'RFC2377: uid object' + SUP top AUXILIARY MUST uid ) +# +# From COSINE Pilot +olcAttributeTypes: ( 0.9.2342.19200300.100.1.37 + NAME 'associatedDomain' + DESC 'RFC1274: domain associated with object' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +# +# RFC 2459 -- deprecated in favor of 'mail' (in cosine.schema) +olcAttributeTypes: ( 1.2.840.113549.1.9.1 + NAME ( 'email' 'emailAddress' 'pkcs9email' ) + DESC 'RFC3280: legacy attribute for email addresses in DNs' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} ) +# diff --git a/src/authentic2_provisionning/tests/cosine.ldif b/src/authentic2_provisionning/tests/cosine.ldif new file mode 100644 index 0000000..9b437f8 --- /dev/null +++ b/src/authentic2_provisionning/tests/cosine.ldif @@ -0,0 +1,200 @@ +# RFC1274: Cosine and Internet X.500 schema +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 1998-2012 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## . +# +# RFC1274: Cosine and Internet X.500 schema +# +# This file contains LDAPv3 schema derived from X.500 COSINE "pilot" +# schema. As this schema was defined for X.500(89), some +# oddities were introduced in the mapping to LDAPv3. The +# mappings were based upon: draft-ietf-asid-ldapv3-attributes-03.txt +# (a work in progress) +# +# Note: It seems that the pilot schema evolved beyond what was +# described in RFC1274. However, this document attempts to describes +# RFC1274 as published. +# +# Depends on core.ldif +# +# This file was automatically generated from cosine.schema; see that +# file for complete background. +# +dn: cn=cosine,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: cosine +olcAttributeTypes: ( 0.9.2342.19200300.100.1.2 NAME 'textEncodedORAddress' + EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1. + 1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.4 NAME 'info' DESC 'RFC1274: g + eneral information' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2048} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.5 NAME ( 'drink' 'favouriteDri + nk' ) DESC 'RFC1274: favorite drink' EQUALITY caseIgnoreMatch SUBSTR caseIgno + reSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.6 NAME 'roomNumber' DESC 'RFC1 + 274: room number' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch S + YNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.7 NAME 'photo' DESC 'RFC1274: + photo (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.23{25000} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.8 NAME 'userClass' DESC 'RFC12 + 74: category of user' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMat + ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.9 NAME 'host' DESC 'RFC1274: h + ost computer' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTA + X 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.10 NAME 'manager' DESC 'RFC127 + 4: DN of manager' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115 + .121.1.12 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.11 NAME 'documentIdentifier' D + ESC 'RFC1274: unique identifier of document' EQUALITY caseIgnoreMatch SUBSTR + caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.12 NAME 'documentTitle' DESC ' + RFC1274: title of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstri + ngsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.13 NAME 'documentVersion' DES + C 'RFC1274: version of document' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSu + bstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.14 NAME 'documentAuthor' DESC + 'RFC1274: DN of author of document' EQUALITY distinguishedNameMatch SYNTAX 1 + .3.6.1.4.1.1466.115.121.1.12 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.15 NAME 'documentLocation' DE + SC 'RFC1274: location of document original' EQUALITY caseIgnoreMatch SUBSTR c + aseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.20 NAME ( 'homePhone' 'homeTe + lephoneNumber' ) DESC 'RFC1274: home telephone number' EQUALITY telephoneNumb + erMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121 + .1.50 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.21 NAME 'secretary' DESC 'RFC + 1274: DN of secretary' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.146 + 6.115.121.1.12 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.22 NAME 'otherMailbox' SYNTAX + 1.3.6.1.4.1.1466.115.121.1.39 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.26 NAME 'aRecord' EQUALITY ca + seIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.27 NAME 'mDRecord' EQUALITY c + aseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.28 NAME 'mXRecord' EQUALITY c + aseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.29 NAME 'nSRecord' EQUALITY c + aseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.30 NAME 'sOARecord' EQUALITY + caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.31 NAME 'cNAMERecord' EQUALIT + Y caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.38 NAME 'associatedName' DESC + 'RFC1274: DN of entry associated with domain' EQUALITY distinguishedNameMatc + h SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.39 NAME 'homePostalAddress' D + ESC 'RFC1274: home postal address' EQUALITY caseIgnoreListMatch SUBSTR caseIg + noreListSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.41 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.40 NAME 'personalTitle' DESC + 'RFC1274: personal title' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstring + sMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.41 NAME ( 'mobile' 'mobileTel + ephoneNumber' ) DESC 'RFC1274: mobile telephone number' EQUALITY telephoneNum + berMatch SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12 + 1.1.50 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.42 NAME ( 'pager' 'pagerTelep + honeNumber' ) DESC 'RFC1274: pager telephone number' EQUALITY telephoneNumber + Match SUBSTR telephoneNumberSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1 + .50 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.43 NAME ( 'co' 'friendlyCount + ryName' ) DESC 'RFC1274: friendly country name' EQUALITY caseIgnoreMatch SUBS + TR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.44 NAME 'uniqueIdentifier' DE + SC 'RFC1274: unique identifer' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.14 + 66.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.45 NAME 'organizationalStatus + ' DESC 'RFC1274: organizational status' EQUALITY caseIgnoreMatch SUBSTR caseI + gnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.46 NAME 'janetMailbox' DESC ' + RFC1274: Janet mailbox' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5Subst + ringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.47 NAME 'mailPreferenceOption + ' DESC 'RFC1274: mail preference option' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.48 NAME 'buildingName' DESC ' + RFC1274: name of building' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstrin + gsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.49 NAME 'dSAQuality' DESC 'RF + C1274: DSA Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.19 SINGLE-VALUE ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.50 NAME 'singleLevelQuality' + DESC 'RFC1274: Single Level Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1.13 SIN + GLE-VALUE ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.51 NAME 'subtreeMinimumQualit + y' DESC 'RFC1274: Subtree Mininum Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1. + 13 SINGLE-VALUE ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.52 NAME 'subtreeMaximumQualit + y' DESC 'RFC1274: Subtree Maximun Quality' SYNTAX 1.3.6.1.4.1.1466.115.121.1. + 13 SINGLE-VALUE ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.53 NAME 'personalSignature' D + ESC 'RFC1274: Personal Signature (G3 fax)' SYNTAX 1.3.6.1.4.1.1466.115.121.1. + 23 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.54 NAME 'dITRedirect' DESC 'R + FC1274: DIT Redirect' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466 + .115.121.1.12 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.55 NAME 'audio' DESC 'RFC1274 + : audio (u-law)' SYNTAX 1.3.6.1.4.1.1466.115.121.1.4{25000} ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.56 NAME 'documentPublisher' D + ESC 'RFC1274: publisher of document' EQUALITY caseIgnoreMatch SUBSTR caseIgno + reSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.4 NAME ( 'pilotPerson' 'newPilo + tPerson' ) SUP person STRUCTURAL MAY ( userid $ textEncodedORAddress $ rfc822 + Mailbox $ favouriteDrink $ roomNumber $ userClass $ homeTelephoneNumber $ hom + ePostalAddress $ secretary $ personalTitle $ preferredDeliveryMethod $ busine + ssCategory $ janetMailbox $ otherMailbox $ mobileTelephoneNumber $ pagerTelep + honeNumber $ organizationalStatus $ mailPreferenceOption $ personalSignature + ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.5 NAME 'account' SUP top STRUCT + URAL MUST userid MAY ( description $ seeAlso $ localityName $ organizationNam + e $ organizationalUnitName $ host ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.6 NAME 'document' SUP top STRUC + TURAL MUST documentIdentifier MAY ( commonName $ description $ seeAlso $ loca + lityName $ organizationName $ organizationalUnitName $ documentTitle $ docume + ntVersion $ documentAuthor $ documentLocation $ documentPublisher ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.7 NAME 'room' SUP top STRUCTURA + L MUST commonName MAY ( roomNumber $ description $ seeAlso $ telephoneNumber + ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.9 NAME 'documentSeries' SUP top + STRUCTURAL MUST commonName MAY ( description $ seeAlso $ telephonenumber $ l + ocalityName $ organizationName $ organizationalUnitName ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.13 NAME 'domain' SUP top STRUCT + URAL MUST domainComponent MAY ( associatedName $ organizationName $ descripti + on $ businessCategory $ seeAlso $ searchGuide $ userPassword $ localityName $ + stateOrProvinceName $ streetAddress $ physicalDeliveryOfficeName $ postalAdd + ress $ postalCode $ postOfficeBox $ streetAddress $ facsimileTelephoneNumber + $ internationalISDNNumber $ telephoneNumber $ teletexTerminalIdentifier $ tel + exNumber $ preferredDeliveryMethod $ destinationIndicator $ registeredAddress + $ x121Address ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.14 NAME 'RFC822localPart' SUP d + omain STRUCTURAL MAY ( commonName $ surname $ description $ seeAlso $ telepho + neNumber $ physicalDeliveryOfficeName $ postalAddress $ postalCode $ postOffi + ceBox $ streetAddress $ facsimileTelephoneNumber $ internationalISDNNumber $ + telephoneNumber $ teletexTerminalIdentifier $ telexNumber $ preferredDelivery + Method $ destinationIndicator $ registeredAddress $ x121Address ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.15 NAME 'dNSDomain' SUP domain + STRUCTURAL MAY ( ARecord $ MDRecord $ MXRecord $ NSRecord $ SOARecord $ CNAME + Record ) ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.17 NAME 'domainRelatedObject' D + ESC 'RFC1274: an object related to an domain' SUP top AUXILIARY MUST associat + edDomain ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.18 NAME 'friendlyCountry' SUP c + ountry STRUCTURAL MUST friendlyCountryName ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.20 NAME 'pilotOrganization' SU + P ( organization $ organizationalUnit ) STRUCTURAL MAY buildingName ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.21 NAME 'pilotDSA' SUP dsa STR + UCTURAL MAY dSAQuality ) +olcObjectClasses: ( 0.9.2342.19200300.100.4.22 NAME 'qualityLabelledData' + SUP top AUXILIARY MUST dsaQuality MAY ( subtreeMinimumQuality $ subtreeMaximu + mQuality ) ) diff --git a/src/authentic2_provisionning/tests/inetorgperson.ldif b/src/authentic2_provisionning/tests/inetorgperson.ldif new file mode 100644 index 0000000..31a0080 --- /dev/null +++ b/src/authentic2_provisionning/tests/inetorgperson.ldif @@ -0,0 +1,69 @@ +# InetOrgPerson (RFC2798) +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 1998-2012 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## . +# +# InetOrgPerson (RFC2798) +# +# Depends upon +# Definition of an X.500 Attribute Type and an Object Class to Hold +# Uniform Resource Identifiers (URIs) [RFC2079] +# (core.ldif) +# +# A Summary of the X.500(96) User Schema for use with LDAPv3 [RFC2256] +# (core.ldif) +# +# The COSINE and Internet X.500 Schema [RFC1274] (cosine.ldif) +# +# This file was automatically generated from inetorgperson.schema; see +# that file for complete references. +# +dn: cn=inetorgperson,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: inetorgperson +olcAttributeTypes: ( 2.16.840.1.113730.3.1.1 NAME 'carLicense' DESC 'RFC279 + 8: vehicle license or registration plate' EQUALITY caseIgnoreMatch SUBSTR cas + eIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.2 NAME 'departmentNumber' DESC ' + RFC2798: identifies a department within an organization' EQUALITY caseIgnoreM + atch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.241 NAME 'displayName' DESC 'RFC + 2798: preferred name to be used when displaying entries' EQUALITY caseIgnoreM + atch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SI + NGLE-VALUE ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.3 NAME 'employeeNumber' DESC 'RF + C2798: numerically identifies an employee within an organization' EQUALITY ca + seIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.12 + 1.1.15 SINGLE-VALUE ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.4 NAME 'employeeType' DESC 'RFC2 + 798: type of employment for a person' EQUALITY caseIgnoreMatch SUBSTR caseIgn + oreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( 0.9.2342.19200300.100.1.60 NAME 'jpegPhoto' DESC 'RFC2 + 798: a JPEG image' SYNTAX 1.3.6.1.4.1.1466.115.121.1.28 ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.39 NAME 'preferredLanguage' DESC + 'RFC2798: preferred written or spoken language for a person' EQUALITY caseIg + noreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1. + 15 SINGLE-VALUE ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.40 NAME 'userSMIMECertificate' D + ESC 'RFC2798: PKCS#7 SignedData used to support S/MIME' SYNTAX 1.3.6.1.4.1.14 + 66.115.121.1.5 ) +olcAttributeTypes: ( 2.16.840.1.113730.3.1.216 NAME 'userPKCS12' DESC 'RFC2 + 798: personal identity information, a PKCS #12 PFX' SYNTAX 1.3.6.1.4.1.1466.1 + 15.121.1.5 ) +olcObjectClasses: ( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' DESC 'RFC2 + 798: Internet Organizational Person' SUP organizationalPerson STRUCTURAL MAY + ( audio $ businessCategory $ carLicense $ departmentNumber $ displayName $ em + ployeeNumber $ employeeType $ givenName $ homePhone $ homePostalAddress $ ini + tials $ jpegPhoto $ labeledURI $ mail $ manager $ mobile $ o $ pager $ photo + $ roomNumber $ secretary $ uid $ userCertificate $ x500uniqueIdentifier $ pre + ferredLanguage $ userSMIMECertificate $ userPKCS12 ) ) diff --git a/src/authentic2_provisionning/tests/test_ldap.py b/src/authentic2_provisionning/tests/test_ldap.py new file mode 100644 index 0000000..d55c4fa --- /dev/null +++ b/src/authentic2_provisionning/tests/test_ldap.py @@ -0,0 +1,82 @@ +import ldap + +from django.test import TestCase +from unittest import skipUnless + +from authentic2_provisionning.ldap_utils import Slapd, has_slapd +from django.core.management import call_command +from authentic2 import compat + + +@skipUnless(has_slapd(), 'slapd is not installed') +class LDAPBaseTestCase(TestCase): + slapd = None + + def setUp(self): + if self.slapd is None: + self.slapd = Slapd() + self.slapd.checkpoint() + # fresh connection + self.conn = self.slapd.get_connection() + + def tearDown(self): + self.slapd.restore() + +class WhoamiTest(LDAPBaseTestCase): + def test_whoami(self): + self.conn.simple_bind_s('uid=admin,o=orga', 'admin') + assert self.conn.whoami_s() == 'dn:uid=admin,o=orga' + +class ProvisionTest(LDAPBaseTestCase): + def test_ldap_provisionning(self): + ressources = [{ + 'name': 'ldap', + 'url': self.slapd.ldapi_url, + 'bind_dn': 'uid=admin,o=orga', + 'bind_pw': 'admin', + 'base_dn': 'o=orga', + 'rdn_attributes': ['uid',], + 'attribute_mapping': { + 'uid': 'django_user_username', + 'givenName': 'django_user_first_name', + 'sn': 'django_user_last_name', + 'mail': 'django_user_email', + }, + 'format_mapping': { + 'cn': ['{django_user_first_name} {django_user_last_name}'], + }, + 'static_attributes': { + 'objectclass': 'inetorgperson', + }, + 'ldap_filter': '(objectclass=inetorgperson)', + }] + User = compat.get_user_model() + users = [User(username='john.doe%s' % i, + first_name='john', + last_name='doe', + email='john.doe@example.com') for i in range(1000)] + + User.objects.bulk_create(users) + self.slapd.add_ldif('''dn: uid=test,o=orga +objectClass: inetOrgPerson +uid: test +cn: test +sn: test +gn: test +mail: test''') + with self.settings(A2_PROVISIONNING_RESSOURCES=ressources): + call_command('provision', 'ldap', batch_size=1000) + results = self.conn.search_s('o=orga', ldap.SCOPE_ONELEVEL) + self.assertEqual(len(results), 1000) + for dn, entry in results: + uid = entry['uid'][0] + self.assertTrue(uid.startswith('john.doe')) + self.assertEquals(entry, { + 'objectClass': ['inetOrgPerson'], + 'uid': [uid], + 'sn': [users[0].last_name], + 'givenName': [users[0].first_name], + 'cn': ['%s %s' % (users[0].first_name, users[0].last_name)], + 'mail': [users[0].email]}) + + -- 1.9.1