Среда, 17 февраля 2021 19:00

Добавляем записи в каталог LDAP. Работа с LDAP в Qt5. Часть 6.

Россия
Оцените материал
(0 голосов)

Сегодня мы рассмотрим добавление записей в каталог LDAP в Qt5 с помощью библиотеки libldap (OpenLDAP).

Мы будем использовать проект из предыдущей статьи.

Делегируем права на добавление пользователей

Прежде чем добавлять пользователя в Active Directory, нам нужно предоставить все необходимые права пользователю, которого мы используем для подключения к каталогу LDAP - ldap-bind

Наша задача предоставить ему права на создание пользователей только в OU=Company,DC=altuninvv,DC=local

Обратите внимание, несмотря на то, что мы будем добавлять учетные записи, я не рекомендую делать это через LDAP – это небезопасно, если вам необходимо хранить телефонный справочник в каталоге LDAP используйте Contact, но об этом мы поговорим в будущих статьях!

Проще всего настроить права через оснастку Пользователи и компьютеры Active Directory

Откройте свойства OU=Company,DC=altuninvv,DC=local

2021-01-28_17-23-20.png

Откройте Безопасность и нажмите Add, добавьте ldap-bind

Нажмите «Дополнительно»

2021-01-28_17-26-39.png

Найдите в списке ldap-bind выберите его и нажмите Изменить

2021-01-28_17-28-19.png

Установите галочки напротив

  • Создание объектов: user (Create user objects)
  • Создание объектов: account (Create account objects)
  • Создание объектов: InetOrgPerson (Create InetOrgPerson objects)

Далее нажимайте Ок, чтобы подтвердить изменения.

Теперь пользователь имеет права на создание новых пользователей в OU=Company,DC=altuninvv,DC=local

Модернизируем класс QLdapUser

Добавим в класс QLdapUser сеттеры, которые помогут нам заполнить данными пользователя QLdapEntry.

Заголовок:

    void setUserValue(const QString &key, const QLdapEntryValues &value) const;

    void setDisplayName(const QString &value) const;

    void setGivenName(const QString &value) const;

    void setInitials(const QString &value) const;

    void setOfficePhone(const QString &value) const;

    void setDepartment(const QString &value) const;

    void setTitle(const QString &value) const;

    void setUserPrincipalName(const QString &value) const;

    void setMobilePhone(const QString &value) const;

    void setSamAccountName(const QString &value) const;

    void setPath(const QString &value) const;

    void setEmail(const QString &value) const;

    void setStreetAddress(const QString &value) const;

    void setOffice(const QString &value) const;

    void setCompany(const QString &value) const;

    void setFax(const QString &value) const;

    void setName(const QString &value) const;

Реализация:

void QLdapUser::setUserValue(const QString &key, const QLdapEntryValues &value) const
{
        (*this->user)[key] = value;
}

void QLdapUser::setDisplayName(const QString &value) const
{

    this->setUserValue("displayName",QStringList(value));
}

void QLdapUser::setGivenName(const QString &value) const
{
    this->setUserValue("givenName",QStringList(value));
}

void QLdapUser::setInitials(const QString &value) const
{
    this->setUserValue("initials",QStringList(value));
}

void QLdapUser::setOfficePhone(const QString &value) const
{
    this->setUserValue("otherTelephone",QStringList(value));
}

void QLdapUser::setDepartment(const QString &value) const
{
    this->setUserValue("department",QStringList(value));
}

void QLdapUser::setTitle(const QString &value) const
{
    this->setUserValue("title",QStringList(value));
}

void QLdapUser::setUserPrincipalName(const QString &value) const
{
    this->setUserValue("userPrincipalName",QStringList(value));
}

void QLdapUser::setMobilePhone(const QString &value) const
{
    this->setUserValue("mobile",QStringList(value));
}

void QLdapUser::setSamAccountName(const QString &value) const
{
    this->setUserValue("sAMAccountName",QStringList(value));
}

void QLdapUser::setPath(const QString &value) const
{
    this->setUserValue("distinguishedName",QStringList(value));
}

void QLdapUser::setEmail(const QString &value) const
{
    this->setUserValue("mail",QStringList(value));
}

void QLdapUser::setStreetAddress(const QString &value) const
{
    this->setUserValue("streetAddress",QStringList(value));
}

void QLdapUser::setOffice(const QString &value) const
{
    this->setUserValue("physicalDeliveryOfficeName",QStringList(value));
}

