Active Directory (cz.1) – omówienie interfejsu, poÅ‚Ä…czenie, dostÄ™p do danych

Active Directory Service Interfaces (ADSI) jest zestawem obiektów tworzących warstwę abstrakcji, mająca za zadanie udostępnić jednolity zestaw interfejsów dostępu do danych i funkcji różnych usług katalogowych. Zastosowanie ADSI ma za zadanie uniezależnić administratorów i programistów od konkretnego środowiska sieciowego w którym znajduje się i pracuje usługa katalogowa. Interfejs ten umożliwia enumerację i zarządzanie zasobami usługi katalogowej niezależnie od środowiska, o ile interfejs ADSI udostępnia odpowiedni provider dla tego środowiska.
ADSI dostarcza prostych metod dostÄ™pu do usÅ‚ug katalogowych poprzez interfejs COM. Aplikacje wykorzystujÄ…ce interfejs COM można pisać w różnych jÄ™zykach – interfejs ADSI może być oprogramowany na przykÅ‚ad w C/C++, Microsoft Visual Basic czy też w skryptach VB Script, Perl. ADSI dostarcza również interfejsu dostÄ™pu OLE DB, dziÄ™ki czemu możliwy jest dostÄ™p do danych katalogu w sposób podobny jak przy oprogramowaniu baz danych.
Interfejs ADSI jest dostępny domyślnie w systemach operacyjnych Windows 2000/XP/2003, w systemach wcześniejszych dostępny jest po zainstalowaniu w systemie klienta usług katalogowych przeznaczonego dla danego systemu. Na poniższym rysunku przedstawiona została ogólna architektura ADSI.

ADSI umożliwia poprzez zainstalowanych w systemie dostawców (providers) współpracę z różnym środowiskami usług katalogowych. Środowiska te w ramach interfejsów ADSI przedstawione są użytkownikowi w postaci różnych przestrzeni nazw (namespace). Przestrzenie te reprezentują różne rodzaje katalogów zawierających dane, i wyróżniane są przy odwołaniu do nich poprzez odpowiedni przedrostek. I tak:

  • Winnt://, umożliwia dostÄ™p do danych systemu i katalogu w zakresie dostÄ™pnym w systemach z rodziny Windows NT.
  • LDAP://, umożliwia dostÄ™p do danych katalogów LDAP (w tym Active Directory)
  • NDS://, umożliwia dostÄ™p do danych Novell Directory Services
  • NWCOMPAT://, umożliwia dostÄ™p do danych serwerów Novell Netware
  • ADs://, specjalny interfejs umożliwiajÄ…cy miÄ™dzy innymi enumeracji wszystkich dostawców zainstalowanych w systemie.

KorzystajÄ…c z ADSI w systemach Windows najczęściej posÅ‚ugiwać siÄ™ bÄ™dziemy odwoÅ‚aniami do przestrzeni WINNT://, oraz LDAP:// sÅ‚użącej do podÅ‚Ä…czenia siÄ™ do danych katalogu domeny/LDAP (w naszym przypadku Active Directory). Należy tutaj zaznaczyć że korzystajÄ…c z dostawcy WINNT:// możliwy jest również dostÄ™p do danych w katalogu Active Directory, lecz zakres danych i interfejsów ograniczony jest do funkcjonalnoÅ›ci domeny Windows NT. W systemie możemy siÄ™ spotkać z dodatkowymi przestrzeniami nazw które zostaÅ‚y doÅ‚Ä…czone do interfejsu ADSI poprzez mechanizm doÅ‚Ä…czania kolejnych dostawców. PrzykÅ‚adem takiego rozszerzenia jest chociażby przestrzeÅ„ IIS:// – umożliwiajÄ…ca dostÄ™p do informacji i zarzÄ…dzanie danymi serwera IIS.

Artykuł ten mający być wprowadzeniem do zastosowania interfejsu ADSI w zarządzaniu obiektami znajdującymi się w ramach usługi katalogowej skupi się na dwóch podstawowych zagadnieniach:

  • podÅ‚Ä…czeniu siÄ™ do zasobów usÅ‚ugi katalogowej Active Directory z zastosowaniem różnego rodzaju interfejsów
  • uzyskaniem dostÄ™pu do danych poprzez uzyskane poÅ‚Ä…czenie.

