Entwicklung und Integration eigener Module in Univention Directory Manager

From Univention Wiki

Jump to: navigation, search

Einführung

Univention Directory Manager verwendet zur Verwaltung der Daten des Verzeichnisdienstes eine flexible und erweiterbare Struktur aus Python-Modulen. Zusätzlich einzubindende Module werden nach Ablage im Dateisystem automatisch erkannt und zur Verwendung an Kommandozeile und Web-Interface angeboten.

Die Entwicklung eigener Module erlaubt es, den Univention Directory Manager über den Funktionsumfang von erweiterten Attributen hinaus flexibel zu erweitern.

Univention Directory Manager-Module

Übersicht

Univention Directory Manager (kurz UDM) verwendet zur Abbildung von LDAP-Objekten eine eigene Modulstruktur. Im Regelfall entspricht eines dieser UDM-Module einem LDAP-Objekt (z.B. einem Benutzer, einer Gruppe oder einem Container). Eine recht kompakte Erläuterung ist im Univention Forum zu finden.

Die Module sind nach Aufgabenbereichen strukturiert im Verzeichnis /usr/lib/python2.4/site-packages/univention/admin/handlers/ abgelegt. Die Module für die Verwaltung der verschiedenen Rechnerobjekte befinden sich beispielsweise unterhalb des Ordners computers. Dieses Objekt kann von der Kommandozeilenschnittstelle durch computers/windows angesprochen werden.

Eigene Module sollten nach Möglichkeit in einem eigenen Unterverzeichnis abgelegt werden, um Konflikte mit eventuell später in UCS integrierten Standardmodulen zu vermeiden. Damit die Module initialisiert werden können, muss im Verzeichnis eine Datei __init__.py existieren.

Aufbau eines Moduls

Der vorgegebene Name object hat historische Gründe und muß trotz der Namenskollision mit dem Python-Typ object beibehalten werden.


Ein Modul besteht aus der Definition der Modul-Attribute und der Definition einer von univention.admin.simpleLdap abgeleiteten Klasse mit Namen object.

Im folgenden wird mit einer ausführlichen Beschreibung der zu definierenden Variablen begonnen. Im darauf folgenden Abschnitt #Die_object-Klasse wird die Klasse object genauer betrachtet und notwendige Definitionen und Funktionen in der Klasse aufgelistet. Abschließend werden noch zwei optionale Funktionen definiert und erklärt.

Globale Variablen

Im Folgenden werden die globalen Variablen beschrieben, die in einem Univention Directory Manager-Modul besondere Bedeutungen haben. Dabei wird zwischen zwingend notwendigen und optionalen Variablen unterschieden. Die folgende Liste enthält die erforderlichen Variablen, die jedes Modul definieren muss:

module

Ein String, der dem Namen des UDM-Moduls entsprechen muss, z.B. computers/computer

operations

Eine Liste von Strings; enthält alle mit diesem Objekt erlaubten LDAP-Operationen. Mögliche Operationen sind: ['add','edit','remove','search','move']

short_description

Diese Beschreibung wird im Web-Frontend des Univention Directory Manager als Name angezeigt. Im Bereich Navigation wird dieser Text in der Auswahlliste für mögliche Objekttypen angezeigt.

childs

Gibt an, ob es sich bei diesem LDAP-Objekt um einen Container handelt. In diesem Fall wird diese Variable auf den Wert 1 gesetzt und andernfalls auf 0.

long_description

Eine ausführliche Beschreibung des Moduls

options

