В первой части статьи я начал рассматривать работу с Active Directory сервером на языке PL/SQL с использованием пакета dbms_ldap
. Были разобраны аутентификация, операция поиска, и написаны хранимые функция и процедуры ldap_open
, ldap_print
и ldap_search_and_print
для облегчения дальнейшей работы. В данной статье будут рассмотрены операции модификации данных в LDAP каталоге, и попутно создан пакет ldap_helper
на базе ранее написанных и новых PL/SQL функций и процедур.
Начнем с создания пакета ldap_helper
, поместив в него, помимо уже упомянутых, новую функцию to_strcol
для преобразования VARCHAR2 списка с разделителем в таблицу dbms_ldap.STRING_COLLECTION
. Эта функция существенно упростит задание списка атрибутов для поиска:
CREATE OR REPLACE PACKAGE ldap_helper IS
-- Разобрать и вывести результат поиска
PROCEDURE print(
p_session dbms_ldap.SESSION,
p_results dbms_ldap.MESSAGE);
-- Преобразовать VARCHAR2 список с разделителм в dbms_ldap.STRING_COLLECTION
FUNCTION to_strcol(
p_list IN VARCHAR2,
p_separator IN VARCHAR2 DEFAULT ',')
RETURN dbms_ldap.STRING_COLLECTION;
-- открыть сеанс работы с LDAP сервером
FUNCTION ldap_open
RETURN dbms_ldap.SESSION;
-- Выполнить поиск
FUNCTION search(
p_session dbms_ldap.SESSION,
p_base IN VARCHAR2,
p_scope IN PLS_INTEGER,
p_filter IN VARCHAR2,
p_attrs IN VARCHAR2)
RETURN dbms_ldap.MESSAGE;
-- Выполнить поиск и вывести результат поиска
PROCEDURE search_and_print(
p_session dbms_ldap.SESSION,
p_base IN VARCHAR2,
p_scope IN PLS_INTEGER,
p_filter IN VARCHAR2,
p_attrs IN VARCHAR2);
END ldap_helper;
/
CREATE OR REPLACE PACKAGE BODY ldap_helper IS
-- Разобрать и вывести результат поиска
PROCEDURE print(
p_session dbms_ldap.SESSION,
p_results dbms_ldap.MESSAGE)
IS
l_entry dbms_ldap.MESSAGE;
l_attr VARCHAR2(256);
l_values dbms_ldap.STRING_COLLECTION;
l_berelem dbms_ldap.ber_element;
i PLS_INTEGER;
BEGIN
l_entry := dbms_ldap.first_entry(p_session, p_results);
WHILE l_entry IS NOT NULL LOOP
dbms_output.put_line('DN = ' || dbms_ldap.get_dn(p_session, l_entry));
l_attr := dbms_ldap.first_attribute(p_session, l_entry, l_berelem);
WHILE l_attr IS NOT NULL LOOP
l_values := dbms_ldap.get_values(p_session, l_entry, l_attr);
i := l_values.first;
WHILE i IS NOT NULL LOOP
dbms_output.put_line(LPAD(l_attr, 15) || ' = ' || l_values(i));
i := l_values.next(i);
END LOOP;
l_attr := dbms_ldap.next_attribute(p_session, l_entry, l_berelem);
END LOOP;
l_entry := dbms_ldap.next_entry(p_session, l_entry);
END LOOP;
END print;
-- Преобразовать VARCHAR2 список с разделителм в dbms_ldap.STRING_COLLECTION
FUNCTION to_strcol(
p_list IN VARCHAR2,
p_separator IN VARCHAR2 DEFAULT ',')
RETURN dbms_ldap.STRING_COLLECTION
IS
i PLS_INTEGER := 0;
beg PLS_INTEGER := 1;
fin PLS_INTEGER := 0;
strcol dbms_ldap.STRING_COLLECTION;
BEGIN
IF p_list IS NULL OR p_separator IS NULL THEN
RETURN strcol;
END IF;
fin := instr(p_list, p_separator, beg);
WHILE fin > 0 LOOP
strcol(i) := TRIM(substr(p_list, beg, fin - beg));
beg := fin + length(p_separator);
fin := instr(p_list, p_separator, beg);
i := i + 1;
END LOOP;
strcol(i) := TRIM(substr(p_list, beg));
RETURN strcol;
END to_strcol;
-- открыть сеанс работы с LDAP сервером
FUNCTION ldap_open
RETURN dbms_ldap.SESSION
IS
LDAP_HOST CONSTANT VARCHAR2(20) := '192.168.0.16';
LDAP_PORT CONSTANT VARCHAR2(20) := dbms_ldap.PORT;
LDAP_USER CONSTANT VARCHAR2(20) := 'trofimov_a@SKY';
LDAP_PSWD CONSTANT VARCHAR2(20) := 'nooneknows';
l_dummy PLS_INTEGER;
l_session dbms_ldap.SESSION;
BEGIN
l_session := dbms_ldap.init(LDAP_HOST, LDAP_PORT);
l_dummy := dbms_ldap.simple_bind_s(l_session, LDAP_USER, LDAP_PSWD);
RETURN l_session;
END ldap_open;
-- Выполнить поиск
FUNCTION search(
p_session dbms_ldap.SESSION,
p_base IN VARCHAR2,
p_scope IN PLS_INTEGER,
p_filter IN VARCHAR2,
p_attrs IN VARCHAR2)
RETURN dbms_ldap.MESSAGE
IS
l_dummy PLS_INTEGER;
l_results dbms_ldap.MESSAGE;
BEGIN
l_dummy := dbms_ldap.search_s(
p_session,
p_base,
p_scope,
p_filter,
to_strcol(p_attrs),
0,
l_results
);
RETURN l_results;
END search;
-- Выполнить поиск и вывести результат поиска
PROCEDURE search_and_print(
p_session dbms_ldap.SESSION,
p_base IN VARCHAR2,
p_scope IN PLS_INTEGER,
p_filter IN VARCHAR2,
p_attrs IN VARCHAR2)
IS
l_dummy PLS_INTEGER;
l_results dbms_ldap.MESSAGE;
BEGIN
print(p_session, search(p_session, p_base, p_scope, p_filter, p_attrs));
EXCEPTION
WHEN dbms_ldap.invalid_search_scope OR dbms_ldap.general_error THEN
dbms_output.put_line('Не найдена запись с DN ' || p_base);
END search_and_print;
END ldap_helper;
/
В отличие от операции поиска, операции LDAP для изменения данных всегда выполняются ровно для одной записи в каталоге, идентифицированной ее уникальным именем DN.
Добавим новую запись класса organizationalPerson
в Производственный отдел. Функция add_s
пакета dbms_ldap
принимает в качестве параметров DN новой записи и массив атрибутов и их значений. Этот массив вначале нужно подготовить, для чего предназначены функция dbms_ldap.create_mod_array
и процедура dbms_ldap.populate_mod_array
. Обратите внимание, как пригодилась в данном случае новая функция ldap_helper.to_strcol
для подготовки массива значений атрибутов.
-- Добавим новую запись в Active Directory
DECLARE
l_sess dbms_ldap.session;
l_dummy PLS_INTEGER;
l_dn VARCHAR2(200);
l_modarray dbms_ldap.MOD_ARRAY;
BEGIN
l_sess := ldap_helper.ldap_open;
-- подготовить данные для добавления сотрудника
l_dn := 'CN=Обломов Илья,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru';
l_modarray := dbms_ldap.create_mod_array(4);
dbms_ldap.populate_mod_array(
l_modarray,
dbms_ldap.MOD_ADD,
'cn',
ldap_helper.to_strcol('Обломов Илья'));
dbms_ldap.populate_mod_array(
l_modarray,
dbms_ldap.MOD_ADD,
'sn',
ldap_helper.to_strcol('Обломов'));
dbms_ldap.populate_mod_array(
l_modarray,
dbms_ldap.MOD_ADD,
'objectClass',
ldap_helper.to_strcol('organizationalPerson, person, top'));
dbms_ldap.populate_mod_array(
l_modarray,
dbms_ldap.MOD_ADD,
'telephoneNumber',
ldap_helper.to_strcol('2121212'));
-- добавить сотрудника
l_dummy := dbms_ldap.add_s(l_sess, l_dn, l_modarray);
-- что получилось?
ldap_helper.search_and_print(
p_session => l_sess,
p_base => l_dn,
p_scope => dbms_ldap.SCOPE_BASE,
p_filter => '(objectClass=*)',
p_attrs => 'cn, givenName, sn, objectClass, telephoneNumber'
);
l_dummy := dbms_ldap.unbind_s(l_sess);
END;
/
DN = CN=Обломов Илья,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru
objectClass = top
objectClass = person
objectClass = organizationalPerson
cn = Обломов Илья
sn = Обломов
telephoneNumber = 2121212
Новая запись была успешно добавлена в каталог, затем найдена в нем и выведена на экран.
Для изменения существующей записи нужно подготовить массив модификаций с помощью тех же dbms_ldap.create_mod_array
и dbms_ldap.populate_mod_array
. Режим модификации для каждого из атрибутов - одно из:
dbms_ldap.MOD_ADD
- добавить атрибутdbms_ldap.MOD_REPLACE
- заменить атрибутdbms_ldap.MOD_DELETE
- удалить атрибут (или одно из его значений)
Изменяю запись и смотрю результат:
DECLARE
l_sess dbms_ldap.session;
l_dummy PLS_INTEGER;
l_dn VARCHAR2(200);
l_modarray dbms_ldap.MOD_ARRAY;
BEGIN
l_sess := ldap_helper.ldap_open;
-- подготовить данные для модификации сотрудника
l_dn := 'CN=Обломов Илья,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru';
l_modarray := dbms_ldap.create_mod_array(3);
dbms_ldap.populate_mod_array(
l_modarray,
dbms_ldap.MOD_ADD,
'givenName',
ldap_helper.to_strcol('Илья'));
dbms_ldap.populate_mod_array(
l_modarray,
dbms_ldap.MOD_REPLACE,
'sn',
ldap_helper.to_strcol('Обломоff'));
dbms_ldap.populate_mod_array(
l_modarray,
dbms_ldap.MOD_DELETE,
'telephoneNumber',
ldap_helper.to_strcol(NULL, NULL));
-- модифицировать сотрудника
l_dummy := dbms_ldap.modify_s(l_sess, l_dn, l_modarray);
-- что получилось?
ldap_helper.search_and_print(
p_session => l_sess,
p_base => l_dn,
p_scope => dbms_ldap.SCOPE_BASE,
p_filter => '(objectClass=*)',
p_attrs => 'cn, givenName, sn, objectClass, telephoneNumber'
);
l_dummy := dbms_ldap.unbind_s(l_sess);
END;
/
DN = CN=Обломов Илья,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru
objectClass = top
objectClass = person
objectClass = organizationalPerson
cn = Обломов Илья
sn = Обломоff
givenName = Илья
Функция dbms_ldap.rename_s
позволяет изменить RDN записи:
-- Переименовать (изменить RDN)
DECLARE
l_sess dbms_ldap.session;
l_dummy PLS_INTEGER;
l_dn VARCHAR2(200);
BEGIN
l_sess := ldap_helper.ldap_open;
-- Изменить RDN
l_dn := 'CN=Обломов Илья,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru';
l_dummy := dbms_ldap.rename_s(
ld => l_sess,
dn => l_dn,
newrdn => 'cn=Обломов Илья Ильич',
newparent => 'OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru',
deleteoldrdn => 1);
-- что получилось?
ldap_helper.search_and_print(
p_session => l_sess,
p_base => l_dn,
p_scope => dbms_ldap.SCOPE_BASE,
p_filter => '(objectClass=*)',
p_attrs => 'cn, givenName, sn, objectClass, telephoneNumber'
);
ldap_helper.search_and_print(
p_session => l_sess,
p_base => 'CN=Обломов Илья Ильич,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru',
p_scope => dbms_ldap.SCOPE_BASE,
p_filter => '(objectClass=*)',
p_attrs => 'cn, givenName, sn, objectClass, telephoneNumber'
);
l_dummy := dbms_ldap.unbind_s(l_sess);
END;
/
Не найдена запись с DN CN=Обломов Илья,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru
DN = CN=Обломов Илья Ильич,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru
objectClass = top
objectClass = person
objectClass = organizationalPerson
cn = Обломов Илья Ильич
sn = Обломоff
givenName = Илья
Здесь первый вызов ldap_helper.search_and_print
не нашел запись по прежнему DN, а второй вызов, с новым DN, нашел и вывел запись на экран.
Наконец, удаляю экспериментальную запись с помощью функции dbms_ldap.delete_s
:
-- Удалить запись
DECLARE
l_sess dbms_ldap.session;
l_dummy PLS_INTEGER;
l_dn VARCHAR2(200);
BEGIN
l_sess := ldap_helper.ldap_open;
-- Изменить RDN
l_dn := 'CN=Обломов Илья Ильич,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru';
l_dummy := dbms_ldap.delete_s(l_sess, l_dn);
-- что получилось?
ldap_helper.search_and_print(
p_session => l_sess,
p_base => l_dn,
p_scope => dbms_ldap.SCOPE_BASE,
p_filter => '(objectClass=*)',
p_attrs => 'cn, givenName, sn, objectClass, telephoneNumber'
);
l_dummy := dbms_ldap.unbind_s(l_sess);
END;
/
Не найдена запись с DN CN=Обломов Илья Ильич,OU=Производственный отдел,O=Синее Небо,DC=org,DC=ru
Все примеры выше используют синхронные операции с LDAP. Пакет dbms_ldap
не предоставляет асинхронных функций.
Что касается пакета ldap_helper
- фасада для dbms_ldap
, - то чтобы сделать его еще полезнее, можно предложить следующие доработки:
- Добавить опциональные параметры
p_host
,p_port
,p_user
,p_pswd
дляldap_open
. Тогда можно будет присоединяться к любому LDAP серверу. И добавить для симметрииldap_close
- обертку дляdbms_ldap.unbind_s
. - Попробовать запоминать открытый сеанс в переменной пакета и повторно его использовать, вместо открытия нового сенса каждый раз.
- Создать функцию для возврата результата поиска как таблица таблиц таблиц (в смысле PL/SQL). Тогда значения атрибутов извлеченных записей можно будет получить как
result(0)('name')(0)
, где первый 0 выбирает первую запись, 'name' выбирает атрибут, а последний 0 выбирает значение. - Создать функцию для подготовки массива модификаций
dbms_ldap.MOD_ARRAY
из таблицы записей PL/SQL вида (режим модификации, имя атрибута, список значений). И добавить процедурыadd
иmodify
, принимающие на вход массив модификаций в виде такой таблицы записей.
добрый день, наткнулся на Ваш пост, у меня следующий вопрос по своему заданию, как можно получить objectGUID? когда я выбираю значение стандартными средставми возвращается строка в таком виде-
ОтветитьУдалить? ??K? Q8s?qp
- как ее преобразовать или правильно получить средствами dbms_ldap?
Добрый день, Ришат.
ОтветитьУдалитьАтрибут objectGUID бинарный. Для получения значений бинарных атрибутов есть функция dbms_ldap.get_values_len, подобная dbms_ldap.get_values, но возвращающая dbms_ldap.BINVAL_COLLECTION вместо dbms_ldap.STRING_COLLECTION. Если воспользоваться ей, то objectGUID выводится как строка, представлющая 16 байт в шестнадцатеричной записи, например: 29B6E8BA75AE5443A65CAC8BDEF9CFF4.
Дальше можно преобразовать ее к нужному виду. Если ее разбить на части по 4-2-2-2-6 байт, в первых трех частях изменить порядок байт на противоположный, то получится то самое каноническое представление, которое берут в фигурные скобки: {BAE8B629-AE75-4354-A65C-AC8BDEF9CFF4}.
Добрый время суток.
ОтветитьУдалитьМожно ли создать структуру DC/O/OU (objectclass=organizationalUnit) c помощью пакета dbms_ldap?
Добрый день
ОтветитьУдалитьМожно, аналогично тому, как в приведенном мной примере создана запись organizationalPerson. Если нужно создать всю иерархию DC/O/OU, то последовательно: сначала DC, затем O, затем OU.
Здравствуйте!
ОтветитьУдалитьА как добавить пользователя в определенную группу?
Добрый день! Не решал такую задачу, так что не подскажу.
ОтветитьУдалитьСпасибо за публикацию!
ОтветитьУдалить