Jako że artykuł ten skierowany jest głównie do administratorów systemów operacyjnych, a oni posługują się najczęściej w codziennej pracy skryptom, przedstawione przykłady tworzone będą z zastosowaniem języka VB Script. Do wykonania ich niezbędny będzie, więc komputer na którym zainstalowany jest interfejs ADSI oraz Windows Scripting Host w wersji minimum 5.1 (wersja 5.6 zalecana).

PodÅ‚Ä…czenie siÄ™ do źródÅ‚a danych – ADO DB

Pierwszym zadaniem jakie nas czeka jest podłączenie się na uprawnieniach odpowiedniego użytkownika do źródła danych. W tym wypadku możliwe jest podłączenie się na podstawie uwierzytelnienia użytkownika uruchamiającego skrypt lub podanie alternatywnych danych dostępu. Podłączyć możemy się również do gałęzi katalogu w której znajduje się stacja z której uruchamiany jest skrypt lub tez do wyspecyfikowanej gałęzi lub innej domeny katalogu. Wszystkie te parametry określamy poprzez poprawnie skonfigurowany łańcuch połączenia (connection string) oraz wywołanie odpowiedniej funkcji.

W przypadku gdy korzystamy z interfejsu dostÄ™pu do katalogu poprzez OLE DB rzecz wyglÄ…da trochÄ™ inaczej – parametry poÅ‚Ä…czenia takie jak nazwÄ™ użytkownika oraz opcje takie jak na przykÅ‚ad szyfrowanie hasÅ‚a okreÅ›lamy w parametrach wywoÅ‚ania zapytania, zaÅ› gałąź katalogu do której siÄ™ poÅ‚Ä…czymy – bezpoÅ›rednio w zapytaniu. I tak w celu poÅ‚Ä…czenia siÄ™ do katalogu za pomocÄ… interfejsu ADO DB musimy utworzyć obiekt poÅ‚Ä…czenia “ADODB.Connection”. Tworzymy go poprzez:

Set objConnection = CreateObject("ADODB.Connection")

Dla obiektu poÅ‚Ä…czenia konieczne jest wskazanie odpowiedniego dostawcy umożliwiajÄ…cego dostÄ™p do danych katalogu. Dostawca ten (provider) nazwy siÄ™ “ADsDSOObject” i definiujemy go poprzez polecenie:

Set objConnection.Provider = "ADsDSOObject"

Połączenie takie może zostać wykonane na podstawie uwierzytelnienia użytkownika wywołującego skrypt lub z zastosowaniem alternatywnych poświadczeń definiowanych w parametrach obiektu połączenia. Parametry te to:

  • “User ID”: nazwa użytkownika
  • “Password”: hasÅ‚o do odpowiedniego konta
  • “Encrypt Password”: zmienna logiczna determinujÄ…ca czy hasÅ‚o jest szyfrowane, domyÅ›lnie wartość tego parametru to false
  • “ADSI Flag”: flagi determinujÄ…ce opcje poÅ‚Ä…czenia, opis tych flag można znaleźć na stronie MSDN.

Opcje te specyfikowane są poprzez ustawienie właściwości obiektu połączenia w następujący sposób:

objConnection.Properties("User ID") = strUser
objConnection.Properties("Password") = strPasswd

W celu uaktywnienia naszego połączenia musimy dokonać operacji otwarcia połączenia:

objConnection.Open "Active Directory Provider"

W przypadku gdy używamy odpowiednich danych uwierzytelnienia otwarcie połączenia przyjmuje formę:

objConnection.Open "Active Directory Provider", strUser, strPasswd

MajÄ…c przygotowane poÅ‚Ä…czenie zajmiemy siÄ™ teraz stworzeniem obiektu polecenia które zostanie wykonane w naszym skrypcie. Obiekt “ADODB.Command” pozwala na przekazanie i wykonanie poprzez aktywne poÅ‚Ä…czenie polecenia w odniesieniu do katalogu LDAP z zastosowaniem ADSI. Tworzymy wiÄ™c obiekt ADODB.Command:

Set objCommand = CreateObject("ADODB.Command")

następnie zaś wskazujemy dla tego połączenia utworzony wcześniej obiekt połączenia jako ten, który ma być zastosowany w celu jego wykonania

