commit 2c30289acf7bdc2768ee7268d80969f81e2aae23 Author: Benjamin Dauvergne Date: Tue Feb 2 16:59:12 2016 +0100 initial release diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..ba924cb --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +recursive-include src/ldaptools/schemas *.ldif +recursive-include tests *.py +include tox.ini +include VERSION +include README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..4de1ae9 --- /dev/null +++ b/README.rst @@ -0,0 +1,57 @@ +ldaptools +========= + +Helper modules to work with LDAP directories and test LDAP tools against OpenLDAP. + +- `ldaptools.ldif_utils`: simple parser for LDIF files +- `ldaptools.ldap_source`: generate a stream of LDAP entries from an LDAP URL +- `ldaptools.synchronize`: synchronization class to synchronize a source of LDAP records with a target +- `ldaptools.paged`: an LDAPObject implementating paged search requests +- `ldaptools.ldapsync`: a command line client to the Synchronize class + +ldapsync +======== + + usage: ldapsync [-h] --object-class-pivot OBJECT_CLASS_PIVOT + [--attributes-file ATTRIBUTES_FILE] [--attributes ATTRIBUTES] + --source-uri SOURCE_URI --source-base-dn SOURCE_BASE_DN + [--source-bind-dn SOURCE_BIND_DN] + [--source-bind-password SOURCE_BIND_PASSWORD] --target-uri + TARGET_URI --target-base-dn TARGET_BASE_DN + [--target-bind-dn TARGET_BIND_DN] + [--target-bind-password TARGET_BIND_PASSWORD] [--fake] + [--verbose] + + Synchronize an LDIF file or a source LDAP directory to another directory Base + DN of the source is remapped to another DN in the target directory + + optional arguments: + -h, --help show this help message and exit + --object-class-pivot OBJECT_CLASS_PIVOT + an objectClass and an attribute name which is the + unique identifier for this class + --attributes-file ATTRIBUTES_FILE + a file containing the list of attributes to + synchronize + --attributes ATTRIBUTES + a list of attribute names separated by spaces + --source-uri SOURCE_URI + URL of an LDAP directory (ldapi://, ldap:// or + ldaps://) or path of and LDIF file + --source-base-dn SOURCE_BASE_DN + base DN of the source + --source-bind-dn SOURCE_BIND_DN + bind DN for a source LDAP directory + --source-bind-password SOURCE_BIND_PASSWORD + bind password for a source LDAP directory + --target-uri TARGET_URI + URL of the target LDAP directory + --target-base-dn TARGET_BASE_DN + base DN of the target LDAP directory + --target-bind-dn TARGET_BIND_DN + bind DN for a target LDAP directory + --target-bind-password TARGET_BIND_PASSWORD + bind password for a target LDAP directory + --fake compute synchronization actions but do not apply + --verbose print all actions to stdout + diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..57d4c75 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +python-ldaptools (1.0-1) wheezy; urgency=low + + * Initial package, targetting wheezy. + + -- Benjamin Dauvergne Mon, 11 Feb 2016 20: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..e5b03b5 --- /dev/null +++ b/debian/control @@ -0,0 +1,14 @@ +Source: python-ldaptools +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/ldaptools/ + +Package: python-ldaptools +Architecture: all +Depends: ${python:Depends} +XB-Python-Version: ${python:Versions} +Description: helper library for python-ldap and openldap 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/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..f1e5bae --- /dev/null +++ b/setup.py @@ -0,0 +1,66 @@ +#! /usr/bin/env python +# +''' + Setup script for Authentic 2 +''' + +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="ldaptools", + version=get_version(), + license="AGPLv3+", + description="ldaptools", + 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=find_packages('src'), + package_dir={'': 'src'}, + include_package_data=True, + install_requires=['python-ldap'], + entry_points={ + 'console_scripts': ['ldapsync=ldaptools.ldapsync.cmd:main'], + }, + zip_safe=False, + cmdclass={'sdist': eo_sdist}) diff --git a/src/ldaptools/__init__.py b/src/ldaptools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/ldaptools/exceptions.py b/src/ldaptools/exceptions.py new file mode 100644 index 0000000..7b298ac --- /dev/null +++ b/src/ldaptools/exceptions.py @@ -0,0 +1,12 @@ + + +class CommandError(Exception): + def __init__(self, *args, **kwargs): + self.err_code = kwargs.pop('err_code', None) + super(CommandError, self).__init__(*args, **kwargs) + + +class ConfigError(CommandError): + def __init__(self, *args, **kwargs): + kwargs['err_code'] = 1 + super(ConfigError, self).__init__(*args, **kwargs) diff --git a/src/ldaptools/ldap_source.py b/src/ldaptools/ldap_source.py new file mode 100644 index 0000000..9fd85eb --- /dev/null +++ b/src/ldaptools/ldap_source.py @@ -0,0 +1,32 @@ +import ldap + +from ldaptools.utils import idict, istr + + +class LDAPSource(object): + entries = None + conn = None + base_dn = None + filterstr = '(objectclass=*)' + attributes = None + + def __init__(self, conn, base_dn=None, filterstr=None, attributes=None): + assert conn + self.conn = conn or self.conn + self.filterstr = filterstr or self.filterstr + self.attributes = attributes or self.attributes + self.base_dn = base_dn or self.base_dn + + def search(self): + for dn, entry in self.conn.paged_search_ext_s(self.base_dn, ldap.SCOPE_SUBTREE, + filterstr=self.filterstr, + attrlist=self.attributes): + if not dn: + continue + entry = idict(entry) + if 'objectclass' in entry: + entry['objectclass'] = [istr(v) for v in entry['objectclass']] + yield dn, entry + + def __iter__(self): + return self.search() diff --git a/src/ldaptools/ldapsync/__init__.py b/src/ldaptools/ldapsync/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/ldaptools/ldapsync/cmd.py b/src/ldaptools/ldapsync/cmd.py new file mode 100644 index 0000000..94febd7 --- /dev/null +++ b/src/ldaptools/ldapsync/cmd.py @@ -0,0 +1,142 @@ +import argparse +import sys + +import ldap.sasl + +from ldaptools import ldif_utils, paged, ldap_source +from ldaptools.synchronize import Synchronize + + +def source_uri(value): + for prefix in ['ldapi://', 'ldap://', 'ldaps://']: + if value.startswith(prefix): + return value + raise argparse.ArgumentTypeError('%r is not an LDAP url' % value) + + +def or_type(f1, f2): + def f(value): + try: + return f1(value) + except argparse.ArgumentTypeError, e1: + try: + return f2(value) + except argparse.ArgumentTypeError, e2: + raise argparse.ArgumentTypeError('%s and %s' % (e1.args[0], e2.args[0])) + return f + + +def object_class_pivot(value): + value = filter(None, map(str.strip, map(str.lower, value.split()))) + if len(value) != 2: + raise argparse.ArgumentTypeError('%r is not a pair of an objectClass and an attribute name') + return value + + +def main(args=None): + parser = argparse.ArgumentParser(description='''\ +Synchronize an LDIF file or a source LDAP directory to another directory + +Base DN of the source is remapped to another DN in the target directory''') + parser.add_argument('--object-class-pivot', + required=True, + type=object_class_pivot, + action='append', + help='an objectClass and an attribute name which is the unique identifier ' + 'for this class') + parser.add_argument('--attributes-file', + type=argparse.FileType('r'), + help='a file containing the list of attributes to synchronize') + parser.add_argument('--attributes', + help='a list of attribute names separated by spaces') + parser.add_argument('--source-uri', + required=True, + type=or_type(source_uri, argparse.FileType('r')), + help='URL of an LDAP directory (ldapi://, ldap:// or ldaps://) or path of ' + 'and LDIF file') + parser.add_argument('--source-base-dn', + required=True, + help='base DN of the source') + parser.add_argument('--source-bind-dn', + help='bind DN for a source LDAP directory') + parser.add_argument('--source-bind-password', + help='bind password for a source LDAP directory') + + parser.add_argument('--target-uri', + type=source_uri, + required=True, + help='URL of the target LDAP directory') + parser.add_argument('--target-base-dn', + required=True, + help='base DN of the target LDAP directory') + parser.add_argument('--target-bind-dn', + help='bind DN for a target LDAP directory') + parser.add_argument('--target-bind-password', + help='bind password for a target LDAP directory') + parser.add_argument('--fake', + action='store_true', + help='compute synchronization actions but do not apply') + parser.add_argument('--verbose', + action='store_true', + help='print all actions to stdout') + + options = parser.parse_args(args=args) + + attributes = set() + + if options.attributes_file: + attributes.update([attribute.strip().lower() for attribute in options.attributes_file]) + if options.attributes: + for attribute in options.attributes.split(): + attribute = attribute.strip().lower() + if attribute: + attributes.add(attribute) + attributes = list(attributes) + if not attributes: + parser.print_help() + print 'Yout must give at least one attribute to synchronize' + + if options.verbose: + print 'Synchronizing ', + if hasattr(options.source_uri, 'read'): + if options.verbose: + print options.source_uri.name, + source = ldif_utils.ListLDIFParser(options.source_uri) + source.parse() + else: + if options.verbose: + print options.source_uri, + conn = paged.PagedLDAPObject(options.source_uri) + if options.source_uri.startswith('ldapi://'): + conn.sasl_interactive_bind_s("", ldap.sasl.external()) + elif options.source_bind_dn and options.source_bind_password: + conn.simple_bind_s(options.source_bind_dn, options.source_bind_password) + + source = ldap_source.LDAPSource(conn, base_dn=options.source_base_dn, attributes=attributes) + + if options.verbose: + print 'to', options.target_uri + target_conn = paged.PagedLDAPObject(options.target_uri) + if options.target_uri.startswith('ldapi://'): + target_conn.sasl_interactive_bind_s("", ldap.sasl.external()) + elif options.target_bind_dn and options.target_bind_dn: + target_conn.simple_bind_s(options.target_bind_dn, options.target_bind_password) + synchronize = Synchronize(source, options.source_base_dn, + target_conn, options.target_base_dn, + pivot_attributes=options.object_class_pivot, + attributes=attributes) + + synchronize.build_actions() + if options.verbose: + for action in synchronize.actions: + print action + if not synchronize.actions: + print 'Nothing to do.' + if not options.fake: + synchronize.apply_actions() + failed_actions = [action for action in synchronize.actions if action.errors] + if failed_actions: + print >>sys.stderr, 'Some actions failed:' + for action in failed_actions: + print >>sys.stderr, action, action.erors + raise SystemExit(1) diff --git a/src/ldaptools/ldif_utils.py b/src/ldaptools/ldif_utils.py new file mode 100644 index 0000000..d991b05 --- /dev/null +++ b/src/ldaptools/ldif_utils.py @@ -0,0 +1,21 @@ +import ldap +import ldif + +from ldaptools.utils import idict + + +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)) + + def __iter__(self): + for dn, attributes in self.entries: + yield dn, idict(attributes) diff --git a/src/ldaptools/paged.py b/src/ldaptools/paged.py new file mode 100644 index 0000000..7a461f5 --- /dev/null +++ b/src/ldaptools/paged.py @@ -0,0 +1,73 @@ +import ldap +from ldap.ldapobject import ReconnectLDAPObject +from ldap.controls import SimplePagedResultsControl + + +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 diff --git a/src/ldaptools/schemas/core.ldif b/src/ldaptools/schemas/core.ldif new file mode 100644 index 0000000..822cf66 --- /dev/null +++ b/src/ldaptools/schemas/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/ldaptools/schemas/cosine.ldif b/src/ldaptools/schemas/cosine.ldif new file mode 100644 index 0000000..9b437f8 --- /dev/null +++ b/src/ldaptools/schemas/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/ldaptools/schemas/eduorg-200210-openldap.ldif b/src/ldaptools/schemas/eduorg-200210-openldap.ldif new file mode 100644 index 0000000..461f228 --- /dev/null +++ b/src/ldaptools/schemas/eduorg-200210-openldap.ldif @@ -0,0 +1,21 @@ +dn: cn=eduorg-200210,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: eduorg-200210 +olcAttributeTypes: {0}( 1.3.6.1.4.1.5923.1.2.1.2 NAME 'eduOrgHomePageURI' DESC + 'eduOrg per Internet2 and EDUCAUSE' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4 + .1.1466.115.121.1.15 ) +olcAttributeTypes: {1}( 1.3.6.1.4.1.5923.1.2.1.3 NAME 'eduOrgIdentityAuthNPoli + cyURI' DESC 'eduOrg per Internet2 and EDUCAUSE' EQUALITY caseExactMatch SYNTA + X 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: {2}( 1.3.6.1.4.1.5923.1.2.1.4 NAME 'eduOrgLegalName' DESC ' + eduOrg per Internet2 and EDUCAUSE' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4. + 1.1466.115.121.1.15 ) +olcAttributeTypes: {3}( 1.3.6.1.4.1.5923.1.2.1.5 NAME 'eduOrgSuperiorURI' DESC + 'eduOrg per Internet2 and EDUCAUSE' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4 + .1.1466.115.121.1.15 ) +olcAttributeTypes: {4}( 1.3.6.1.4.1.5923.1.2.1.6 NAME 'eduOrgWhitePagesURI' DE + SC 'eduOrg per Internet2 and EDUCAUSE' EQUALITY caseExactMatch SYNTAX 1.3.6.1 + .4.1.1466.115.121.1.15 ) +olcObjectClasses: {0}( 1.3.6.1.4.1.5923.1.2.2 NAME 'eduOrg' AUXILIARY MAY ( cn + $ eduOrgHomePageURI $ eduOrgIdentityAuthNPolicyURI $ eduOrgLegalName $ eduOr + gSuperiorURI $ eduOrgWhitePagesURI ) ) diff --git a/src/ldaptools/schemas/eduperson.ldif b/src/ldaptools/schemas/eduperson.ldif new file mode 100644 index 0000000..7a9310b --- /dev/null +++ b/src/ldaptools/schemas/eduperson.ldif @@ -0,0 +1,47 @@ +# This LDIF file can be used to add to add the eduPerson schema to +# an OpenLDAP server > 2.4 that uses the new configuration format. +# Put this file to /etc/ldap/schema/ and then execute the following +# comand to add this schema: +# ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/eduperson.ldif +dn: cn=eduperson,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: eduperson +olcAttributeTypes: {0}( 1.3.6.1.4.1.5923.1.1.1.1 NAME 'eduPersonAffiliation' D + ESC 'eduPerson per Internet2 and EDUCAUSE' EQUALITY caseIgnoreMatch SUBSTR ca + seIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: {1}( 1.3.6.1.4.1.5923.1.1.1.2 NAME 'eduPersonNickname' DESC + 'eduPerson per Internet2 and EDUCAUSE' EQUALITY caseIgnoreMatch SUBSTR caseI + gnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: {2}( 1.3.6.1.4.1.5923.1.1.1.3 NAME 'eduPersonOrgDN' DESC 'e + duPerson per Internet2 and EDUCAUSE' EQUALITY distinguishedNameMatch SYNTAX 1 + .3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE ) +olcAttributeTypes: {3}( 1.3.6.1.4.1.5923.1.1.1.4 NAME 'eduPersonOrgUnitDN' DES + C 'eduPerson per Internet2 and EDUCAUSE' EQUALITY distinguishedNameMatch SYNT + AX 1.3.6.1.4.1.1466.115.121.1.12 ) +olcAttributeTypes: {4}( 1.3.6.1.4.1.5923.1.1.1.5 NAME 'eduPersonPrimaryAffilia + tion' DESC 'eduPerson per Internet2 and EDUCAUSE' EQUALITY caseIgnoreMatch SU + BSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VA + LUE ) +olcAttributeTypes: {5}( 1.3.6.1.4.1.5923.1.1.1.6 NAME 'eduPersonPrincipalName' + DESC 'eduPerson per Internet2 and EDUCAUSE' EQUALITY caseIgnoreMatch SUBSTR + caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcAttributeTypes: {6}( 1.3.6.1.4.1.5923.1.1.1.7 NAME 'eduPersonEntitlement' D + ESC 'eduPerson per Internet2 and EDUCAUSE' EQUALITY caseExactMatch SYNTAX 1.3 + .6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: {7}( 1.3.6.1.4.1.5923.1.1.1.8 NAME 'eduPersonPrimaryOrgUnit + DN' DESC 'eduPerson per Internet2 and EDUCAUSE' EQUALITY distinguishedNameMat + ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE ) +olcAttributeTypes: {8}( 1.3.6.1.4.1.5923.1.1.1.9 NAME 'eduPersonScopedAffiliat + ion' DESC 'eduPerson per Internet2 and EDUCAUSE' EQUALITY caseIgnoreMatch SYN + TAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: {9}( 1.3.6.1.4.1.5923.1.1.1.10 NAME 'eduPersonTargetedID' D + ESC 'eduPerson per Internet2 and EDUCAUSE' EQUALITY caseIgnoreMatch SYNTAX 1. + 3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: {10}( 1.3.6.1.4.1.5923.1.1.1.11 NAME 'eduPersonAssurance' D + ESC 'eduPerson per Internet2 and EDUCAUSE' EQUALITY caseIgnoreMatch SYNTAX 1. + 3.6.1.4.1.1466.115.121.1.15 ) +olcObjectClasses: {0}( 1.3.6.1.4.1.5923.1.1.2 NAME 'eduPerson' DESC 'eduPerson + per Internet2 and EDUCAUSE' AUXILIARY MAY ( eduPersonAffiliation $ eduPerson + Nickname $ eduPersonOrgDN $ eduPersonOrgUnitDN $ eduPersonPrimaryAffiliation + $ eduPersonPrincipalName $ eduPersonEntitlement $ eduPersonPrimaryOrgUnitDN $ + eduPersonScopedAffiliation $ eduPersonTargetedID $ eduPersonAssurance ) ) diff --git a/src/ldaptools/schemas/inetorgperson.ldif b/src/ldaptools/schemas/inetorgperson.ldif new file mode 100644 index 0000000..31a0080 --- /dev/null +++ b/src/ldaptools/schemas/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/ldaptools/schemas/nis.ldif b/src/ldaptools/schemas/nis.ldif new file mode 100644 index 0000000..32a63be --- /dev/null +++ b/src/ldaptools/schemas/nis.ldif @@ -0,0 +1,120 @@ +# NIS (RFC2307) +# $OpenLDAP$ +## This work is part of OpenLDAP Software . +## +## Copyright 1998-2014 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 +## . +# +# Definitions from RFC2307 (Experimental) +# An Approach for Using LDAP as a Network Information Service +# +# Depends upon core.ldif and cosine.ldif +# +# This file was automatically generated from nis.schema; see that file +# for complete references. +# +dn: cn=nis,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: nis +olcAttributeTypes: ( 1.3.6.1.1.1.1.2 NAME 'gecos' DESC 'The GECOS field; th + e common name' EQUALITY caseIgnoreIA5Match SUBSTR caseIgnoreIA5SubstringsMatc + h SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.3 NAME 'homeDirectory' DESC 'The absolut + e path to the home directory' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1 + 466.115.121.1.26 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.4 NAME 'loginShell' DESC 'The path to th + e login shell' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.2 + 6 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.5 NAME 'shadowLastChange' EQUALITY integ + erMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.6 NAME 'shadowMin' EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.7 NAME 'shadowMax' EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.8 NAME 'shadowWarning' EQUALITY integerM + atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.9 NAME 'shadowInactive' EQUALITY integer + Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.10 NAME 'shadowExpire' EQUALITY integerM + atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.11 NAME 'shadowFlag' EQUALITY integerMat + ch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.12 NAME 'memberUid' EQUALITY caseExactI + A5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1. + 26 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.13 NAME 'memberNisNetgroup' EQUALITY ca + seExactIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.11 + 5.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.14 NAME 'nisNetgroupTriple' DESC 'Netgr + oup triple' SYNTAX 1.3.6.1.1.1.0.0 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.15 NAME 'ipServicePort' EQUALITY intege + rMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.16 NAME 'ipServiceProtocol' SUP name ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.17 NAME 'ipProtocolNumber' EQUALITY int + egerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.18 NAME 'oncRpcNumber' EQUALITY integer + Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.19 NAME 'ipHostNumber' DESC 'IP address + ' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.20 NAME 'ipNetworkNumber' DESC 'IP netw + ork' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} SI + NGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.21 NAME 'ipNetmaskNumber' DESC 'IP netm + ask' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} SI + NGLE-VALUE ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.22 NAME 'macAddress' DESC 'MAC address' + EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.23 NAME 'bootParameter' DESC 'rpc.bootp + aramd parameter' SYNTAX 1.3.6.1.1.1.0.1 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.24 NAME 'bootFile' DESC 'Boot image nam + e' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.26 NAME 'nisMapName' SUP name ) +olcAttributeTypes: ( 1.3.6.1.1.1.1.27 NAME 'nisMapEntry' EQUALITY caseExac + tIA5Match SUBSTR caseExactIA5SubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121. + 1.26{1024} SINGLE-VALUE ) +olcObjectClasses: ( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction o + f an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ uidNu + mber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $ + description ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.1 NAME 'shadowAccount' DESC 'Additional a + ttributes for shadow passwords' SUP top AUXILIARY MUST uid MAY ( userPassword + $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive + $ shadowExpire $ shadowFlag $ description ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of + a group of accounts' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( userPas + sword $ memberUid $ description ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.3 NAME 'ipService' DESC 'Abstraction an I + nternet Protocol service' SUP top STRUCTURAL MUST ( cn $ ipServicePort $ ipSe + rviceProtocol ) MAY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.4 NAME 'ipProtocol' DESC 'Abstraction of + an IP protocol' SUP top STRUCTURAL MUST ( cn $ ipProtocolNumber $ description + ) MAY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.5 NAME 'oncRpc' DESC 'Abstraction of an O + NC/RPC binding' SUP top STRUCTURAL MUST ( cn $ oncRpcNumber $ description ) M + AY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.6 NAME 'ipHost' DESC 'Abstraction of a ho + st, an IP device' SUP top AUXILIARY MUST ( cn $ ipHostNumber ) MAY ( l $ desc + ription $ manager ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.7 NAME 'ipNetwork' DESC 'Abstraction of a + n IP network' SUP top STRUCTURAL MUST ( cn $ ipNetworkNumber ) MAY ( ipNetmas + kNumber $ l $ description $ manager ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.8 NAME 'nisNetgroup' DESC 'Abstraction of + a netgroup' SUP top STRUCTURAL MUST cn MAY ( nisNetgroupTriple $ memberNisNe + tgroup $ description ) ) +olcObjectClasses: ( 1.3.6.1.1.1.2.9 NAME 'nisMap' DESC 'A generic abstracti + on of a NIS map' SUP top STRUCTURAL MUST nisMapName MAY description ) +olcObjectClasses: ( 1.3.6.1.1.1.2.10 NAME 'nisObject' DESC 'An entry in a + NIS map' SUP top STRUCTURAL MUST ( cn $ nisMapEntry $ nisMapName ) MAY descri + ption ) +olcObjectClasses: ( 1.3.6.1.1.1.2.11 NAME 'ieee802Device' DESC 'A device w + ith a MAC address' SUP top AUXILIARY MAY macAddress ) +olcObjectClasses: ( 1.3.6.1.1.1.2.12 NAME 'bootableDevice' DESC 'A device + with boot parameters' SUP top AUXILIARY MAY ( bootFile $ bootParameter ) ) diff --git a/src/ldaptools/schemas/supann-2009.ldif b/src/ldaptools/schemas/supann-2009.ldif new file mode 100644 index 0000000..d9683ac --- /dev/null +++ b/src/ldaptools/schemas/supann-2009.ldif @@ -0,0 +1,486 @@ +# +# 60supann.ldif - version 389 Directory Server du schéma SupAnn version 2009.6 +# +# (cf original à http://www.cru.fr/_media/documentation/supann/supann_2009.schema.txt) +# +# Modifications : +# 21 mai 10 (JML): supannRefId : suppression du SINGLE-VALUE mis par erreur +# 23 fevr. 10 (JML): import du fichier OpenLDAP supann_2009.schema.txt pour 389 Directory Server +# 03 nov. 14 (BDA): ajout mailForwardingAddress +# +################################################################################ +# +dn: cn=supann,cn=schema,cn=config +objectClass: olcSchemaConfig +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.1 + NAME 'supannListeRouge' + DESC 'indique que l entree correspondante n est pas publique' + EQUALITY booleanMatch + SINGLE-VALUE + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.2 + NAME 'supannActivite' + DESC 'activite ou metier de la personne' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.3 + NAME 'supannOrganisme' + DESC 'code organisme d appartenance' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SINGLE-VALUE + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.4 + NAME 'supannCivilite' + DESC 'civilite : M., Mme, Mlle' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SINGLE-VALUE + SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{32} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.5 + NAME 'supannAffectation' + DESC 'affectation' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.6 + NAME 'supannCodeEntite' + DESC 'identifiant d entite' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SINGLE-VALUE + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.7 + NAME 'supannCodeEntiteParent' + DESC 'identifiant d entite parente' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.8 + NAME 'supannEntiteAffectation' + DESC 'identifiant d entite d affectation' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.9 + NAME 'supannCodeINE' + DESC 'code INE' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.10 + NAME 'supannEtuId' + DESC 'identifiant scolarite' + EQUALITY caseExactMatch + SUBSTR caseExactSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.11 + NAME 'supannEmpId' + DESC 'identifiant personnel' + EQUALITY caseExactMatch + SUBSTR caseExactSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.12 + NAME 'supannAutreTelephone' + SUP telephoneNumber + DESC 'numeros de telephone secondaires' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.50 + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.13 + NAME 'supannEntiteAffectationPrincipale' + DESC 'identifiant d entite principale d affectation' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SINGLE-VALUE + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.14 + NAME 'supannEtablissement' + DESC 'code d etablissement' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.15 + NAME 'supannMailPerso' + DESC 'Mailbox RFC822 privee' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.16 + NAME 'supannTypeEntite' + DESC 'type de structure ou entite' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.17 + NAME 'supannParrainDN' + SUP distinguishedName + DESC 'dn du responsable de cette entree' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.18 + NAME 'supannGroupeDateFin' + DESC 'indique la date de fin de validite de l entree correspondante' + EQUALITY generalizedTimeMatch + ORDERING generalizedTimeOrderingMatch + SINGLE-VALUE + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.19 + NAME 'supannGroupeAdminDN' + SUP distinguishedName + DESC 'dn des administrateurs du groupe concerne' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.20 + NAME 'supannAliasLogin' + DESC 'login personalise' + EQUALITY caseExactMatch + SUBSTR caseExactSubstringsMatch + SINGLE-VALUE + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.21 + NAME 'supannRole' + DESC 'role' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.22 + NAME 'supannGroupeLecteurDN' + SUP distinguishedName + DESC 'dn des entites habilite a lire le contenu d un groupe' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.23 + NAME 'supannRoleGenerique' + DESC 'role generique d une personne' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.24 + NAME 'supannRoleEntite' + DESC 'role contextuel' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{512} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.25 + NAME 'supannEtuAnneeInscription' + DESC 'annee inscription' + EQUALITY numericStringMatch + ORDERING numericStringOrderingMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{4} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.26 + NAME 'supannEtuCursusAnnee' + DESC 'cursus et annee dans le diplome' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.27 + NAME 'supannEtuDiplome' + DESC 'diplome' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.28 + NAME 'supannEtuElementPedagogique' + DESC 'element pedagogique' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.29 + NAME 'supannEtuEtape' + DESC 'etape' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( 1.3.6.1.4.1.7135.1.2.1.30 + NAME 'supannEtuInscription' + DESC 'description d inscriptions' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{4096} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.31 + NAME 'supannEtuRegimeInscription' + DESC 'regime d inscription' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.32 + NAME 'supannEtuSecteurDisciplinaire' + DESC 'secteur disciplinaire' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.33 + NAME 'supannEtuTypeDiplome' + DESC 'type de diplome' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.34 + NAME 'supannAutreMail' + SUP mail + DESC 'adresses mail non institutionnelles' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.35 + NAME 'supannEmpCorps' + DESC 'corps d appartenance d un agent' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.36 + NAME 'supannTypeEntiteAffectation' + DESC 'type de structure ou entite d affectation' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# +################################################################################ +# +olcAttributeTypes: ( + 1.3.6.1.4.1.7135.1.2.1.37 + NAME 'supannRefId' + DESC 'identifiant partage avec autre brique du SI' + EQUALITY caseExactMatch + SUBSTR caseExactSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} + ) +# repris du schema Netscape (enlever si conflit) : +olcAttributeTypes: ( 2.16.840.1.113730.3.1.17 + NAME 'mailForwardingAddress' + DESC 'coin' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 + ) +# +################################################################################ +# +olcObjectClasses: ( + 1.3.6.1.4.1.7135.1.2.2.1 + NAME 'supannPerson' + SUP top AUXILIARY + DESC 'classe d infos complementaires sur personnes supann' + MAY ( supannOrganisme $ supannCivilite $ supannAutreTelephone $ supannAffectation $ supannEmpId $ supannCodeINE $ supannEtuId $ supannAliasLogin $ supannParrainDN $ supannActivite $ supannEntiteAffectation $ supannEntiteAffectationPrincipale $ supannMailPerso $ supannRole $ supannRoleEntite $ supannRoleGenerique $ supannEtuAnneeInscription $ supannEtuCursusAnnee $ supannEtuDiplome $ supannEtuElementPedagogique $ supannEtuEtape $ supannEtuInscription $ supannEtuRegimeInscription $ supannEtuSecteurDisciplinaire $ supannEtuTypeDiplome $ supannEtablissement $ supannListeRouge $ supannAutreMail $ mailForwardingAddress $ supannEmpCorps $ supannTypeEntiteAffectation $ supannRefId ) + ) +# +################################################################################ +# +olcObjectClasses: ( + 1.3.6.1.4.1.7135.1.2.2.2 + NAME 'supannOrg' + SUP top AUXILIARY + DESC 'classe d infos complementaires pour etablissement' + MAY ( supannEtablissement ) + ) +# +################################################################################ +# +olcObjectClasses: ( + 1.3.6.1.4.1.7135.1.2.2.3 + NAME 'supannEntite' + SUP top AUXILIARY + DESC 'classe d infos complementaires pour entite' + MUST ( supannCodeEntite ) + MAY ( supannTypeEntite $ supannCodeEntiteParent $ supannRefId ) + ) +# +################################################################################ +# +#objectClasses: ( +# 1.3.6.1.4.1.7135.1.2.2.4 +# NAME 'entree disponible' +# ) +# +################################################################################ +# +#objectClasses: ( +# 1.3.6.1.4.1.7135.1.2.2.5 +# NAME 'entree disponible' +# ) +# +################################################################################ +# +olcObjectClasses: ( + 1.3.6.1.4.1.7135.1.2.2.6 + NAME 'supannGroupe' + SUP top AUXILIARY + DESC 'attributs specifiques des groupes' + MAY ( supannGroupeDateFin $ supannGroupeAdminDN $ supannGroupeLecteurDN $ supannRefId ) + ) +# +################################################################################ +# diff --git a/src/ldaptools/slapd.py b/src/ldaptools/slapd.py new file mode 100644 index 0000000..2f8c250 --- /dev/null +++ b/src/ldaptools/slapd.py @@ -0,0 +1,237 @@ +import time +import tempfile +import shutil +import subprocess +import os +import ldap +import ldap.modlist +import ldap.sasl +import StringIO +import atexit + +from ldaptools.ldif_utils import ListLDIFParser +from ldaptools.paged import PagedLDAPObject + +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 Slapd(object): + '''Initiliaze an OpenLDAP server with just one database containing branch + o=orga and loading the core schema. ACL are very permissive. + ''' + root_bind_dn = 'uid=admin,cn=config' + root_bind_password = 'admin' + + config_ldif = '''dn: cn=config +objectClass: olcGlobal +cn: config +olcToolThreads: 1 +olcLogLevel: stats +olcLogFile: {slapd_dir}/log + +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 +''' + process = None + schemas = ['core', 'cosine', 'inetorgperson', 'nis', 'eduorg-200210-openldap', 'eduperson', + 'supann-2009'] + schemas_ldif = [open(os.path.join(os.path.dirname(__file__), + 'schemas', '%s.ldif' % schema)).read() for schema in schemas] + checkpoints = None + data_dirs = None + + def create_process(self, args): + return subprocess.Popen(args, stdin=subprocess.PIPE, env=os.environ, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + def __init__(self, ldap_url=None): + assert has_slapd() + self.checkpoints = [] + self.data_dirs = [] + 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.socket = os.path.join(self.slapd_dir, 'socket') + if not ldap_url: + ldap_url = 'ldapi://%s' % self.socket.replace('/', '%2F') + self.ldap_url = ldap_url + self.slapadd(self.config_ldif, context={'slapd_dir': self.slapd_dir, 'gid': os.getgid(), + 'uid': os.getuid()}) + for schema_ldif in self.schemas_ldif: + self.slapadd(schema_ldif) + self.start() + self.add_db('o=orga') + ldif = '''dn: o=orga +objectClass: organization +o: orga''' + self.add_ldif(ldif) + + def add_db(self, suffix): + path = os.path.join(self.slapd_dir, suffix) + os.mkdir(path) + ldif = '''dn: olcDatabase=mdb,cn=config +objectClass: olcDatabaseConfig +objectClass: olcMdbConfig +olcDatabase: mdb +olcSuffix: {suffix} +olcDbDirectory: {path} +olcReadOnly: FALSE +# Index +olcAccess: {{0}}to * by manage''' + self.add_ldif(ldif, context={'suffix': suffix, 'path': path}) + self.data_dirs.append(path) + + def slapadd(self, ldif, context=None): + assert not self.process + + if context: + ldif = ldif.format(**context) + 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''' + assert not self.process + + cmd = [SLAPD_PATH, + '-d768', # put slapd in foreground + '-F' + self.config_dir, + '-h', self.ldap_url] + self.process = self.create_process(cmd) + atexit.register(self.clean) + + while True: + try: + conn = self.get_connection() + conn.whoami_s() + except ldap.SERVER_DOWN: + time.sleep(0.1) + else: + break + + def stop(self): + '''Send SIGTERM to slapd''' + assert self.process + + process = self.process + self.process = None + process.kill() + + def checkpoint(self): + '''Stop slapd and save current data state''' + assert not self.process + + self.checkpoints.append( + os.path.join(self.slapd_dir, 'checkpoint-%d' % len(self.checkpoints))) + for data_dir in self.data_dirs: + dirname = os.path.basename(data_dir) + target = os.path.join(self.checkpoints[-1], dirname) + shutil.copytree(data_dir, target) + + def restore(self): + '''Stop slapd and restore last data state''' + assert not self.process + assert self.checkpoints, 'no checkpoint exists' + for data_dir in self.data_dirs: + dirname = os.path.basename(data_dir) + shutil.rmtree(data_dir) + shutil.copytree(os.path.join(self.checkpoints[-1], dirname), data_dir) + shutil.rmtree(self.checkpoints[-1]) + self.checkpoints.pop() + + # Clean behind us + def __del__(self): + self.clean() + + def clean(self): + '''Remove directory''' + if self.slapd_dir: + if os.path.exists(self.slapd_dir): + shutil.rmtree(self.slapd_dir, ignore_errors=True) + self.slapd_dir = None + if self.process: + self.stop() + + def __enter__(self): + return self + + def __exit__(self, type, value, tb): + self.clean() + + def add_ldif(self, ldif, context=None): + assert self.process + + if context: + ldif = ldif.format(**context) + parser = ListLDIFParser(StringIO.StringIO(ldif)) + parser.parse() + conn = self.get_connection_admin() + parser.add(conn) + + def get_connection(self): + assert self.process + + return PagedLDAPObject(self.ldap_url) + + def get_connection_admin(self): + conn = self.get_connection() + conn.simple_bind_s(self.root_bind_dn, self.root_bind_password) + return conn + + def get_connection_external(self): + assert self.ldap_url.startswith('ldapi://') + + conn = self.get_connection() + conn.sasl_interactive_bind_s("", ldap.sasl.external()) + return conn diff --git a/src/ldaptools/synchronize.py b/src/ldaptools/synchronize.py new file mode 100644 index 0000000..d6f8fbc --- /dev/null +++ b/src/ldaptools/synchronize.py @@ -0,0 +1,260 @@ +import logging +import functools +from itertools import groupby + +import ldap +from ldap.filter import filter_format +import ldap.modlist +import ldap.dn + + +from .utils import batch_generator, to_dict_of_set, idict, str2dn, istr + + +@functools.total_ordering +class Action(object): + dn = None + new_dn = None + entry = None + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + self.msgids = [] + self.errors = [] + self.results = [] + + def __eq__(self, other): + return (other.__class__ is self.__class__ and self.dn == other.dn and other.new_dn == + other.new_dn and to_dict_of_set(self.entry) == to_dict_of_set(other.entry)) + + # - first rename, sorted by dn depth + # - then update and creations, sorted by depth + # - the deletions sorted by reverse of depth + def __lt__(self, other): + if self.order == other.order: + n = len(str2dn(self.dn)) + m = len(str2dn(other.dn)) + if self.order == 4: # Delete should be done from leaves to root + return m < n + else: + return n < m # Other operations must be donne from root to leaves + else: + return self.order < other.order + + def do(self, conn): + pass + + def collect_result(self, conn): + for msgid in self.msgids: + try: + self.results.append(conn.result2(msgid)) + except ldap.LDAPError, e: + self.errors.append(e) + + def __str__(self): + s = '<%s dn: %s' % (self.__class__.__name__, self.dn) + if self.entry: + s += ' entry: %s' % self.entry + if self.new_dn: + s += ' new_dn: %s' % self.new_dn + return s + + +class Create(Action): + order = 3 + + def do(self, conn): + self.msgids.append(conn.add(self.dn, ldap.modlist.addModlist(self.entry))) + + +class Rename(Action): + order = 1 + + def do(self, conn): + old_dn = str2dn(self.dn) + new_dn = str2dn(self.new_dn) + new_rdn = ldap.dn.dn2str(new_dn[:1]) + newsuperior = None + if old_dn[1:] != new_dn[1:]: + newsuperior = ldap.dn.dn2str(new_dn[1:]) + self.msgids.append(conn.rename(self.dn, new_rdn, newsuperior=newsuperior, delold=0)) + + +class Update(Action): + order = 2 + + def do(self, conn): + modlist = [] + for key, values in self.entry.iteritems(): + modlist.append((ldap.MOD_REPLACE, key, values)) + self.msgids.append(conn.modify(self.dn, modlist)) + + +class Delete(Action): + order = 4 + + def do(self, conn): + self.msgids.append(conn.delete(self.dn)) + + +class Synchronize(object): + '''Synchronize a source or records with an LDAP server''' + BATCH_SIZE = 100 + + # an iterable yield pair of (dn, attributes) + source = None + # an LDAP connection as provided by python-ldap + target_conn = None + + # list or pairs, objectclass -> attribute + pivot_attributes = [] + + # attributes + attributes = [] + + # actions + CREATE = 1 + UPDATE = 2 + RENAME = 3 + DELETE = 4 + + all_filter = '(objectclass=*)' + + # actions + actions = None + + def __init__(self, source, source_dn, target_conn, target_dn, attributes=None, all_filter=None, + pivot_attributes=None, logger=None): + self.source = source + self.source_dn = source_dn + self.target_conn = target_conn + self.target_dn = target_dn + self.attributes = attributes or self.attributes + self.all_filter = all_filter or self.all_filter + self.pivot_attributes = pivot_attributes or self.pivot_attributes + self.logger = logger or logging.getLogger(__name__) + self.errors = [] + + def massage_dn(self, old_dn): + return old_dn[:-len(self.source_dn)] + self.target_dn + + def get_pivot_attribute(self, entry): + '''Find a pivot attribute value for an LDAP entry''' + for objc, attr in self.pivot_attributes: + entry['objectclass'] = map(istr, entry['objectclass']) + if objc in entry['objectclass']: + try: + value = entry[attr] + except KeyError: + raise Exception('entry missing pivot attribute %s: %s' % (attr, entry)) + break + else: + raise Exception('entry has unknown objectclasses %s' % entry['objectclass']) + if len(value) != 1: + raise Exception('entry pivot attribute %s must have only one value' % attr) + return objc, attr, value[0] + + def get_target_entries(self, filterstr=None, attributes=[]): + '''Return all target entries''' + l = self.target_conn.paged_search_ext_s(self.target_dn, ldap.SCOPE_SUBTREE, + filterstr=filterstr or self.all_filter, + attrlist=attributes) + return ((dn, idict(entry)) for dn, entry in l if dn) + + def build_actions_for_entries(self, entries): + seen_dn = self.seen_dn + renamed_dn = self.renamed_dn + in_dns = [] + out_filters = [] + # Transform input entries into filters + for dn, entry in entries: + objectclass, attr, value = self.get_pivot_attribute(entry) + in_dns.append(((attr, value), (dn, entry))) + filter_tpl = '(&(objectclass=%%s)(%s=%%s))' % attr + out_filters.append( + filter_format(filter_tpl, (objectclass, value))) + out_filter = '(|%s)' % ''.join(out_filters) + # Get existing output entries + out_dns = {} + for dn, entry in self.get_target_entries(filterstr=out_filter, attributes=self.attributes): + objectclass, attr, value = self.get_pivot_attribute(entry) + out_dns[(attr, value)] = dn, entry + for pivot, (source_dn, entry) in in_dns: + target_dn = self.massage_dn(source_dn) + seen_dn.add(target_dn) + if pivot in out_dns: + out_dn, out_entry = out_dns[pivot] + # translate dn to dn after all previous renames + out_ava = str2dn(out_dn) + rename = True + while rename: + rename = False + for i in range(len(out_ava) - 1, 0, -1): + print 'looking for rewrite of', out_ava[i:] + if out_ava[i:] in renamed_dn: + # rewrite suffix if it has been renamed + out_ava = out_ava[:i] + renamed_dn[out_ava[i:]] + print 'found, renaming to', out_ava + rename = True + break + new_out_dn = ldap.dn.dn2str(out_ava) + if new_out_dn != target_dn: + seen_dn.add(out_dn) + self.rename(new_out_dn, target_dn) + print 'rename', new_out_dn, target_dn + renamed_dn[str2dn(new_out_dn)] = str2dn(target_dn) + if to_dict_of_set(out_entry) != to_dict_of_set(entry): + new_entry = {} + for attribute in self.attributes: + new_entry[attribute] = entry.get(attribute, []) + self.update(target_dn, new_entry) + else: + self.create(target_dn, entry) + + def build_actions(self): + self.seen_dn = set() + self.renamed_dn = {} + self.actions = [] + # Order source entries by DN depth + entries = list(self.source) + entries.sort(key=lambda (dn, entry): len(str2dn(dn))) + # First create, rename and update + for batch in batch_generator(entries, self.BATCH_SIZE): + self.build_actions_for_entries(batch) + # Then delete + for dn, entry in self.get_target_entries(): + if dn not in self.seen_dn: + self.delete(dn) + # Now sort actions by their special order + self.actions.sort() + + def create(self, dn, entry): + self.actions.append(Create(dn=dn, entry=entry)) + + def rename(self, old_dn, new_dn): + self.actions.append(Rename(dn=old_dn, new_dn=new_dn)) + + def update(self, dn, entry): + self.actions.append(Update(dn=dn, entry=entry)) + + def delete(self, dn): + self.actions.append(Delete(dn=dn)) + + def apply_actions(self): + '''Apply actions, wait for result of different kind of actions + separately, since openldap seem to reorder some of them''' + + def action_key(action): + return (action.__class__, str2dn(action.dn)) + for key, sequence in groupby(self.actions, action_key): + for batch in batch_generator(sequence, self.BATCH_SIZE): + for action in batch: + action.do(self.target_conn) + self.logger.debug('applying %s', action) + self.logger.debug('waiting for completion of %d actions', len(batch)) + for action in batch: + action.collect_result(self.target_conn) + + def run(self): + self.build_actions() + self.apply_actions() diff --git a/src/ldaptools/utils.py b/src/ldaptools/utils.py new file mode 100644 index 0000000..a3f9ce4 --- /dev/null +++ b/src/ldaptools/utils.py @@ -0,0 +1,223 @@ +import ldap.dn + + +# Copied from http://code.activestate.com/recipes/194371-case-insensitive-strings/ +class istr(str): + """Case insensitive strings class. + Performs like str except comparisons are case insensitive.""" + + def __init__(self, strMe): + str.__init__(self, strMe) + self.__lowerCaseMe = strMe.lower() + + def __repr__(self): + return "iStr(%s)" % str.__repr__(self) + + def __eq__(self, other): + if not hasattr(other, 'lower'): + return False + return self.__lowerCaseMe == other.lower() + + def __lt__(self, other): + return self.__lowerCaseMe < other.lower() + + def __le__(self, other): + return self.__lowerCaseMe <= other.lower() + + def __gt__(self, other): + return self.__lowerCaseMe > other.lower() + + def __ne__(self, other): + if not hasattr(other, 'lower'): + return True + return self.__lowerCaseMe != other.lower() + + def __ge__(self, other): + return self.__lowerCaseMe >= other.lower() + + def __cmp__(self, other): + return cmp(self.__lowerCaseMe, other.lower()) + + def __hash__(self): + return hash(self.__lowerCaseMe) + + def __contains__(self, other): + return other.lower() in self.__lowerCaseMe + + def count(self, other, *args): + return str.count(self.__lowerCaseMe, other.lower(), *args) + + def endswith(self, other, *args): + return str.endswith(self.__lowerCaseMe, other.lower(), *args) + + def find(self, other, *args): + return str.find(self.__lowerCaseMe, other.lower(), *args) + + def index(self, other, *args): + return str.index(self.__lowerCaseMe, other.lower(), *args) + + def lower(self): # Courtesy Duncan Booth + return self.__lowerCaseMe + + def rfind(self, other, *args): + return str.rfind(self.__lowerCaseMe, other.lower(), *args) + + def rindex(self, other, *args): + return str.rindex(self.__lowerCaseMe, other.lower(), *args) + + def startswith(self, other, *args): + return str.startswith(self.__lowerCaseMe, other.lower(), *args) + + +class idict(dict): + """A case insensitive dictionary that only permits strings as keys.""" + def __init__(self, indict={}): + dict.__init__(self) + self._keydict = {} # not self.__keydict because I want it to be easily accessible by subclasses + for entry in indict: + self[entry] = indict[entry] # not dict.__setitem__(self, entry, indict[entry]) becasue this causes errors (phantom entries) where indict has overlapping keys + + def findkey(self, item): + """A caseless way of checking if a key exists or not. + It returns None or the correct key.""" + if not isinstance(item, str): + raise TypeError('Keywords for this object must be strings. You supplied %s' % type(item)) + key = item.lower() + try: + return self._keydict[key] + except: + return None + + def changekey(self, item): + """For changing the casing of a key. + If a key exists that is a caseless match for 'item' it will be changed to 'item'. + This is useful when initially setting up default keys - but later might want to preserve an alternative casing. + (e.g. if later read from a config file - and you might want to write back out with the user's casing preserved). + """ + key = self.findkey(item) # does the key exist + if key is None: + raise KeyError(item) + temp = self[key] + del self[key] + self[item] = temp + self._keydict[item.lower()] = item + + def lowerkeys(self): + """Returns a lowercase list of all member keywords.""" + return self._keydict.keys() + + def __setitem__(self, item, value): # setting a keyword + """To implement lowercase keys.""" + key = self.findkey(item) # if the key already exists + if key is not None: + dict.__delitem__(self, key) + self._keydict[item.lower()] = item + dict.__setitem__(self, item, value) + + def __getitem__(self, item): + """To implement lowercase keys.""" + key = self.findkey(item) # does the key exist + if key is None: + raise KeyError(item) + return dict.__getitem__(self, key) + + def __delitem__(self, item): # deleting a keyword + key = self.findkey(item) # does the key exist + if key is None: + raise KeyError(item) + dict.__delitem__(self, key) + del self._keydict[item.lower()] + + def pop(self, item, default=None): + """Correctly emulates the pop method.""" + key = self.findkey(item) # does the key exist + if key is None: + if default is None: + raise KeyError(item) + else: + return default + del self._keydict[item.lower()] + return dict.pop(self, key) + + def popitem(self): + """Correctly emulates the popitem method.""" + popped = dict.popitem(self) + del self._keydict[popped[0].lower()] + return popped + + def has_key(self, item): + """A case insensitive test for keys.""" + if not isinstance(item, str): + return False # should never have a non-string key + return item.lower() in self._keydict # does the key exist + + def __contains__(self, item): + """A case insensitive __contains__.""" + if not isinstance(item, str): + return False # should never have a non-string key + return item.lower() in self._keydict # does the key exist + + def setdefault(self, item, default=None): + """A case insensitive setdefault. + If no default is supplied it sets the item to None""" + key = self.findkey(item) # does the key exist + if key is not None: + return self[key] + self.__setitem__(item, default) + self._keydict[item.lower()] = item + return default + + def get(self, item, default=None): + """A case insensitive get.""" + key = self.findkey(item) # does the key exist + if key is not None: + return self[key] + return default + + def update(self, indict): + """A case insensitive update. + If your dictionary has overlapping keys (e.g. 'FISH' and 'fish') then one will overwrite the other. + The one that is kept is arbitrary.""" + for entry in indict: + self[entry] = indict[entry] # this uses the new __setitem__ method + + def copy(self): + """Create a new caselessDict object that is a copy of this one.""" + return idict(self) + + def dict(self): + """Create a dictionary version of this caselessDict.""" + return dict.copy(self) + + def clear(self): + """Clear this caselessDict.""" + self._keydict = {} + dict.clear(self) + + def __repr__(self): + """A caselessDict version of __repr__ """ + return 'caselessDict(' + dict.__repr__(self) + ')' + + +def batch_generator(gen, *batch_size): + batch = [] + for result in gen: + batch.append(result) + if len(batch) == batch_size[0]: + yield batch + batch = [] + if len(batch_size) > 1: + batch_size = batch_size[1:] + if len(batch): + yield batch + + +def to_dict_of_set(d): + r = idict({k: set(v) for k, v in d.iteritems()}) + if 'objectclass' in r: + r['objectclass'] = set(istr(v) for v in r['objectclass']) + return r + + +def str2dn(s): + return tuple(map(tuple, ldap.dn.str2dn(s))) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..0129020 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,75 @@ +import pytest +import tempfile +import os + +from ldaptools.slapd import Slapd + + +@pytest.fixture +def slapd(request): + return Slapd(ldap_url=getattr(request, 'param', None)) + + +@pytest.fixture +def slapd_tcp1(request): + return Slapd(ldap_url='ldap://localhost:3389') + + +@pytest.fixture +def slapd_tcp2(request): + return Slapd(ldap_url='ldap://localhost:4389') + + +@pytest.fixture +def ldif(): + return '''dn: dc=orga2 +o: orga +dc: orga2 +objectClass: organization +objectClass: dcObject + +dn: uid=admin,dc=orga2 +objectClass: inetOrgPerson +cn: John Doe +uid: admin +sn: John +givenName: Doe +mail: john.doe@entrouvert.com''' + + +@pytest.fixture +def attributes(): + return ['o', 'objectClass', 'uid', 'sn', 'givenName', 'mail', 'dc', 'cn'] + + +@pytest.fixture +def pivot_attributes(): + return ( + ('organization', 'o'), + ('inetOrgPerson', 'uid'), + ) + + +@pytest.fixture +def ldif_path(request, ldif): + handle, path = tempfile.mkstemp() + with open(path, 'w') as f: + f.write(ldif) + f.flush() + def finalize(): + os.unlink(path) + request.addfinalizer(finalize) + return path + + +@pytest.fixture +def attributes_path(request, attributes): + handle, path = tempfile.mkstemp() + with open(path, 'w') as f: + for attribute in attributes: + print >>f, ' %s ' % attribute + f.flush() + def finalize(): + os.unlink(path) + request.addfinalizer(finalize) + return path diff --git a/tests/test_ldapsync.py b/tests/test_ldapsync.py new file mode 100644 index 0000000..8ac2944 --- /dev/null +++ b/tests/test_ldapsync.py @@ -0,0 +1,70 @@ +import pytest +import ldap + +from ldaptools.ldapsync.cmd import main + + + +def test_ldapsync_ldif_to_ldapi(slapd, ldif_path, attributes, pivot_attributes): + args = [ + '--source-uri', ldif_path, + '--source-base-dn', 'dc=orga2', + '--target-uri', slapd.ldap_url, + '--target-base-dn', 'o=orga', + '--attributes', ' '.join(attributes), + '--verbose', + ] + for object_class, pivot_attribute in pivot_attributes: + args += ['--object-class-pivot', '%s %s' % (object_class, pivot_attribute)] + main(args) + main(args) + conn = slapd.get_connection() + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 2 + assert set([dn for dn, entry in conn.search_s('o=orga', ldap.SCOPE_SUBTREE)]) == set(['o=orga', + 'uid=admin,o=orga']) + + +def test_ldapsync_ldif_to_ldapi_attributes_file(slapd, ldif_path, attributes_path, pivot_attributes): + args = [ + '--source-uri', ldif_path, + '--source-base-dn', 'dc=orga2', + '--target-uri', slapd.ldap_url, + '--target-base-dn', 'o=orga', + '--attributes-file', attributes_path, + '--verbose', + ] + for object_class, pivot_attribute in pivot_attributes: + args += ['--object-class-pivot', '%s %s' % (object_class, pivot_attribute)] + main(args) + main(args) + conn = slapd.get_connection() + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 2 + assert set([dn for dn, entry in conn.search_s('o=orga', ldap.SCOPE_SUBTREE)]) == set(['o=orga', + 'uid=admin,o=orga']) + + +def test_ldapsync_ldap_to_ldap(slapd_tcp1, slapd_tcp2, ldif, attributes, pivot_attributes): + slapd_tcp1.add_db('dc=orga2') + slapd_tcp1.add_ldif(ldif) + + args = [ + '--source-uri', slapd_tcp1.ldap_url, + '--source-bind-dn', slapd_tcp1.root_bind_dn, + '--source-bind-password', slapd_tcp1.root_bind_password, + '--source-base-dn', 'dc=orga2', + + '--target-uri', slapd_tcp2.ldap_url, + '--target-bind-dn', slapd_tcp2.root_bind_dn, + '--target-bind-password', slapd_tcp2.root_bind_password, + '--target-base-dn', 'o=orga', + '--attributes', ' '.join(attributes), + '--verbose', + ] + for object_class, pivot_attribute in pivot_attributes: + args += ['--object-class-pivot', '%s %s' % (object_class, pivot_attribute)] + main(args) + main(args) + conn = slapd_tcp2.get_connection() + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 2 + assert set([dn for dn, entry in conn.search_s('o=orga', ldap.SCOPE_SUBTREE)]) == set(['o=orga', + 'uid=admin,o=orga']) diff --git a/tests/test_ldif_utils.py b/tests/test_ldif_utils.py new file mode 100644 index 0000000..d9df2f7 --- /dev/null +++ b/tests/test_ldif_utils.py @@ -0,0 +1,11 @@ +import StringIO + +from ldaptools.ldif_utils import ListLDIFParser + +def test_ldifparser(): + parser = ListLDIFParser(StringIO.StringIO('''dn: o=orga +objectClass: organization''')) + parser.parse() + assert len(list(parser)) == 1 + assert list(parser)[0][0] == 'o=orga' + assert list(parser)[0][1] == {'objectClass': ['organization']} diff --git a/tests/test_slapd.py b/tests/test_slapd.py new file mode 100644 index 0000000..9bff61f --- /dev/null +++ b/tests/test_slapd.py @@ -0,0 +1,27 @@ +import pytest + +import ldap + + +@pytest.mark.parametrize('slapd', [None, 'ldap://localhost:1389'], indirect=True) +def test_checkpoint(slapd): + conn = slapd.get_connection() + conn.simple_bind_s('uid=admin,cn=config', 'admin') + assert conn.whoami_s() == 'dn:uid=admin,cn=config' + slapd.stop() + slapd.checkpoint() + slapd.start() + slapd.add_ldif('''dn: uid=admin,o=orga +objectclass: person +objectclass: uidObject +uid:in +cn: n +sn: n +''') + conn = slapd.get_connection() + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 2 + slapd.stop() + slapd.restore() + slapd.start() + conn = slapd.get_connection() + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 1 diff --git a/tests/test_synchronize.py b/tests/test_synchronize.py new file mode 100644 index 0000000..a75bf7f --- /dev/null +++ b/tests/test_synchronize.py @@ -0,0 +1,211 @@ +import StringIO + +import ldap + +from ldaptools.synchronize import Synchronize, Delete, Rename, Update, Create +from ldaptools.ldif_utils import ListLDIFParser +from ldaptools.ldap_source import LDAPSource + + +def test_synchronize_ldif(slapd): + pivot_attributes = ( + ('organization', 'o'), + ('inetOrgPerson', 'uid'), + ) + attributes = ['o', 'objectClass', 'uid', 'sn', 'givenName', 'mail', 'dc', 'cn'] + conn = slapd.get_connection_admin() + + def syn_ldif(ldif): + parser = ListLDIFParser(StringIO.StringIO(ldif)) + parser.parse() + synchronize = Synchronize(parser, 'o=orga', conn, 'o=orga', + pivot_attributes=pivot_attributes, + attributes=attributes) + synchronize.run() + return synchronize + + ldif = '''dn: o=orga +o: orga +dc: coucou +objectClass: organization +objectClass: dcObject + +dn: uid=admin,o=orga +objectClass: inetOrgPerson +cn: John Doe +uid: admin +sn: John +givenName: Doe +mail: john.doe@entrouvert.com''' + + synchronize = syn_ldif(ldif) + assert all(not action.errors for action in synchronize.actions) + assert len(synchronize.actions) == 2 + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 2 + + # Rename + slapd.add_ldif('''dn: ou=people,o=orga +ou: people +objectClass: organizationalUnit''') + conn.rename_s('uid=admin,o=orga', 'cn=John Doe', newsuperior='ou=people,o=orga', delold=0) + assert set([dn for dn, entry in conn.search_s('o=orga', ldap.SCOPE_SUBTREE)]) == set(['o=orga', + 'ou=people,o=orga', + 'cn=John Doe,ou=people,o=orga']) + synchronize.run() + + assert not any([action.errors for action in synchronize.actions]) + assert len(synchronize.actions) == 2 + assert isinstance(synchronize.actions[0], Rename) + assert isinstance(synchronize.actions[1], Delete) + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 2 + assert set([dn for dn, entry in conn.search_s('o=orga', ldap.SCOPE_SUBTREE)]) == set(['o=orga', + 'uid=admin,o=orga']) + + # Delete one entry + ldif = '''dn: o=orga +o: orga +dc: coucou +objectClass: organization +objectClass: dcobject +''' + synchronize = syn_ldif(ldif) + assert all(not action.errors for action in synchronize.actions) + assert len(synchronize.actions) == 1 + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 1 + + +def test_synchronize_ldap(slapd): + pivot_attributes = ( + ('organization', 'o'), + ('inetOrgPerson', 'uid'), + ) + attributes = ['o', 'objectClass', 'uid', 'sn', 'givenName', 'mail', 'dc', 'cn'] + conn = slapd.get_connection_admin() + + slapd.add_db('dc=orga2') + ldif = '''dn: dc=orga2 +o: orga +dc: orga2 +objectClass: organization +objectClass: dcObject + +dn: uid=admin,dc=orga2 +objectClass: inetOrgPerson +cn: John Doe +uid: admin +sn: John +givenName: Doe +mail: john.doe@entrouvert.com''' + slapd.add_ldif(ldif) + + source = LDAPSource(conn, base_dn='dc=orga2', attributes=attributes) + + + synchronize = Synchronize(source, 'dc=orga2', conn, 'o=orga', + pivot_attributes=pivot_attributes, + attributes=attributes) + synchronize.run() + + assert all(not action.errors for action in synchronize.actions) + assert len(synchronize.actions) == 2 + assert isinstance(synchronize.actions[0], Update) + assert isinstance(synchronize.actions[1], Create) + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 2 + assert set([dn for dn, entry in conn.search_s('o=orga', ldap.SCOPE_SUBTREE)]) == set(['o=orga', + 'uid=admin,o=orga']) + + # Rename + slapd.add_ldif('''dn: ou=people,o=orga +ou: people +objectClass: organizationalUnit''') + conn.rename_s('uid=admin,o=orga', 'cn=John Doe', newsuperior='ou=people,o=orga', delold=0) + assert set([dn for dn, entry in conn.search_s('o=orga', ldap.SCOPE_SUBTREE)]) == set(['o=orga', + 'ou=people,o=orga', + 'cn=John Doe,ou=people,o=orga']) + synchronize.run() + + assert not any([action.errors for action in synchronize.actions]) + assert len(synchronize.actions) == 2 + assert isinstance(synchronize.actions[0], Rename) + assert isinstance(synchronize.actions[1], Delete) + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 2 + assert set([dn for dn, entry in conn.search_s('o=orga', ldap.SCOPE_SUBTREE)]) == set(['o=orga', + 'uid=admin,o=orga']) + + # Delete one entry + conn.delete_s('uid=admin,dc=orga2') + synchronize.run() + + assert all(not action.errors for action in synchronize.actions) + assert len(synchronize.actions) == 1 + assert isinstance(synchronize.actions[0], Delete) + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 1 + +def test_synchronize_deep_rename(slapd): + pivot_attributes = ( + ('organization', 'o'), + ('inetOrgPerson', 'uid'), + ('organizationalUnit', 'ou'), + ) + attributes = ['o', 'objectClass', 'uid', 'sn', 'givenName', 'mail', 'dc', + 'cn', 'description', 'ou'] + conn = slapd.get_connection_admin() + + def syn_ldif(ldif): + parser = ListLDIFParser(StringIO.StringIO(ldif)) + parser.parse() + synchronize = Synchronize(parser, 'o=orga', conn, 'o=orga', + pivot_attributes=pivot_attributes, + attributes=attributes) + synchronize.run() + return synchronize + + ldif = '''dn: o=orga +o: orga +dc: coucou +objectClass: organization +objectClass: dcObject + +dn: ou=people,o=orga +objectClass: organizationalUnit +ou: people +description: coin + +dn: uid=admin,ou=people,o=orga +objectClass: inetOrgPerson +cn: John Doe +uid: admin +sn: John +givenName: Doe +mail: john.doe@entrouvert.com''' + + synchronize = syn_ldif(ldif) + assert all(not action.errors for action in synchronize.actions) + assert len(synchronize.actions) == 3 + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 3 + + # Rename + ldif = '''dn: o=orga +o: orga +dc: coucou +objectClass: organization +objectClass: dcObject + +dn: description=coin,o=orga +objectClass: organizationalUnit +ou: people +description: coin + +dn: cn=John Doe,description=coin,o=orga +objectClass: inetOrgPerson +cn: John Doe +uid: admin +sn: John +givenName: Doe +mail: john.doe@entrouvert.com''' + + synchronize = syn_ldif(ldif) + + assert all(not action.errors for action in synchronize.actions) + assert len(synchronize.actions) == 2 + assert len(conn.search_s('o=orga', ldap.SCOPE_SUBTREE)) == 3 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..c00b49d --- /dev/null +++ b/tox.ini @@ -0,0 +1,22 @@ +# Tox (http://tox.testrun.org/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = coverage, nocoverage + +[testenv:coverage] +usedevelop = true +deps = pytest + pytest-cov + pytest-random +commands = + py.test --cov=ldaptools --cov-report xml {posargs} + +[testenv:nocoverage] +deps = pytest + pytest-cov + pytest-random +commands = + py.test {posargs}