воскресенье, 10 ноября 2013 г.

Как работать с LDAP в Python

Недавно я написал о том, что такое LDAP и с чем его едят. Сегодня, в продолжение этой темы, расскажу, как работать с LDAP сервером из программы на языке программирования Python. Для создания примеров я использую Python 2.7 и MS Active Directory. В примерах все имена организаций, подразделений и пользователей являются вымышленными, а всякое совпадение с реальностью является совершенно случайным :)

Для работы с LDAP из Python нам понадобится пакет python-ldap. Пакет позволяят работать с различными LDAP-серверами, и с Active Directory, в частности.

>>> import ldap
>>> ad = ldap.initialize("ldap://192.168.0.16")
>>> ad.simple_bind_s("trofimov_a@SKY", "nooneknows")
(97, [], 1, [])

По LDAP URL ldap://192.168.0.16, который указывает на Active Directory сервер, получен объект LDAPObject и присвоен переменной ad. Далее используем метод simple_bind_s этого объекта для присоединения к серверу, передавая имя и пароль пользователя для аутентификации. (Символы _s в конце имени метода означают, что метод выполняется синхронно.)

Аутентификация пользователей - одна из функций LDAP сервера, реализуемая операцией bind. Другие функции: поиск, чтение и модификация данных в каталоге.

Найдем и выведем на экран атрибут name всех подразделений (objectClass=organizationalUnits) верхнего уровня (ldap.SCOPE_ONELEVEL) для организации "Синее Небо" (O=Синее Небо,DC=org,DC=ru):

>>> basedn = 'O=Синее Небо,DC=org,DC=ru'
>>> scope = ldap.SCOPE_ONELEVEL
>>> filterexp = 'objectClass=organizationalUnit'
>>> attrlist = ['name']
>>> results = ad.search_s(basedn, scope, filterexp, attrlist)
>>> for result in results:
...     print result[0].decode('utf-8'), result[1]['name'][0].decode('utf-8')
... 
OU=Коммерческий отдел,O=Синее Небо,DC=org,DC=ru Коммерческий отдел
OU=Общее руководство,O=Синее Небо,DC=org,DC=ru Общее руководство
OU=Отдел обеспечения,O=Синее Небо,DC=org,DC=ru Отдел обеспечения
OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru Производственный отдел

Метод search_s возвращает результат поиска как список кортежей (DN записи, словарь с запрошенными атрибутами). А поскольку атрибут может иметь больше одного значения, то значения атрибутов представлены списком. В рассмотренном примере атрибут name имеет одно значение, то есть, список значений содержит единственный элемент result[1]['name'][0].

Значения, которые может принимать параметр scope (в соответствии со спецификацией LDAP):

  • ldap.SCOPE_BASE - запись base DN,
  • ldap.SCOPE_ONELEVEL - дочерние записи base DN,
  • ldap.SCOPE_SUBTREE - поддерево с вершиной base DN.

Найду запись о пользователе (objectClass=user), указав в списке атрибутов objectClass, - этот атрибут имеет несколько значений:

>>> basedn = 'O=Синее Небо,DC=org,DC=ru'
>>> scope = ldap.SCOPE_SUBTREE
>>> filterexp = "(&(cn=Трофимов Андрей*)(objectClass=user))"
>>> attrlist = ["sAMAccountName", "mail", "objectClass"]
>>> results = ad.search_s(basedn, scope, filterexp, attrlist)
>>> for result in results:
...    print 'DN'.rjust(15) + ' = ' + result[0].decode('utf-8')
...    print '\n'.join(x.rjust(15) + ' = ' + str(result[1][x]) for x in attrlist)
...
             DN = CN=Трофимов Андрей,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru
 sAMAccountName = ['trofimov_a']
           mail = ['trofimov_a@sineenebo.org.ru']
    objectClass = ['top', 'person', 'organizationalPerson', 'user']

Несколько значений атрибута objectClass отражают тот факт, что класс user явлется наследником классов organizationalPerson, person и top. Таким образом, запись класса user является одновременно записью каждого из классов-предков.

Атрибут sAMAccountName содержит имя пользователя, под которым пользователь регистрируется в корпоративном домене, атрибут mail - адрес электронной почты.

Следующий скрипт выводит на экран имя и адрес электронной почты всех активных пользователей организации "Синее Небо". Приблизительно то же самое делает адресная книга вашего почтового клиента, чтобы вывести доступных адресатов электронной почты.

# -*- coding: utf-8 -*-

import ldap

LDAP_URL = "ldap://192.168.0.16"
USERNAME = "trofimov_a@SKY"
PASSWORD = "nooneknows"

ad = ldap.initialize(LDAP_URL)
ad.simple_bind_s(USERNAME, PASSWORD)

basedn = "O=Синее Небо,DC=org,DC=ru"
scope = ldap.SCOPE_SUBTREE
filterexp = "(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(cn=*)(mail=*))"
attrlist = ["cn", "mail"]
results = ad.search_s(basedn, scope, filterexp, attrlist)