void QLdapUser::setCompany(const QString &value) const
{
    this->setUserValue("company",QStringList(value));
}

void QLdapUser::setFax(const QString &value) const
{
    this->setUserValue("facsimileTelephoneNumber",QStringList(value));
}

void QLdapUser::setName(const QString &value) const
{
    this->setUserValue("name",QStringList(value));
}

 Очистим конструктор главной формы от лишнего кода, оставив только инициализацию и подключение.

Добавим в конструктор главной формы код: 

    QLdapEntry *user = new QLdapEntry();
    QLdapUser u = QLdapUser(user);

    u.setDisplayName("Иванов Иван Иванович");

    u.setTitle("Бухгалтер");

    u.setGivenName("Иванов И. И.");

    u.setName("Иванов И. И.");

    u.setInitials("И.");
    u.setDepartment("Бухгалтерия");
    u.setEmail("Этот адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript.");
    u.setStreetAddress("Ленина 1");
    u.setOffice("104");
    u.setCompany("Altunin Soft");

    u.setOfficePhone("+7(495)12300024");
    u.setMobilePhone("+7(495)876-16-18");
    u.setFax("");
    u.setUserValue("objectClass",QStringList({"user","person","top","organizationalPerson"}));


    qDebug() << u;
    qDebug() << user->values();
    qDebug() << user->keys();

Запустим:

QLdapUser( displayName: "Иванов Иван Иванович", initials: "И.", officePhone: "+7(495)12300024", department: "Бухгалтерия", title: "Бухгалтер", userPrincipalName: "", cellPhone: "+7(495)876-16-18", samAccountName: "", path: "", email: "Этот адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript.", streetAddress: "Ленина 1", office: "104", company: "Altunin Soft", fax: "" )

(("Иванов Иван Иванович"), ("И."), ("Ленина 1"), ("Иванов И. И."), ("Altunin Soft"), ("Этот адрес электронной почты защищен от спам-ботов. Для просмотра адреса в вашем браузере должен быть включен Javascript."), ("104"), ("+7(495)12300024"), ("+7(495)876-16-18"), ("Иванов И. И."), ("Бухгалтерия"), ("user", "person", "top", "organizationalPerson"), (), (), ("Бухгалтер"), (""), ())

("displayName", "initials", "streetAddress", "name", "company", "mail", "physicalDeliveryOfficeName", "otherTelephone", "mobile", "givenName", "department", "objectClass", "sAMAccountName", "distinguishedName", "title", "facsimileTelephoneNumber", "userPrincipalName")

Структура данных LDAPMod

Для того чтобы добавить запись в LDAP мы должны представить её в виде понятном функции ldap_add_ext_s().

В качестве аргумента эта функция принимает:

LDAPMod **mods;

Это обычный двумерный массив на указателях.

Для упрощения выделения памяти и дальнейшей работы напишем класс, который сделает за нас всю работу по выделению и корректному освобождению памяти – QLdapMod

Заголовок: 

#ifndef QLDAPMOD_H
#define QLDAPMOD_H

#include "qldap.h"
#include "QDebug"


typedef LDAPMod *LdapMod;

class QLdapMod
{
public:
    explicit QLdapMod(QLdapEntry *entry);
    ~QLdapMod();
    LDAPMod **getMods();

private:
    LDAPMod **mods;
    QLdapEntry *entry;
};

#endif // QLDAPMOD_H

Реализация:

#include "qldapmod.h"
#include "qstr.h"

QLdapMod::QLdapMod(QLdapEntry *entry)
{
    this->entry = entry;

    int nonEmptyRecords = 0;
    for(QLdapEntry::iterator i=this->entry->begin();i!=this->entry->end();++i)
    {
        if (!i.value().join(",").isEmpty())
        {
            nonEmptyRecords++;
        }

    }

    mods = (LDAPMod**) malloc( (nonEmptyRecords+1) * sizeof(LDAPMod*) );

    int j = 0;
    for(QLdapEntry::iterator i=this->entry->begin();i!=this->entry->end();++i)
    {
        if (!i.value().join(",").isEmpty())
        {
            if ((mods[j]=(LDAPMod *) malloc(sizeof(LDAPMod))) == NULL )
            {
                fprintf( stderr, "Cannot allocate memory for mods element\n" );
                exit( 1 );
            }
            j++;
        }
    }


}