Set objCommand.ActiveConnection = objConnection

Dla obiektu polecenia konieczne jest również ustawienie kilku parametrów, w szczególności treści polecenia które zostanie wykonane. Parametry obiektu polecenia ustawiamy analogicznie jak w przypadku obiektu połączenia. Z kilku dostępnych parametrów w przypadku naszego polecenia zostaną ustalone:

  • “Page Size”: opcja przydatna przy zapytaniach które zwracajÄ… dużą liczbÄ™ obiektów (powyżej 1000). Standardowo bez ustawienia tej wartoÅ›ci liczba zwracanych rezultatów jest ograniczona do 1000, jeżeli oczekujemy że zwrócone zostanie wiÄ™cej niż 1000 obiektów konieczne jest posÅ‚użenie siÄ™ mechanizmem stronicowania (paging). Wartość tego parametru okresla wielkość pojedynczej “strony” i uruchamia mechanizm stronicowania wyników danych.
  • “Timeout”: podany w sekundach czas oczekiwania na odpowiedź serwera do którego zostaÅ‚o skierowane zapytanie
  • “Searchscope”: zakres wyszukiwania, możliwe wartoÅ›ci to 0,1,2 (dla wygody definiowane jako staÅ‚e). OkreÅ›lajÄ… zakres jakiego dotyczyć bÄ™dzie zapytanie czyli czy dotyczy ono tylko obiektu zdefiniowanego w zapytaniu (na przykÅ‚ad jednego OU, obiektu), jednego poziomu czy tez caÅ‚ego poddrzewa poczynajÄ…c od wyspecyfikowanego obiektu (czyli na przykÅ‚ad caÅ‚ej gaÅ‚Ä™zi katalogu poczynajÄ…c od startowej domeny czy też OU). W naszym wypadku chcÄ…c przeszukać caÅ‚Ä… gałąź domeny użyjemy wartoÅ›ci 2 – zdefiniowanej jako staÅ‚a o nazwie ADS_SCOPE_SUBTREE
  • “Cache Results”: wartość logiczna wskazujÄ…ca czy wyniki zapytania majÄ… być zapamiÄ™tywane w “cache” po stronie klienta. DomyÅ›lnÄ… wartoÅ›ciÄ… jest “true” jednak przy wyszukiwaniu wiÄ™kszej liczby obiektów warto tÄ… opcje wyÅ‚Ä…czyć ustawiajÄ…c jej wartość na “false”.

objCommand.Properties("Page Size") = 1000
objCommand.Properties("Timeout") = 30
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.Properties("Cache Results") = False

Opis wszystkich parametrów obiektu połączenia można znaleźć na stronach MSDN.
Po ustawieniu wybranych parametrów połączenia należ skonstruować podstawową rzecz czyli zapytanie które skierujemy do katalogu. Zapytanie do katalogu w przypadku interfejsu ADO DB może być zdefiniowane w jednym z dwóch dialektów:

Umieszczamy je w parametrze “CommandText” obiektu polecenia jako Å‚aÅ„cuch znaków. W treÅ›ci tej specyfikujemy oprócz warunków zapytania również Å›cieżkÄ™ w katalogu zawierajÄ…cÄ… nazwÄ™ domeny itp. Jako że skrypty tworzymy przeważnie aby dziaÅ‚aÅ‚y w różnych Å›rodowiskach warto zadbać o to aby przy przeniesieniu naszego skryptu do innej domeny nie zachodziÅ‚a konieczność jego modyfikacji. W tym celu posÅ‚użymy siÄ™ wÅ‚aÅ›ciwoÅ›ciami zdefiniowanego w LDAP obiektu o nazwie rootDSE, który zawiera informacje o serwerze katalogu, miÄ™dzy innymi takie jak nazwa domeny katalogu zdefiniowana we wÅ‚aÅ›ciwoÅ›ci “defaultNamingContext” tego obiektu. Aby uzyskać do niej dostÄ™p pobierzemy obiekt rootDSE korzystajÄ…c z przestrzeni nazw LDAP:// interfejsu ADSI i odczytamy tÄ… wartość tej wÅ‚aÅ›ciwoÅ›ci:

Set rootDSE = GetObject("LDAP://rootDSE")
strDomain = rootDSE.Get("defaultNamingContext")