for result in results:
    print result[1]["cn"][0].decode("utf-8").rjust(35), result[1]["mail"][0]

ad.unbind_s()

Здесь в выражении фильтра расширенная проверка с атрибутом userAccountControl выбирает только пользователей с отключенной учетной записью (AccountDisabled), а ее отрицание, соответственно, выбирает активных пользователей. Выражение фильтра также требует наличия у записей атрибутов cn и mail.

В отличие от операции поиска, операции LDAP для изменения данных всегда выполняются ровно для одной записи в каталоге, идентифицированной ее уникальным именем DN.

Добавим новую запись класса organizationalPerson в Производственный отдел:

>>> dn = "CN=Обломов Илья,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru"
>>> addlist = [
...    ("cn", "Обломов Илья"),
...    ("sn", "Обломов"),
...    ("objectClass", ["organizationalPerson","person","top"]),
...    ("telephoneNumber", "2121212")
...    ]
>>> ad.add_s(dn, addlist)
(105, [])

Метод add_s объекта LDAPObject принимает в качестве параметров DN новой записи и список кортежей (атрибут, значение). Запись успешно добавлена. Для чтения записи создам функцию:

>>> def show_entry(dn):
...     try:
...         results = ad.search_s(dn, ldap.SCOPE_BASE)
...         print "DN".rjust(20), "=", results[0][0].decode("utf-8")
...         for attr in ["cn", "sn", "givenName", "telephoneNumber"]:
...             print attr.rjust(20), "=", (results[0][1][attr][0].decode("utf-8") if results[0][1].get(attr) else '')
...     except:
...         print ("Не найден DN %s" % dn).decode("utf-8")
... 
>>> show_entry(dn)
             DN = CN=Обломов Илья,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru
             cn = Обломов Илья
             sn = Обломов
      givenName = 
telephoneNumber = 2121212

Для изменения существующей записи нужно подготовить список модификаций:

>>> modlist = [
...     (ldap.MOD_ADD, "givenName", "Илья"),
...     (ldap.MOD_REPLACE, "sn", "Обломоff"),
...     (ldap.MOD_DELETE, "telephoneNumber", None)
...     ]

Первый элемент каждого кортежа в списке определяет тип модификации, одно из:

  • ldap.MOD_ADD - добавить атрибут
  • ldap.MOD_REPLACE - заменить атрибут
  • ldap.MOD_DELETE - удалить атрибут (или одно из его значений)

Изменяю запись и смотрю результат:

>>> ad.modify_s(dn, modlist)
(103, [])
>>> show_entry(dn)
             DN = CN=Обломов Илья,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru
             cn = Обломов Илья
             sn = Обломоff
      givenName = Илья
telephoneNumber = 

Метод rename_s объекта LDAPObject позволяет изменить RDN записи:

>>> ad.rename_s(dn, "cn=Обломов Илья Ильич")
(109, [], 10, [])

>>> show_entry(dn)
Не найден DN CN=Обломов Илья Ильич,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru

>>> dn = "CN=Обломов Илья Ильич,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru"
>>> show_entry(dn)
             DN = CN=Обломов Илья Ильич,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru
             cn = Обломов Илья Ильич
             sn = Обломоff
      givenName = Илья
telephoneNumber = 

Наконец, удаляю экспериментальную запись:

>>> ad.delete_s(dn)
(107, [], 12, [])
>>> show_entry(dn)
Не найден DN CN=Обломов Илья Ильич,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru

Закрываю соединение с Active Directory:

>>> ad.unbind_s()

Все примеры выше используют синхронные операции с LDAP. Объект LDAPObject предоставляет также асинхронные методы search, add, modify, rename, delete. Эти методы возвращают целочисленный идентификатор операции, который затем нужно передать методу result для получения результата операции, или методу abandon - для отмены операции.

Были рассмотрены базовые возможности, предоставлемые пакетом python-ldap, для чтения и изменения данных в LDAP каталоге.

2 комментария:

  1. Позвольте мне представить услуги LE-MERIDIAN FINANCING. кредитная компания, которая предоставляет мне кредит в размере 5 000 000,00 долларов США. Когда другие кредитные инвесторы игнорируют мое предложение, но Le_Meridian Funding Service предоставляет мне успешный кредит. Они непосредственно участвуют в финансировании кредита и проекте с точки зрения инвестиций. они предоставляют финансовые решения компаниям и частным лицам, желающим получить доступ к фондам на рынках капитала, они могут помочь вам профинансировать ваш проект или расширить ваш бизнес. Адрес электронной почты: :::: lfdsloans@lemeridianfds.com Также lfdsloans@outlook.com или напишите на WhatsApp Number 1- (989-394-3740)
    Доброе намерение,

    ОтветитьУдалить