QLdapMod::~QLdapMod()
{
    int j = 0;
    for(QLdapEntry::iterator i=this->entry->begin();i!=this->entry->end();++i)
    {
        if (!i.value().join(",").isEmpty())
        {
            free(mods[j]->mod_type);
            free(mods[j]->mod_vals.modv_strvals);
            free(mods[j]);
            j++;
        }

    }
    free(mods);
}

void setCharArrayFromQstringList(const QStringList &value, char **c)
{
    for (int i=0; i < value.count(); i++)
    {
        c[i] = _strdup(value[i].toStdString().c_str());
    }

    c[value.count()] = NULL;
}

LDAPMod **QLdapMod::getMods()
{
    qDebug() << "GetMods";



    int j = 0;
    for(QLdapEntry::iterator i=this->entry->begin();i!=this->entry->end();++i)
    {
        if (!i.value().join(",").isEmpty())
        {
            mods[j]->mod_op = LDAP_MOD_ADD;

            mods[j]->mod_type = _strdup(i.key().toStdString().c_str());

            mods[j]->mod_values = new char*[i.value().count()+1];

            setCharArrayFromQstringList(i.value(),mods[j]->mod_values);
            j++;
        }
    }

    mods[j] = NULL;

    return mods;
}

Рассмотрим данный класс.

Тип LDAPMod

Чтобы упростить себе жизнь, мы объявляем новый тип – LDAPMod.

В поле класса mods у нас будут храниться данные, готовые к отправке на сервер.

В поле entry мы сохраняем указатель на источник данных – QldapEntry.

Конструктор QLdapMod::QLdapMod

Рассмотрим конструктор - QLdapMod::QLdapMod(QLdapEntry *entry)

У вас может сложиться такая ситуация, когда атрибуты в записи объявлены, но им не присвоено значение, в этом случае нет смысла передавать такие данные на LDAP-сервер. Поэтому сначала мы проверяем сколько у нас не пустых записей: 

    int nonEmptyRecords = 0;
    for(QLdapEntry::iterator i=this->entry->begin();i!=this->entry->end();++i)
    {
        if (!i.value().join(",").isEmpty())
        {
            nonEmptyRecords++;
        }

    }

Как только нам стало известно точное количество не пустых записей, мы выделяем память:

mods = (LDAPMod**) malloc( (nonEmptyRecords+1) * sizeof(LDAPMod*) );

 

Обратите внимание, мы выделяем память для nonEmptyRecords+1 записей, так как последняя запись обязательно должна быть равна NULL – что служит признаком окончания списка.

 Далее мы просто выделяем память для всех атрибутов: 

    int j = 0;
    for(QLdapEntry::iterator i=this->entry->begin();i!=this->entry->end();++i)
    {
        if (!i.value().join(",").isEmpty())
        {
            if ((mods[j]=(LDAPMod *) malloc(sizeof(LDAPMod))) == NULL )
            {
                fprintf( stderr, "Cannot allocate memory for mods element\n" );
                exit( 1 );
            }
            j++;
        }
    }

 Деструктор класса

Здесь все просто - мы освобождаем память, но в обратном порядке, тому, при котором её выделяли:

free(mods[j]->mod_type);
free(mods[j]->mod_vals.modv_strvals);
free(mods[j]);

mods[j] содержит два указателя - mod_type и modv_strvals, для которых мы позже будем выделять память, поэтому мы должны освободить эту выделенную память, а только потом освобождать её у mods[j]. Если этого не сделать, возможны утечки памяти и непредсказуемое поведение программы.

Если вы сейчас закомментируете строчки:

free(mods[j]->mod_type);
free(mods[j]->mod_vals.modv_strvals);

Программа отработает без ошибок, но это не значит, что, когда дело дойдет до серьезного проекта, не возникнут проблемы.

Существует функция ldap_mods_free(), освобождающая память, но лучше освободить всё самому.

Вспомогательный метод setCharArrayFromQstringList

void setCharArrayFromQstringList(const QStringList &value, char **c)
{
    for (int i=0; i < value.count(); i++)
    {
        c[i] = _strdup(value[i].toStdString().c_str());
    }

    c[value.count()] = NULL;
}

Основная его задача состоит в том, чтобы преобразовать QStringList в массив строк char **c.

Так как в LDAP каждый атрибут может иметь несколько значений, например несколько телефонов, для хранения используется массив char **c.

Метод setCharArrayFromQstringList проходит по всем элементам QStringList и для каждого создает указатель на новый элемент массива char **c.

Для этого используется функция _strdup()

По сути код этой функции аналогичен:

