Geschrieben von: Christoph Wille
Kategorie: ASP.NET
This printed page brought to you by AlphaSierraPapa
Active Directory ist das zentrale Element einer Windows 2000 Domain Infrastruktur. Es gibt viele Zugriffsmethoden (ADSI oder LDAP, um zwei sehr bekannte zu wählen), die mehr oder minder komplex sind. Mit dem .NET Framework ist Microsoft angetreten, es leichter zu machen. Heute werden wir uns das anhand des Zugriffs als auch der Suche nach Elementen ansehen.
Voraussetzung um den Sourcecode dieses Artikels verwenden zu können ist eine Installation des Microsoft .NET Framework SDK's auf einem Webserver. Weiters setze ich voraus, daß der Leser die Programmiersprache C# zu einem gewissen Grad beherrscht - es finden sich etliche Artikel auf diesem Server, um das notwendige Wissen zu erlernen.
Sicherheitshinweis: die Beispielannahme ist, daß der ASP.NET Worker Prozess unter dem SYSTEM Konto läuft (standardmäßig läuft er unter dem ASPNET Account). Bitte ändern Sie processModel in machine.config entsprechend ab, oder geben nur Administratoren Zugriff auf die Dateien (Impersonation).
Es empfiehlt sich nicht, die ersten Gehversuche mit Echtdaten vorzunehmen. Eine Organizational Unit (OU) mit Testdaten ist da schon deutlich besser geeignet. Das Anlegen einer OU passiert in Active Directory User and Computers:
Da man ja nicht immer als Domain-Administrator eingeloggt ist (hoffentlich nicht!), kann man sich ohne Ab-/Anmelden speziell für die jeweils gewünschte Applikation als Domain-Administrator ausgeben: mit Run As. Dieses nette Kontextmenü erhält man übrigens duch die Kombination Shift Taste und rechte Maustaste. Es öffnet sich folgender Dialog:
Hier gibt man die entsprechenden Logindaten ein, und schon startet Active Directory User and Computers unter einem beliebigen User Account:
Hier habe ich bereits eine OU angelegt ("Demo"), sowie einen User Account für unsere Experimente ("Max Mustermann"). Die Domain heißt für die nachfolgenden Beispiele Dev.AlfaSierraPapa.com, der DC den wir ansprechen Strangelove.
Die gesamte Funktionalität für den Zugriff auf das Active Directory ist im System.DirectoryServices Namespace implementiert. Um mir den Import der Assembly in jeder ASP.NET Datei zu ersparen, habe ich das in der web.config vorgenommen:
<configuration> <system.web> <compilation debug="true"> <assemblies> <add assembly="System.DirectoryServices, Version=1.0.3300.0,... </assemblies> </compilation> </system.web> </configuration>
Beginnen wir mit dem Zugriff auf einen einzelnen Active Directory Eintrag (simplestart.aspx):
<% @Page Language="C#" %> <% @Import Namespace="System.DirectoryServices" %> <% DirectoryEntry de = new DirectoryEntry("LDAP://strangelove/OU=Demo,DC=Dev,DC=AlfaSierraPapa,DC=COM", "DEV\\Administrator", "foryoureyesonly"); foreach(DirectoryEntry child in de.Children) { Response.Write(child.Name); } %>
Die Klasse zum Zugriff auf ein einzelnes Verzeichnisobjekt ist DirectoryEntry. Diesem übergebe ich in obigen Beispiel den LDAP (Lightweight Directory Access Protocol) String, unter dem das Objekt zu finden ist, sowie separat Useraccountdaten. Warum der User Account separat? Nun, vertrauen in Impersonisierung ist gut, händisches sicherstellen ist deutlich angenehmer und spart Stunden der Fehlersuche.
Der LDAP String sieht nur auf den ersten Blick fürchterlich aus, er ist aber völlig logisch aufgebaut, und wir werden heute noch einige Variationen sehen. Der String wird von rechts nach links immer spezifischer - von der COM Domain hin zum gewünschten OU Objekt. Der Servername muß nicht angegeben werden, fehlt er, wird er automatisch mit Hilfe von Active Directory ermittelt.
Nach dem Zugriff auf das Objekt liste ich die Kindelemente auf, das sieht für die Beispiel-OU dann so aus:
CN=Max Mustermann
Der Common Name (CN) des einzigen Kindobjektes in dieser OU ist Max Mustermann.
Erweitern wir das Beispiel ein wenig (somemore.aspx):
<% @Page Language="C#" %> <% @Import Namespace="System.DirectoryServices" %> <% DirectoryEntry de = new DirectoryEntry("LDAP://strangelove/OU=Demo,DC=Dev,DC=AlfaSierraPapa,DC=COM", "DEV\\Administrator", "youwontexpectthatone"); DirectoryEntry parent = de.Parent; foreach(DirectoryEntry child in parent.Children) { Response.Write(child.Name + "<br>"); } DirectoryEntry dcOU = parent.Children.Find("OU=Domain Controllers"); foreach(DirectoryEntry child in dcOU.Children) { Response.Write(child.Name + "<br>"); } %>
Mit der Parent Eigenschaft kann man auf das im Baum darüberliegende Objekt zugreifen, und dort ebenso die Objekte auflisten. Ebenfalls praktisch ist die Find Methode zum Suchen von Objekten, in diesem Falle suche ich nach der OU für Domain Controller. Der Output sieht dann so aus:
CN=Builtin CN=Computers OU=Demo OU=Domain Controllers CN=ForeignSecurityPrincipals CN=Infrastructure CN=LostAndFound CN=System CN=Users CN=STRANGELOVE CN=WEBDEVSRV01
Die letzten beiden Einträge sind die Domain Controller, die in der Domain Controllers OU abgelegt sind, der Rest sind die Top-Level Objekte der Domain.
Die Find Methode haben wir schon kennengelernt, jetzt werden wir uns aber mit dem richtigen Tool anfreunden: der DirectorySearcher Klasse. Zur Einstimmung gleich ein Beispiel:
<% @Page Language="C#" %> <% @Import Namespace="System.DirectoryServices" %> <% DirectoryEntry de = new DirectoryEntry("LDAP://OU=Demo,DC=Dev,DC=AlfaSierraPapa,DC=COM", "DEV\\Administrator", "itisyourguess"); // Angabe Suchausdruck und zu holende properties (PropertiesToLoad) DirectorySearcher src = new DirectorySearcher("(objectClass=*)", new string[]{"sn","givenName","title"}); src.SearchRoot = de; src.SearchScope = SearchScope.OneLevel; foreach(SearchResult res in src.FindAll()) { Response.Write(res.Properties["sn"][0] + "<br>"); } %>
In diesem Beispiel verwende ich beim öffnen des DirectoryEntry keine Serverangabe. Danach erstelle ich ein DirectorySearcher Objekt, dem ich den Suchausdruck und eine Liste der Eigenschaften mitgebe, die ich aus dem Active Directory zurückgeliefert bekommen möchte (pro Objekt werden sehr viele Eigenschaften gespeichert, hier geht es also um Performance).
Es fehlt noch die Angabe des SearchRoots, also wo die Suche beginnen soll, und wie im Baum gesucht werden soll - das SearchScope. Die folgende Grafik illustriert die drei verschiedenen SearchScopes mit einem ausgewählten SearchRoot:
Ich hatte OneLevel gewählt, also hat DirectorySearcher nur nach Elementen in der aktuellen OU gesucht. Die Suche beginnt allerdings erst, sobald man FindAll aufruft.
Natürlich kann der Suchbegriff auch komplizierter werden, zB kann man nach allen Usern einer Domain suchen, wie im folgenden Beispiel gezeigt (allusers.aspx):
<% @Page Language="C#" %> <% @Import Namespace="System.DirectoryServices" %> <% DirectoryEntry de = new DirectoryEntry("LDAP://DC=Dev,DC=AlfaSierraPapa,DC=COM", "DEV\\Administrator", "noway"); // Angabe Suchausdruck DirectorySearcher src = new DirectorySearcher("(&(objectCategory=Person)(objectClass=user))"); src.SearchRoot = de; src.SearchScope = SearchScope.Subtree; foreach(SearchResult res in src.FindAll()) { Response.Write(res.Properties["Name"][0] + "<br>"); } %>
Hier ist eine UND-Verknüpfung eingesetzt (für eine genaue Diskussion der Abfragesprache muß ich leider an die Dokumentation verweisen), und die Suche erstreckt sich auf den gesamten Unterbaum (Subtree), ausgehend vom Domain Root. Minimale Zeilenanzahl für maximalen Erfolg.
Heute habe ich mir nur einen Unteraspekt der Active Directory Funktionen von .NET herausgepickt. Es ist noch einiges weiteres vorhanden - so zB das Anlegen von Objekten, Löschen, Verschieben, etc. Aber das ist eine andere Geschichte.
This printed page brought to you by AlphaSierraPapa
Klicken Sie hier, um den Download zu starten.
http://www.aspheute.com/code/20011121.zip
©2000-2006 AspHeute.com
Alle Rechte vorbehalten. Der Inhalt dieser Seiten ist urheberrechtlich geschützt.
Eine Übernahme von Texten (auch nur auszugsweise) oder Graphiken bedarf unserer schriftlichen Zustimmung.