Die Variable options ist ein Python-Dictionary und definiert verschiedene Optionen, die für das Modul gesetzt werden können bzw. standardmäßig gesetzt sind. Diese Optionen können später bspw. über das Web-Interface des UDM über den Reiter "(Optionen)" aktiviert und deaktiviert werden. Wird eine Option aktiviert, werden ein oder mehrere LDAP-Objektklassen (gegeben durch den Parameter objectClasses) dem Objekt hinzugefügt und im Web-UDM weitere Felder und ggf. Reiter freigeschaltet (bspw. die Groupware-Option für Benutzer). Das Dictionary ordnet jeder Option (als univention.admin.option-Objekte) eine eindeutigen Zeichenkette zu, die später zur Referenzierung verwendet wird (siehe #property-descriptions). Jedes option-Object hat die folgenden Parameter:

  • short_description: Eine kurze Beschreibung der Option, die beispielsweise im Web-Frontend des Univention Directory Manager als beschreibender Text zu den Eingabefeldern genutzt wird.
  • long_description: Eine längere Beschreibung der Option.
  • default Definiert, ob diese Option standardmäßig aktiviert sein soll. True steht dabei für aktiv und False für inaktiv.
  • editable Definiert, ob diese Option mehrmalig gesetzt und entfernt werden kann, oder ob diese Option nach einmaliger Aktivierung immer gesetzt bleibt.
  • objectClasses Eine Menge von LDAP-Objektklassen, die der LDAP-Eintrag haben muß, damit es mit dieser Option assoziiert wird. Damit ist kein Automatismus verbunden, aber diese Information ist bei der späteren Implementierung der Funktion identify() (siehe #Die Funktionen identify und lookup) ggf. hilfreich.

Beispiel:

options = {
 'opt1': univention.admin.option(
   short_description=_('short description'),
   default=True),
 ...}

property-descriptions

Dieses Python-Dictionary enthält alle Attribute, die dieses Modul zur Verfügung stellt. Dabei werden die Attribute (diesmal als univention.admin.property-Objekte), über eine eindeutige Zeichenkette als Schlüssel referenziert. Ein solches Modul-Attribut entspricht in der Regel einem LDAP-Attribut, kann aber auch aus anderen Quellen stammen oder berechnet werden. Ein Beispiel:

property_descriptions = {
	'prop1': univention.admin.property(
		short_description='name'
		long_description='long description',
		syntax=univention.admin.syntax.string,
		multivalue=False,
		required=True,
		may_change=True,
		identifies=False,
		dontsearch=True,
		default='default value',
		options=['opt1']),
	...}

Die Parameter haben folgende Bedeutung:

  • short_description Eine kurze Beschreibung, die beispielsweise im Web-Frontend des Univention Directory Manager als beschreibender Text zu den Eingabefeldern genutzt wird.
  • long_description: Eine ausführlichere Beschreibung, die im Web-Frontend des Univention Directory Manager für die Tooltips genutzt wird.
  • syntax: Dieser Parameter gibt den Typ eines Attributs an. Anhand dieser Typ-Definitionen kann der Univention Directory Manager die angegebenen Werte für das Attribut überprüfen und bei ungültigen Werten eine detaillierte Fehlermeldung liefern.
    Die nachfolgende Liste enthält die wichtigsten Typen für mögliche Werte, denen jeeils das Prefix univention.admin.syntax. voranzustellen ist. Zusätzlich zu den vordefinierten Typdefinitionen aus /usr/share/pyshared/univention/admin/syntax.py können auch eigene Typen über UDM als settings/syntax-Objekte oder programmatisch über eigene Dateien im Verzeichnis /usr/share/pyshared/univention/admin/syntax.d/ definiert werden.
    • string: eine beliebige Zeichenkette
    • integer: ein ganzzahliger positiver Wert
    • boolean: kann nur eine 0 oder eine 1 enthalten
    • filesize: ein ganzzahliger positiver Wert mit einer optional folgenden Angabe der Einheit. Unterstützt werden die Einheiten: B, KB, MB, GB, die Groß- und Kleinschreibung ist irrelevant
    • phone: eine Telefonnummer
    • uid: ein Benutzername
    • uid_umlauts: ein Benutzername mit Umlauten
    • gid: ein Gruppenname
    • sharePath: ein Pfad einer Freigabe
    • passwd: ein Passwort von min. acht Zeichen Länge
    • userPasswd: ein Passwort mit min. einem Zeichen
    • hostName: ein Rechnername nach RFC 952
    • ipAddress: eine IPv4-Adresse
    • hostOrIP: ein Rechnername oder eine IPv4-Adresse
    • netmask: eine Netzmaske
    • absolutePath: ein absoluter Pfad nach UNIX-Syntax, d.h. der Wert muss mit einem '/' anfangen
    • emailAddress: eine Email-Adresse
    • emailAddressTemplate: eine E-Mail Adresse mit Nutzername
    • date: ein Datum in verschiedenen Kurzschreibweisen: TT.MM.JJ oder TT.MM.JJJJ oder JJJJ-MM-TT
  • multivalue kann die Werte True oder False annehmen. Ist dieser Parameter auf True gesetzt handelt es sich bei dem Wert des Attributs um eine Liste. Der Parameter syntax gibt in diesem Fall den Typ der Elemente dieser Liste an.
  • required: Ist dieser Parameter auf True gesetzt, muß für dieses Attribut ein Wert angegeben werden.
  • may_change: Ist dieser Parameter auf True gesetzt, kann der Wert dieses Attributs auch nach der Erstellung des Objektes verändert werden, ansonsten nur einmalig bein Anlegen.
  • editable: Ist dieser Parameter auf False gesetzt, dann kann der Wert dieses Attributes schon beim Erstellen nicht angegeben werden. Dies ist i.d.R. nur für automatisch generierte oder berechnete Werte interessant.
  • identifies: diese Option sollte bei genau einem Attribut eines Moduls auf True gesetzt sein, um dasjenige Attribut zu kennzeichnen, anhand dessen das Objekt eindeutig zu identifizieren ist.
  • dontsearch: Ist dieser Parameter auf False gesetzt, kann das Feld nicht als das Feld für die Suche ausgewählt werden.
  • default: Ein Vorgabewert für das Attribut, wenn das Objekt über das Web-Frontend angelegt wird.
  • options: Eine Liste von Schlüsselwörtern, die Optionen identifizieren mit denen dieses Attribut ein- bzw. ausgeblendet werden kann.

layout

Die Attribute eines Objektes können in Gruppen angeordnet werden. Diese werden beispielsweise im Univention Directory Manager als Reiter dargestellt. Für jeden Reiter muß eine Instanz von univention.admin.tab im Array layout angelegt werden. Als Parameter wird der Name, eine Beschreibung für den Reiter sowie eine Liste der Zeilen erwartet. In jeder Zeile können sich bis zu zwei Attribute befinden, für die jeweils eine Instanz von univention.admin.field anzulegen ist. Als Parameter wird jeweils der Name eines UDM-Attributs aus #property-descriptions erwartet. Über den optionalen Parameter colspan=2 kann das Widget für ein UDM-Attribut auch über beide Spalten ausgedehnt werden.

layout = [
  univention.admin.tab(_('Tab header'), _('Tab description'), [
    [univention.admin.field('prop1'), univention.admin.field('prop3')],
    [univention.admin.field('prop3', colspan=2), ],
    ...
  ], advanced=False),
  ...
]

Über die optionale advanced=True-Einstellung kann kontrolliert werden, ob der Reiter standardmäßig angezeigt werden soll oder nur dann, wenn das Anzeigen der erweiterten Einstellungen aktiviert ist.

mapping

Bildet die UDM-Attribute auf LDAP-Attribute ab. Für jedes UDM-Attribut wird i.d.R. eine Abbildung registriert, die den Namen eines UDM-Attributs (udm_name) mit dem zugehörigen LDAP-Attribut (ldap_name) verknüpft:

mapping.register(udm_name, ldap_name)
mapping.register(udm_name, ldap_name, map_value, unmap_value)

Für die Umwandlung der Werte zwischen UDM-Attribut und LDAP-Attribut können zwei Funktionen angegeben werden: map_value ist für die Richtung UDM → LDAP zuständig, unmap_value für die Regenrichtung von LDAP zu UDM. Die zweite Funktion ist insbesondere notwendig für alle einwertige UDM-Attribute, da im LDAP selbst diese immer als null- oder einelementige Liste realisiert sind. Die Standard-Implementierung univention.admin.mapping.ListToString liefert jeweils den ersten Eintrag der Liste zurück und kann daher i.d.R. für alle einwertigen Attribute als unmap_value-Funktion angegeben werden. Für map_value (UDM→LDAP) genügt es None anzugeben, das dafür sorgt, das der ggf. vorhandene Wert in eine einelementige Liste konvertiert wird.

Achtung
Ein UDM-Attribute enthält entweder immer nur eine Zeichenkette (einwertige Attribute) oder eine Liste von Zeichenketten (mehrwertige Attribute), nie nur eine Zahl oder einen anderen Python-Typ!


Folgende Angaben sind optional und müssen nur definiert werden, wenn das Modul diese speziellen Attribute besitzt:

virtual

Module, die diese Variable auf 1 setzen sind eine Art Hilfsmodul für andere Module, die keine zugehörigen LDAP-Objekte haben. Ein Beispiel hierfür ist das Modul computers/computer, das als Hilfsmodul für alle Rechnertypen dient.

template

Ein Modul, das diese Variable auf 1 setzt, bietet die Möglichkeit Vorgabewerte für die Attribute von anderen Modulen zu definieren. Ein Beispiel hierfür ist die Benutzer-Vorlage (das Modul settings/usertemplate). Eine solche Vorlage kann dann beispielsweise beim Anlegen eines Benutzers ausgewählt werden und die darin definierten Werte werden als Vorgaben in die Eingabemasken übernommen.

Die object-Klasse

Die Klasse object eines Moduls bildet die Schnittstelle zwischen Univention Directory Manager und den LDAP-Operationen, die beim Anlegen, Verändern, Verschieben oder Löschen eines Objekts ausgelöst werden. Diese Klasse unterstützt den Univention Directory Manager bei der Abbildung der definierten Attribute des Moduls auf LDAP-Objekte und -Attribute.

Dafür ist die vordefinierte API der Klasse einzuhalten. Die Basisklasse univention.admin.handlers.simpleLdap bietet für einfache LDAP-Objekte die wesentlichen Funktionalitäten, so dass in der Regel nur noch wenige Anpassungen notwendig sind. Eine Instanz (self) kapselt alle Informationen eines Objekts, auf die auf verschiedenen Arten zugegriffen werden kann:

self.dn → String
Distinguished Name im LDAP-DIT
self.position → univention.admin.uldap#position
Container-Element im LDAP-DIT
self['UDM-Propertyname'] → [Werte, ...]
Wrapper um self.info, der zusätzlich bei einer Zuweisung den Wert anhand der Syntax überprüft und Standardwerte beim Lesen liefert.
self.info['UDM-Propertyname'] → [Werte, ...]
Dictionary mit den aktuell gesetzten Werten der UDM-Properties. Der direkte Zugriff darauf ermöglicht z.B. die Initialisierung von editable=False-Properties.
self.oldinfo['UDM-Propertyname'] → [Werte, ...]
Dictionary der ursprünglich gelesenen Werte, umgewandelt in UDM-Propertynamen. Diese Information wird vor allem intern benötigt, um Änderungen am Python-Objekt zurück auf den zugehörigen Eintrag im LDAP zu übertragen.
self.oldattr['LDAP-Attributname'] → [Werte, ...]
Dictionary der ursprünglich aus dem LDAP gelesenen Attribute. Wird i.d.R. nur dazu genutzt, um die objectClasses auszulesen.
self.oldpolicies → [Policy-DNs, ...]
Kopie der Liste der DNs der referenzierten univentionPolicyReference...
self.policies → [Policy-DNs, ...]
Liste der DNs der referenzierten univentionPolicyReference...
self.policyObjects[Policy-DN] → univention.admin.handlers#simplePolicy
Dictionary der geladenen Policies.
self.extended_udm_attributes → [univention.admin#extended_attribute, ...]
Liste aller Erweiterten Attribute des Objekts.
self.ldap_extra_objectclasses → [(objectClass, propertyName, syntax, ldapMapping, deleteValues, deleteObjectClass), ...]
interne Liste von Tupeln für Erweiterten Attribute, um automatisch Objektklassen hinzuzufügen bzw. zu entfernen

Die Klasse simpleLdap bietet zudem die weitere Möglichkeit, vor und nach der LDAP-Operation durch den Aufruf von Funktionen weitere Anpassungen vorzunehmen. Beispielsweise wird vor dem Anlegen eines LDAP-Objekte die Funktion _ldap_pre_create() und nach dem Vorgang die Funktion _ldap_post_create() aufgerufen. Solche Pre- und Post-Funktionen existieren analog auch für die Operationen modify, move und remove. Die folgende Tabelle listet alle verwendeten Funktionen in der Aufrufreihenfolge von oben nach unten auf:

Aktionen und Hooks
Beschreibung
Create
Modify
Remove
Vor der Validierung
_ldap_pre_ready()

Validiert, ob alle Muß-Attribute vorhanden sind
ready()


_ldap_pre_create() _ldap_pre_modify()
_ldap_pre_remove()
Policy Copy-on-Write
_update_policies() _update_policies()
Erweiterungspunkt für Erweiterte Attribute hook_ldap_pre_create() hook_ldap_pre_modify() hook_ldap_pre_remove()
Liefert initiale Liste der (LDAP-Attributname, Wert)- bzw. (LDAP-Attributname, [Werte])-Tupel
_ldap_addlist()

Berechnet die Unterschiede zwischen self.oldinfo und self.info
_ldap_modlist()

Erweiterungspunkt für Erweiterte Attribute hook_ldap_addlist() hook_ldap_modlist()
eigentliche Aktion ADD MODIFY DELETE

_ldap_post_create() _ldap_post_modify()
_ldap_post_remove()
Erweiterungspunkt für Erweiterte Attribute hook_ldap_post_create() hook_ldap_post_modify() hook_ldap_post_remove()

Die hook_ldap_*-Funktionen werden genauer in einem gesonderten Artikel über das Erstellen eines erweiterten Attributs mit Hook beschrieben.

Die Funktionen identify und lookup

Diese Funktionen werden genutzt, um bei Suchanfragen aus dem Web-Frontend des Univention Directory Manager die dazugehörigen Objekte zu finden (lookup) und um LDAP-Objekte einem Univention Directory Manager-Modul zuzuordnen. Später werden Beispielimplementierungen für diese zwei Funktionen vorgestellt, die für einfache LDAP-Objekte nur leicht modifiziert werden müssen.

Beispiel-Modul

Im folgenden wird ein Beispiel-Modul für den Univention Directory Manager vorgestellt, das auch als Paket univention-directory-manager-module-example unter univention-directory-manager-module.example.tar.gz verfügbar ist.

Ein Univention Directory Manager-Modul besteht in der Regel immer aus zwei Komponenten: Einmal dem Python-Modul, das die Implementierung der Schnittstelle zum Univention Directory Manager enthält und einem LDAP-Schema, das das zu verwaltende LDAP-Objekt definiert. Im folgenden werden beide Teile beschrieben, wobei der Schwerpunkt bei der Erstellung des Python-Moduls liegt.

Python-Modul des Beispiel-Moduls

Das folgende Modul für den Univention Directory Manager demonstriert die rudimentäre Verwaltung von IP-Telefonen. Dabei wird versucht mit einem einfach gehaltenen Beispiel möglichst viele der Möglichkeiten eines Univention Directory Manager-Moduls aufzuzeigen.

Vor der Definition des eigentlichen Modul-Quellcodes müssen einige Basis-Python-Module importiert werden, die auf jeden Fall notwendig sind:

import univention.admin.filter
import univention.admin.handlers
import univention.admin.syntax

Diese Liste von Python-Modulen kann natürlich noch erweitert werden. Wie in Abschnitt #Globale Variablen beschrieben, werden in einem Univention Directory Manager-Modul zu Beginn einige notwendige globale Variablen definiert, die eine Beschreibung des Moduls liefern:

module = 'test/ip-phone'
childs = 0
short_description = u'IP-Telefon'
long_description = u'Ein Beispiel-Modul für den \
		     Univention Directory Manager zur Verwaltung von IP-Telefonen'
operations = ['add', 'edit', 'remove', 'search', 'move']

Eine weitere für das Web-Frontend des Univention Directory Manager wichtige globale Variable ist #layout:

layout = [
        univention.admin.tab('Allgemein', 'Allgemeine Einstellungen', [
                [ univention.admin.field("name"), univention.admin.field("active") ],
                [ univention.admin.field("ip"), univention.admin.field("protocol") ],
                [ univention.admin.field("priuser") ],
            ]),
        univention.admin.tab('Erweiterungen', 'Erweiterte Einstellungen', [
                [ univention.admin.field("users") ],
            ], advanced=True),
        ]

Sie strukturiert die Anordnung der einzelnen Attribute des Objektes auf die Reiter. Es handelt sich dabei um eine Liste aus Elementen vom Typ univention.admin.tab, die jeweils den Inhalt eines Reiters bestimmen. In diesem Fall gibt es einen Reiter Allgemein und einen weiteren Reiter Erweiterungen.

Achtung
Es können nicht mehr als zwei Felder nebeneinander angeben werden, d.h. ein Listenelement darf nicht aus mehr als zwei Objekten vom Typ univention.admin.field bestehen!

Als nächstes sollten die Optionen (#options) und Attribute des Moduls definiert werden.

In diesem Fall wird eine Option extended angelegt, deren Funktion später noch erläutert wird. Um die Parameter zu konfigurieren wird dem Objekt univention.admin.option der Option short_description für eine Kurzbeschreibung übergeben. Mit default kann die Vorkonfiguration bestimmt werden, mit True ist die Option standardmässig aktiviert, mit False deaktiviert.

options = {
    'extended': univention.admin.option(
            short_description=u'Erweiterte Einstellungen',
            default=True
        )
}

Nach den Optionen werden die Attribute des Moduls festgelegt. Dabei werden die Attribute durch textuelle Beschreibungen, Syntaxdefinitionen und Anweisungen für das Web-Frontend des Univention Directory Managers definiert.

property_descriptions = {

Das Attribut name definiert den "Rechnernamen" des IP-Telefons. Mit dem Parameter syntax wird dem Univention Directory Manager mitgeteilt, dass gültige Werte für dieses Attribut der Syntax eines Rechnernamen entsprechen müssen. Weitere vordefinierte Syntaxdefinitionen finden sich im Abschnitt #property-descriptions. Eine Besonderheit dieses Attributs ist der Parameter may_change, der in diesem Fall auf False gesetzt ist. Dadurch wird festgelegt, dass dieses Attribut nach dem Anlegen des Objektes nicht mehr geändert werden kann:

    'name': univention.admin.property(
            short_description=u'Name',
            long_description=u'Name des Telefons',
            syntax=univention.admin.syntax.hostName,
            multivalue=False,
            options=[],
            required=True,
            may_change=False,
            identifies=True
        ),

active ist ein Beispiel für ein boolsches/binäres Attribut, das nur die Werte 1 oder 0 annehmen kann. In diesem Beispiel definiert es eine Freischaltung/Sperrung des IP-Telefons. Durch dem Parameter default=1 wird das Telefon initial freigeschaltet:

    'active': univention.admin.property(
            short_description=u'freigeschaltet',
            long_description=u'Ein IP-Telefon kann gesperrt werden',
            syntax=univention.admin.syntax.boolean,
            multivalue=False,
            options=[],
            required=False,
            default='1',
            may_change=True,
            identifies=False
        ),

Das Attribut protocol legt fest, welches VoIP-Protokoll von dem Telefon unterstützt wird. Dabei wird für dieses Attribut keine Standard-Syntaxdefinition verwendet, sondern eine eigens dafür deklarierte Klasse SynVoIP_Protocols. (Der Quellcode dieser Klasse folgt in einem späteren Abschnitt). Die Syntax der Klasse definiert eine Auswahlliste mit einer vordefinierten Menge an Möglichkeiten. Durch den Parameter default wird der Wert mit dem Schlüssel sip vorausgewählt.

    'protocol': univention.admin.property(
            short_description=u'Protokoll',
            long_description=u'Welches VoIP Protokoll \
	    wird von dem Telefon unterstützt',
            syntax=SynVoIP_Protocols,
            multivalue=False,
            options=[],
            required=False,
            default='sip',
            may_change=True,
            identifies=False
        ),

Das Attribut ip legt die IP-Adresse des Telefons fest. Als Syntaxdefinition wird die vordefinierte Klasse univention.admin.syntax.ipAddress angeben. Zusätzlich wird mit dem Parameter required erzwungen, dass dieses Attribut zwingend gesetzt sein muss.

    'ip': univention.admin.property(
            short_description=u'IP-Adresse',
            long_description=u'',
            syntax=univention.admin.syntax.ipAddress,
            multivalue=False,
            options=[],
            required=True,
            may_change=True,
            identifies=False
        ),

Das Attribut priuser setzt den primären Benutzer des IP-Telefons. Hierfür wird ebenfalls eine eigene Syntax-Definition verwendet. Zum Einsatz kommt dabei eine Klasse, die die gültigen Werte über einen regulären Ausdruck definiert. (Der Quellcode wird später dargestellt)

    'priuser': univention.admin.property(
            short_description=u'primärer Benutzer',
            long_description=u'Der primäre Benutzer \
	    dieses Telefons',
            syntax=SynVoIP_Address,
            multivalue=False,
            options=[],
            required=True,
            may_change=True,
            identifies=False
        ),

Das Attribut users zeigt die Verwendung von Optionen. Mit dem Parameter options=['extended'] wird festgelegt, dass dieses Attribut nur zur Verfügung steht, wenn mindestens eine der aufgelisteten Optionen aktiviert ist. Ist die Liste der angegeben Optionen leer (vordefinierter Wert), wird das Attribut immer angezeigt.

    'users': univention.admin.property(
            short_description=u'weitere Benutzer',
            long_description=u'Benutzer, die an diesem \
	    Telefon registriert sein dürfen',
            syntax=SynVoIP_Address,
            multivalue=True,
            options=['extended'],
            required=False,
            may_change=True,
            identifies=False
        )
}

Die folgenden zwei Klassen sind die Syntaxdefinitionen, die für die Attribute protocols, priuser und users verwendet wurden. SynVoIP_Protocols basiert auf der vordefinierten Klasse univention.admin.syntax.select, die die Basisfunktionalität für Auswahllisten zur Verfügung stellt. Abgeleitete Klassen müssen, wie in der folgenden Klasse zu sehen ist, nur einen Namen und die Liste der Auswahlmöglichkeiten definieren.

Das Attribut choices besteht aus einer Liste von Paaren, die aus einem eindeutigen Schlüssel für das Element und dem Text, der angezeigt werden soll, bestehen.

class SynVoIP_Protocols(univention.admin.syntax.select):
    name=u'VoIP_Protocol'
    choices=[('sip', u'SIP'), ('h323', u'H.323'), ('skype', u'Skype')]

Die andere Syntaxdefinition — SynVoIP_Address — basiert auf der Klasse univention.admin.syntax.simple, die eine Basisfunktionalität für Syntaxdefinitionen bietet, die mit regulären Ausdrücken arbeiten. Wie bei der anderen Definition muss ein Name vergeben werden. Zusätzlich sind noch die Attribute min_length und max_length anzugeben, die eine minimale und eine maximale Länge für zulässige Werte festlegen. Wird eines dieser Attribute auf 0 gesetzt, entspricht das einer nicht existenten Grenze in die jeweilige Richtung. Außer den genannten Attributen muss noch die Funktion parse() definiert werden, die als Parameter den zu prüfenden Wert übergeben bekommt. Mittels des Python Moduls re wird in diesem Fall geprüft, ob der Wert dem Muster einer VoIP Adresse entspricht, z.B. sip:hans@mustermann.de.

class SynVoIP_Address(univention.admin.syntax.simple):
    name = 'VoIP_Address'
    min_length = 4
    max_length = 256
    _re = re.compile('((^(sip|h323|skype):)?([a-zA-Z])[a-zA-Z0-9._-]+)@[a-zA-Z0-9._-]+$')

    def parse(self, text):
        if self._re.match(text) is not None:
            return text
        raise univention.admin.uexceptions.valueError,
                u'Keine gültige VoIP Adresse'

Anschließend wird die Abbildung von den Modul-Attributen auf die Attribute des zu erzeugenden LDAP-Objektes definiert (#mapping). Dabei wird die Klasse univention.admin.mapping.mapping verwendet, die mit der Funktion register() eine einfache Möglichkeit bietet, für die einzelnen Attribute Abbildungen zu registrieren. Das erste Argument der Funktion ist der Name des Modul-Attributs und das zweite der Name des LDAP-Attribute. Mit den folgenden zwei Argument der Funktion register() können Abbildungsfunktionen für die Konvertierung von der Modul-Attributen zum LDAP-Attribute und vice versa angegeben werden.

mapping = univention.admin.mapping.mapping()
mapping.register('name', 'cn', None, univention.admin.mapping.ListToString)
mapping.register('active', 'testPhoneActive', boolToString, stringToBool)
mapping.register('protocol', 'testPhoneProtocol', None, univention.admin.mapping.ListToString)
mapping.register('ip', 'testPhoneIP', None, univention.admin.mapping.ListToString)
mapping.register('priuser', 'testPhonePrimaryUser', None, univention.admin.mapping.ListToString)
mapping.register('users', 'testPhoneUsers')

Abschließend muss für das Modul noch eine Klasse object (#Die object-Klasse) definiert werden, die den in Abschnitt #Aufbau eines Moduls definierten Vorgaben entspricht. Für das IP-Telefon würde die Klasse folgendermaßen aussehen:

class object(univention.admin.handlers.simpleLdap):
    module=module

    def __init__(self, co, lo, position, dn='', superordinate=None, 
            arg=None):
        global mapping
        global property_descriptions
        self.co = co
        self.lo = lo
        self.dn = dn
        self.position = position
        self._exists = 0
        self.mapping = mapping
        self.descriptions = property_descriptions
        univention.admin.handlers.simpleLdap.__init__(self, co, lo, \
	    position, dn, superordinate)

    def exists(self):
        return self._exists

    def open(self):
        univention.admin.handlers.simpleLdap.open(self)
        self.save()

    def _ldap_pre_create(self):
        self.dn = '%s=%s,%s' % (mapping.mapName('name'), mapping.mapValue
                ('name', self.info['name']), self.position.getDn())

    def _ldap_addlist(self):
        return [('objectClass', ['top', 'testPhone' ])]

Damit auch nach Objekten, die dieses Modul verwaltet, gesucht werden kann gibt es noch zwei Funktionen lookup() und identify() (#Die Funktionen identify und lookup). Die hier vorgegebenen Funktionen sollten für einfache LDAP-Objekte, die durch eine einzelne objectClass identifiziert werden können, ausreichen. Für eigene LDAP-Objekte müsste die Objektklasse testPhone ersetzt werden.

def lookup(co, lo, filter_s, base='', superordinate=None, scope='sub', 
        unique=False, required=False, timeout=-1, sizelimit=0):
    filter = univention.admin.filter.conjunction('&', [
            univention.admin.filter.expression('objectClass', \
	    'testPhone'),])

    if filter_s:
        filter_p = univention.admin.filter.parse(filter_s)
        univention.admin.filter.walk(filter_p, 
                univention.admin.mapping.mapRewrite, arg=mapping)
        filter.expressions.append(filter_p)

    res = []
    for dn in lo.searchDn(unicode(filter), base, scope, unique, required, 
            timeout, sizelimit):
        res.append(object(co, lo, None, dn))
    return res

Diese Funktion prüft, ob das übergebene LDAP-Objekt zu der von diesem Modul verwalteten Menge gehört.

def identify(dn, attr, canonical=0):
    return 'testPhone' in attr.get('objectClass', [])

LDAP-Schema-Erweiterung für das Beispiel-Modul

Bevor das entwickelte Modul für den Univention Directory Manager genutzt werden kann, muss dem LDAP-Server erst noch die neue Objektklasse, in diesem Fall testPhone, zusammen mit ihren Attributen bekannt gemacht werden. Solche Objektdefinitionen werden bei LDAP über sogenannte Schemata definiert, die in Dateien beschrieben werden, die wie folgt aussehen:

attributetype ( 1.3.6.1.4.1.10176.9999.1.1 NAME 'testPhoneActive'
    DESC 'state of the IP phone'
    EQUALITY caseIgnoreIA5Match
    SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )

attributetype ( 1.3.6.1.4.1.10176.9999.1.2 NAME 'testPhoneProtocol'
    DESC 'The supported VoIP protocol'
    EQUALITY caseExactIA5Match
    SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )

attributetype ( 1.3.6.1.4.1.10176.9999.1.3 NAME 'testPhoneIP'
    DESC 'The IP address of the phone'
    EQUALITY caseExactIA5Match
    SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )

attributetype ( 1.3.6.1.4.1.10176.9999.1.4 NAME 'testPhonePrimaryUser'
    DESC 'The primary user of the phone'
    EQUALITY caseIgnoreIA5Match
    SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )

attributetype ( 1.3.6.1.4.1.10176.9999.1.5 NAME 'testPhoneUsers'
    DESC 'A list of other users allowed to use the phone'
    EQUALITY caseIgnoreIA5Match
    SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )

objectclass ( 1.3.6.1.4.1.10176.9999.2.1 NAME 'testPhone'
    DESC 'IP Phone'
    SUP top  STRUCTURAL
    MUST ( cn $ testPhoneActive $ testPhoneProtocol $ \
    testPhoneIP $ testPhonePrimaryUser )
    MAY ( testPhoneUsers )
    )

Eine ausführliche Dokumentation zur Erstellung von LDAP Schema-Dateien ist auf der Webseite des OpenLDAP Projektes (http://www.openldap.org/) zu finden und ist nicht Schwerpunkt dieser Dokumentation.

Einrichtung des Moduls

Als letzter Schritt müssen das Python-Modul und das LDAP-Schema installiert werden. Im folgenden werden diese beiden Schritte dokumentiert.

Das Python-Modul muss in das Verzeichnis /usr/lib/python2.4/site-packages/univention/admin/handlers/ kopiert werden, damit der Univention Directory Manager es findet. In diesem Verzeichnis ist ein Unterverzeichnis anzulegen. Es sollte dem ersten Teil des Modulnamens entsprechen. Wenn der Name des Moduls beispielsweise test/ip-phone ist, dann sollte das Verzeichnis test heißen. Das Python-Modul muss dann in dieses Verzeichnis kopiert werden. Idealerweise wird ein UDM-Modul wie in ein eigenes Paket integriert. Eine Dokumentation dazu findet sich im Artikel Paketierung von Software für UCS. Das neu angelegte Paket wird dann beim Aufruf von

univention-directory-manager modules

mit angezeigt.

Die Datei, die das LDAP-Schema enthält, kann im Prinzip in ein beliebiges Verzeichnis kopiert werden. Die Schema-Definitionen von Univention werden beispielsweise in dem Verzeichnis /usr/share/univention-ldap/schema/ abgelegt. Damit der LDAP-Server dieses Schema findet muss es in die Konfigurationsdatei /etc/ldap/slapd.conf eingebunden werden. Da diese Datei unter der Kontrolle von Univention Configuration Registry steht, sollte nicht direkt die Datei editiert werden, sondern ein Univention Configuration Registry-Template erstellt werden (siehe UCS-Handbuch).

Download des Beispielcodes

Die aktuelle Version des Beispielcodes ist unter univention-directory-manager-module-example im SVN einsehbar. Das Verzeichnis enthält ein Quellpaket im Debian-Format, aus dem bei Paketbau per ./debian/rules binary zwei Binärpakete erzeugt werden: Ein Schema-Paket, das auf dem Master installiert werden muss und das Paket mit dem UDM-Modul selbst. Der Beispielcode enthält auch ein Skript ip-phone-tool, das beispielhaft die Verwendung der UDM Python API in einem Python-Skript zeigt.

Personal tools