__strdup (const char *s)
{
  size_t len = strlen (s) + 1;
  void *new = malloc (len);
  if (new == NULL)
    return NULL;
  return (char *) memcpy (new, s, len);
}

Но мы будем использовать готовый вариант.

Функция просто выделяет память для строки и возвращает указатель на созданную копию данных.

Обратите внимание на строку

c[value.count()] = NULL;

Она обязательна! Последний элемент массива всегда должен быть равен NULL!

Метод getMods()

Возвращает массив данных готовый к отправке на сервер.

Именно здесь мы проходим по всем элементам entry и создаем новые элементы массива mods.

Обратите внимание, существуют несколько операций при работе с LDAP:

  • LDAP_MOD_ADD – добавление значений
  • LDAP_MOD_REPLACE - обновление значений
  • LDAP_MOD_DELETE –удаление значений

Сегодня мы будем использовать LDAP_MOD_ADD

Так же вы можете добавить двоичные данные в LDAP, используя:

LDAP_MOD_ADD | LDAP_MOD_BVALUES

Но об этом мы поговорим в одной из следующих статей.

Строкой

mods[j]->mod_op = LDAP_MOD_ADD;

 мы задаем режим работы.

Строка

mods[j]->mod_type = _strdup(i.key().toStdString().c_str());

Задает имя атрибута для добавления.

Строка:

mods[j]->mod_values = new char*[i.value().count()+1];

выделяет память под массив строк – значений атрибута, а затем этот массив заполняется данными с помощью вспомогательного метода setCharArrayFromQstringList.

Цикл повторяется для всех непустых атрибутов.

Обратите внимание, в самом конце мы обязательно добавляем NULL как последний элемент массива:

mods[j] = NULL;

Это обязательное требование!

И, наконец мы возвращаем указатель на наши данные.

Добавление записи в каталог LDAP

Мы подготовили данные для отправки на LDAP сервер, пришло время написать код, для работы с ним:

Добавим метод в класс QLdap:

Заголовок:

int QLdap::add(const QString &baseDN, QLdapEntry *entry, LDAPMod **mods)

Реализация:

int QLdap::add(const QString &baseDN, QLdapEntry *entry, LDAPMod **mods)
{
    int result ;

    QString newdns = "CN=" + entry->value("name")[0] + "," + baseDN; //OU=Company,DC=altuninvv,DC=local";

    qDebug() << "new dn = " << newdns;

    char *newDN = _strdup(newdns.toStdString().c_str());

    result = ldap_add_ext_s( this->ldp, newDN, mods, NULL, NULL );

    free(newDN);

    qDebug() << "Added";

    return result;
}

Здесь всё просто, для начала мы создаем строку DN для нового пользователя. DN должен начинаться с ФИО пользователя, например:

CN=Иванов И. И.,OU=Company,DC=altuninvv,DC=local

После этого мы вызываем функцию ldap_add_ext_s и возвращаем результат выполнения.

Используем созданный метод:

QLdapMod *mod = new QLdapMod(user);

LDAPMod **m = mod->getMods();

result = ldap->add("OU=Company,DC=altuninvv,DC=local", user, m);

delete mod;

    if ( result != LDAP_SUCCESS )
    {
        QString msg = QString("QLDAP add() error: ") + QString(ldap_err2string(result));
        qDebug("%s",msg.toLatin1().constData());
        //return;
    }

Запустим:

GetMods

new dn =  "CN=Иванов И. И.,OU=Company,DC=altuninvv,DC=local"

Added

Close result = Success

Если мы запустим второй раз, то результат будет немного другим:

GetMods
new dn =  "CN=Иванов И. И.1,OU=Company,DC=altuninvv,DC=local"
Added
QLDAP add() error: Already exists
Close result =  Success

Поэтому вы сами обязательно должны обрабатывать ответ от LDAP-сервера.

Таким образом мы успешно добавили пользователя в каталог LDAP.

Заключение

Сегодня мы рассмотрели добавление пользователя в LDAP-каталог.

Предоставили пользователю ldap-bind доступ на создание пользователей в AD.

Доработали класс QLdapUser добавив дополнительные поля.

Создали класс QLdapMod для перевода данных в формат, подходящий для отправки серверу LDAP.

Добавили в класс QLdap метод для добавления записей в LDAP-каталог.

Скачать исходный код проекта вы можете на Github.

Прочитано 93 раз Последнее изменение Среда, 17 февраля 2021 20:29