Powyższy fragment kodu zakłada że skrypt uruchamiany jest na uprawnieniach użytkownika posiadającego dostęp do serwera katalogu i nie wymaga dodatkowego uwierzytelnienia (opis połączenia z katalogiem w przypadku alternatywnego uwierzytelnia poprzez LDAP:// przedstawiony zostanie w dalszej części artykułu). Pełny opis obiektu rootDSE można znaleźć na stronie MSDN.

Dysponując nazwą domeny możemy przystąpić do konstruowania treści zapytania, jako pierwszy przykład posłużymy się zapytaniem pobierającym listę użytkowników z domeny, z nazwami kont logowania, nazwami DN oraz datą utworzenia konta. Zapytanie to w dialekcie SQL prezentuje się następująco:

strSQLStmt = "SELECT whenCreated, distinguishedName, Name, samAccountName FROM 'LDAP://" & strDomain & "' WHERE objectCategory='Person'"

To samo zapytanie w dialekcie LDAP przedstawia się następująco:

strSQLStmt= ";(objectCategory=person)(objectClass=user); dn, samAccountname, Name, WhenCreated"

Pozostaje nam jedynie przypisać treść zapytania do obiekt polecenia i wykonać to polecenie:

objCommand.CommandText = strSQLStmt
Set objResultRecordSet = objCommand.Execute

jako wynik polecenia otrzymujemy obiekt typu RecordSet który zawiera dane uzyskane z katalogu. Obiekt taki pozwala nam na przeglądanie zawartości zwróconych rekordów nawigując po zbiorze rekordów. Poniżej znajduje się przykład kodu który wyświetla na ekranie zawartość rekordów które otrzymaliśmy w wyniku przedstawionego powyżej zapytania:

If NOT objResultRecordSet.BOF and NOT objResultRecordSet.EOF Then

Do Until objResultRecordSet.EOF
Wscript.echo "Created - "& objResultRecordSet.Fields("whenCreated").Value &";Sam="& _
objResultRecordSet.Fields("samAccountName").Value &";Name="& _
objResultRecordSet.Fields("Name").Value & _
";DN="& objResultRecordSet.Fields("distinguishedName").Value

objResultRecordSet.MoveNext
Loop
Else
wscript.echo "Zdefiniowane zapytanie nie zwrocilo zadnych danych"
End If

Przedstawione powyżej przykÅ‚ady pokazywaÅ‚y jak podÅ‚Ä…czyć siÄ™ do domeny AD – wszystkie wykonywane zapytania bÄ™dÄ… odnosiÅ‚y siÄ™ do tej domeny lub wyspecyfikowanej w zapytaniu jej części. Jeżeli nasza struktura AD skÅ‚ada siÄ™ z wiÄ™cej niż jednej domeny i chcielibyÅ›my wykonać zapytanie obejmujÄ…ce dane z caÅ‚oÅ›ci naszego lasu konieczne jest wykorzystanie dodatkowego mnemonika okreÅ›lajÄ…cego źródÅ‚o danych jakim jest GC:// – pozwalajÄ…cy podÅ‚Ä…czyć siÄ™ do zasobów Katalogu Globalnego (Globa Catalog). W tym przypadku definicja zapytania wyglÄ…da nastÄ™pujÄ…co:

  • dla skÅ‚adni dialektu SQL:

strSQLStmt = "SELECT whenCreated, distinguishedName, Name, samAccountName FROM 'GC://" & strDomain & "' WHERE objectCategory='Person'"

  • dla skÅ‚adni dialektu LDAP:

strSQLStmt= ";(objectCategory=person)(objectClass=user); dn, samAccountname, Name, WhenCreated"

Aby pokazać, że dla każdego z dostÄ™pnych w ramach ADSI “dostawców” metody dostÄ™pu do danych przedstawiam poniżej “szkolny” przykÅ‚ad zastosowania interfejsu ADSI do dostÄ™pu do zasobów przestrzeni nazewniczej ADs:// – jak to zostaÅ‚o wspomniane na poczÄ…tku, umożliwia ona dostÄ™p do informacji o samym interfejsie ADSI. Poniżej przedstawiona zostaÅ‚a metoda wylistowania dostÄ™pnych w systemie “dostawców” dla interfejsu ADSI:

Set objProvider = GetObject("ADs:")
For Each Provider In objProvider
Wscript.Echo Provider.Name
Next

W wyniku działania tego skryptu w standardowej instalacji interfejsu ADSI uzyskamy poniższy wynik:

WinNT:
NWCOMPAT:
NDS:
LDAP:
IIS:
przedstawiający listę dostawców obecnych w systemie.

Modyfikacja danych obiektu

Przedstawione powyżej metody dostępu do katalogu i wybrania interesujących nas obiektów są przydatne nie tylko przy tworzeniu różnego rodzaju zestawień ale mogą również posłużyć do wybrania interesującego nas zakresu obiektów i zmodyfikowania odpowiedniego atrybutu tychże. Jako przykład takiego zastosowania przedstawiony zostanie poniżej skrypt modyfikujący ścieżkę do katalogu domowego użytkowników. Skorzystamy tutaj z dostępu do zawartości katalogu poprzez ADO DB w celu wybrania interesującego nas zakresu obiektów, a następnie z metody GetObject w celu podłączenia się do odpowiedniego obiektu i zmodyfikowania jego danych.
Zadanie to przeÅ›ledźmy na podstawie hipotetycznego zadania zmodyfikowania wartoÅ›ci parametru okreÅ›lajÄ…cego Å›cieżkÄ™ folderu domowego oraz litery dysku do której jest on mapowany przy logowaniu dla użytkowników w wybranym OU (na przykÅ‚ad o nazwie “Ksiegowosc”).

'w celu pewnej uniwersalizacji skryptu sciezke dostepu do OU (bez domeny)
'umiescimy w zmiennej - jej wartosc mozna przekazac jako parametr wywolania skryptu
strOUPath = "OU=Ksiegowosc"

Set rootDSE = GetObject("LDAP://rootDSE")
strDomain = rootDSE.Get("defaultNamingContext")

'okreslamy pelna sciezkke OU na ktorym chcemy wykonac operacje

strADPath = strOUPath & "," & strDomain

W celu wybrania odpowiednich danych wykonamy zapytanie do katalogu poprzez ADODB, wybierając z OU określonego utworzoną powyżej ścieżką obiekty użytkowników. Ponieważ w dalszej części skryptu będziemy odwoływać się do tych obiektów z zastosowaniem wartość która jest nam potrzebny to atrybut DN obiektu.

strSQLStmt = "SELECT samAccountName, distinguishedName FROM 'LDAP://" & strADPath & "' WHERE objectCategory='Person' and objectClass='user'"

Tak skonstruowane zapytanie do katalogu wykonujemy w sposób przedstawiony wcześniej, przy omawianiu dostępu do danych katalogu. Zmiana w stosunku do poprzedniego skryptu pojawia się w momencie iteracji po danych zawartych w zwróconym zestawie rekordów. W poprzednim przypadku po prostu listowaliśmy je na ekranie, teraz chcemy zrobić coś więcej czyli zmodyfikować pewne atrybuty tych obiektów. Wynikiem naszego zapytania są nazwy DN tychże obiektów, które posłużą nam jako ścieżka odwołania do nich. Dostęp do obiektu uzyskujemy poprzez wywołanie metody GetObject, która pobiera obiekt na podstawie podanej ścieżki dostępu (w naszym przypadku nazwy DN pobranej we wcześniejszym zapytaniu). Metoda ta próbuje uzyskać dostęp do obiektu na podstawie poświadczeń bezpieczeństwa użytkownika wywołującego skrypt.

'proba pobrania obiektu uzytkownika poprzez wartosc jego atrybutu DN
Set objUser = GetObject (objResultRecordSet.Fields("distinguishedName").Value)

'obsluga mozliwego bledu polaczenia
If Err.Number Then
Wscript.echo "blad dostepu do obiektu: " & & _
objResultRecordSet.Fields("distinguishedName").Value
Else

'tutaj znajdzie sie obsluga zmiany wartosci atrybutow obiektu

End If

W przypadku gdybyÅ›my chcieli uzyskać dostÄ™p do danych na podstawie alternatywnego zestawu poÅ›wiadczeÅ„, bÄ™dÄ…cych na przykÅ‚ad parametrem wywoÅ‚ania skryptu (co znacznie wpÅ‚ywa na jego uniwersalność) konieczne jest posÅ‚użenie siÄ™ innÄ… metodÄ… “OpenDSObject” (IADsOpenDSObject::OpenDSObject. Metoda ta pozwala na okreÅ›lenie danych uwierzytelnienia przy poÅ‚Ä…czeniu do katalogu. Powyższy fragment kodu przedstawia siÄ™ wtedy nastÄ™pujÄ…co:

'pobietramy obiekt typu IADsOpenDSObject
Set dsObject = GetObject("LDAP:")

'Podlaczenie sie do wybranego obiektu
objDomain = dsObject.OpenDSObject("LDAP://" & objResultRecordSet.Fields("distinguishedName").Value
,strUserName,strPass, ADS_SECURE_AUTHENTICATION)

'obsluga mozliwego bledu polaczenia
If Err.Number Then
Wscript.echo "Blad dostepu do obiektu: " & objResultRecordSet.Fields("distinguishedName").Value
Else

'tutaj znajdzie sie obsluga zmiany wartosci atrybutow obiektu

End If

Po uzyskaniu dostępu do obiektu możemy przystąpić do modyfikacji jego atrybutów. Modyfikacji tej dokonujemy poprzez wywołanie metody Put wywołanej w stosunku do pobranego obiektu:

With objUser
.Put "homeDrive", "Y:"
.Put "homeDirectory",strServerFS & "\Users\" & objResultRecordSet.Fields("samAccountName").Value
End With

W przypadku gdy wartość którą będziemy modyfikować będzie składała się z kilku oddzielnych wartości lub łańcuchów (na przykład kilku adresów e-mail przypisanych użytkownikowi w miejsce metody Put konieczne jest zastosowanie metody PutEx. Tak więc całość kodu iterującego po wynikach naszego zapytania i zmieniającego wartość wybranych przez nas parametrów przedstawia się następująco:

If NOT objResultRecordSet.BOF and NOT objResultRecordSet.EOF Then
Do Until objResultSet.EOF
Set objUser = GetObject (objResultRecordSet.Fields("distinguishedName").Value)
If Err.Number Then
Wscript.echo "Blad dostepu do obiektu: " & objResultRecordSet.Fields("distinguishedName").Value
Else
With objUser
.Put "homeDrive", "Y:"
.Put "homeDirectory","Server\Users\" & objResultRecordSet.Fields("samAccountName").Value
.SetInfo
End With
End If
objResultRecordSet.MoveNext
Loop
Else
wscript.echo "Zdefiniowane zapytanie nie zwrocilo zadnych danych"
End If

Ten sam efekt możemy uzyskać w inny sposób, poprzez pobranie obiektu OU z wykorzystaniem metody GetObject lub OpenDSObject, następnie zaś filtrując wszystkie inne obiekty niż obiekty użytkowników i wykonując dla nich określone zmiany. Realizacja tego zadania w skrypcie korzystając z tej metody wygląda następująco:

'tworzymy sciezke dostepu do OU w katalogu
strOUPath = "OU=Ksiegowowsc"
Set rootDSE = GetObject("LDAP://rootDSE")
strDomain = rootDSE.Get("defaultNamingContext")
strADPath = strOUPath & "," & strDomain

'Pobranie obiektu OU
Set objOU = GetObject("LDAP://" & strADPath )

'Nakladamy na wybrany obiekt filtr w celu uzyskania tylko interesujacych nas obiektow
objOU.Filter = Array("user")

For each objUser in objOU
'Dodatkowe sprawdzenie klasy obiektu
If objUser.Class = "user"Then
'Modyfikacja danych obiektu
objUser.Put "homeDrive", "Y:"

'ustalamy nazwe katalogu domowego na podstawie wartosci samAccountName pobranej z danych uzytkownika
objUser.Put "homeDirectory","\\Server\Users\" & objUser.Get ( "samAccountName")

'zapisujemy dane uzytkownika
objUser.SetInfo
End If
Next

wscript.quit 0

Więcej zasobów na WWW dotyczących interfejsu ADSI

Przykłady skryptów

Poniżej przedstawione zostały pełne kody skryptów które omawiane były w ramach tego artykułu:

Modyfikacja danych użytkownika: