Перейти к содержанию

Синхронизация с Active Directory (AD Sync) — справочник

Синхронизация пользователей и групп из Active Directory / OpenLDAP в 1Форму. Односторонняя: AD → 1Форма. Справочник описывает, как устроена синхронизация, какие таблицы и настройки задействованы, какие настройки и endpoint'ы доступны администратору и как диагностировать проблемы.

Сервис подключения к Active Directory создаётся и настраивается в администрировании: указываются домен, учётная запись для доступа и признак «Домен является корнем леса».

Настройка сервиса Active Directory в администрировании

В сертифицированной сборке 1F Certificate Edition (ФСТЭК) синхронизация с внешним каталогом не выполняется: задача ежедневного запуска не зарегистрирована, административные маршруты api/admin/ldap, api/sync-ad, api/admin/users/{id}/sync-with-ad и api/admin/groups/domains в сборке отсутствуют (ответ 404), синхронизация членства при входе не выполняется. Профили синхронизации и записи расписания при этом сохраняются в базе. Подробности — раздел 13.

Где настраивается

Все компоненты синхронизации настраиваются в администрировании, раздел Подключения:

Что настроить Где (Администрирование → Подключения) Форма
Подключение к каталогу (домен, учётная запись) Сервисы → создать сервис типа Active Directory или OpenLDAP active-directory-service-settings
Профиль синхронизации (привязка к сервису, фильтр OU) Синхронизации synchronizations
Флаги и параметры синхронизации (что синхронизировать, лимит, LDAP-фильтры) Синхронизации → настройки профиля synchronization-settings
Маппинг свойств AD → поля 1Ф Синхронизации → вкладки «Общие настройки» и «Расширенные настройки пользователя» —
Вход пользователей по учётным записям AD Провайдеры → провайдер типа Active Directory authentication-providers

Порядок первичной настройки:

  1. Создать сервис каталога (Подключения → Сервисы): тип Active Directory, домен, учётная запись для доступа.
  2. Создать профиль синхронизации (Подключения → Синхронизации) и привязать его к сервису.
  3. В настройках профиля включить нужные флаги (раздел 4), при необходимости задать фильтр OU, LDAP-фильтры и лимит обновлений.
  4. Настроить маппинг свойств (раздел 5): сопоставить атрибуты AD полям пользователя 1Ф.
  5. Запустить синхронизацию — вручную (кнопка в профиле или API из раздела 6) либо дождаться планового запуска задания ADSyncJob (ежедневно в 20:00).

1. Архитектура

Синхронизация может запускаться по расписанию, при логине, через смарт-действие или API; связь объектов AD и 1Ф ведётся по идентификатору SID.

Точки запуска синхронизации

Синхронизация может запускаться из нескольких точек:

Триггер Что синхронизирует
По расписанию (ежедневно 20:00), задание ADSyncJob Активные профили Active Directory и OpenLDAP: пользователи + группы + членство + вложенность. У OpenLDAP вложенность строится только для групп с членством по DN
При логине пользователя Один пользователь (по запросу), может создать нового
Смарт-действие синхронизации Один пользователь (по UserID) в каталоге Active Directory или OpenLDAP, может привязать идентификатор записи каталога
API (admin) — синхронизация пользователя Один пользователь
API (admin) — синхронизация групп Все группы
Создание пользователя администратором Один пользователь, сразу после записи. Профиль ищется по имени службы каталога из Users.DomainController: сначала среди активных профилей OpenLDAP, затем среди профилей Active Directory

В 1F Certificate Edition ни один из перечисленных триггеров, кроме внутреннего вызова при создании пользователя, в сборке отсутствует — см. раздел 13.

Связь между базой 1Ф и AD выполняется через SID: Users.SID = objectSid пользователя, Groups.ADSID = objectSid группы. Для профиля OpenLDAP в те же поля пишется идентификатор записи каталога из схемы, по умолчанию entryUUID, а логин пользователя берётся из атрибута логина, по умолчанию uid.

Автосинхронизация при создании пользователя. Пользователь, созданный администратором, синхронизируется с каталогом сразу после записи, не дожидаясь планового прогона. Профиль выбирается по имени службы каталога из Users.DomainController (в карточке пользователя — «Домен пользователя»): у пользователя OpenLDAP это имя службы, у пользователя Active Directory — домен. Для пользователя OpenLDAP имя службы записывает само ядро, когда в карточке выбран провайдер аутентификации OpenLDAP. Синхронизация запускается при включённых флагах профиля «Данные пользователей» или «Членство пользователей» вместе с «Синхронизировать существующих пользователей», а для профиля OpenLDAP — только при создании из административного интерфейса: при создании смарт-действием или импортом пользователь OpenLDAP автоматически не синхронизируется. Если имя службы каталога не заполнено, синхронизация пропускается, а пользователь создаётся. Если активных профилей OpenLDAP с этим именем несколько, синхронизация тоже пропускается, причина пишется в журнал ошибок, и создание пользователя не прерывается.

В синхронизацию попадают:

  • Пользователи — с непустым SID и не уволенные (IsFired_2 = false).
  • Группы — с непустым ADSID и принадлежащие текущему клиенту (CustomerID).

2. ADSyncJob — порядок выполнения

Плановая синхронизация выполняется заданием ADSyncJob со следующими параметрами:

  • Расписание: ежедневно в 20:00.
  • Параллельный запуск запрещён (защита через OS Mutex Global\ADSyncJob) — ручной и автоматический синк не пересекаются.
  • Таймаут транзакции: 300 минут.
  • Контекст выполнения: системный робот.

В 1F Certificate Edition задача в сборку не входит и ежедневного запуска в 20:00 не происходит. Диагностический запрос QRTZ_FIRED_TRIGGERS из раздела 11 в профиле всегда пуст.

Ошибки при обходе каталога. Если объект, найденный поиском в каталоге, при чтении отвечает ошибкой «There is no such object on the server» (0x80072030), он пропускается: в журнал пишется предупреждение «Пропущен недоступный объект каталога» с его distinguishedName, и обход продолжается. Другие ошибки каталога не пропускаются. Ошибка синхронизации одного профиля записывается в журнал и не останавливает остальные профили; если хотя бы один профиль завершился ошибкой, задание после обработки всех профилей завершается ошибкой и запуск не считается успешным.

Шаги синхронизации

Шаги выполняются в следующем порядке; каждый управляется своим флагом профиля (SynchronizationProfilesADSettings):

# Шаг Флаг (SyncSettings)
1 Создание отсутствующих групп SyncGroupCreation && SyncExistingUsers
2 Создание новых пользователей из AD SyncUserDataBySchedule
3 Синхронизация данных групп (SID, DisplayName, Domain) SyncGroupData && SyncExistingUsers
4 Синхронизация данных пользователей (по маппингу) SyncUserData && SyncExistingUsers
5 Синхронизация вложенности групп (GroupParents) SyncGroupNesting && SyncExistingUsers
6 Синхронизация членства user-group SyncUserGroupMembership && SyncExistingUsers
7 Нормализация членства (хранимая процедура) SyncUserGroupMembership && SyncExistingUsers
8 Проверка лимита строк MaximumADSyncRows
9 Перезагрузка кэша пользователей всегда
10 Смарт-события для созданных пользователей для новых пользователей
11 Смарт-события для обновлённых пользователей для изменённых

Что делает каждый шаг

Подробнее о том, какие таблицы затрагивает каждый шаг:

Шаг Что делает
Создание отсутствующих групп Читает маски из ADGroupSyncMasks, находит группы в AD, создаёт в Groups с заполненным ADSID
Синхронизация данных групп Обновляет ADSID, DisplayName, Domain у существующих групп
Вложенность групп Синхронизирует GroupParents (parent-child связи групп)
Членство user-group Синхронизирует UserGroupsActual (прямое членство пользователь-группа)
Данные пользователей Обновляет поля пользователей по маппингу ADPropertyMapping; для изменённых — локализованные ФИО и пересборка отображаемых имён
Создание пользователей Создаёт новых пользователей (фильтр по OU и UserAccountControl)
Синхронизация одного пользователя Nick, SID, mapped-свойства, ext-свойства, оргструктура

Пересборка отображаемых имён при смене ФИО. Синхронизация обновляет не только поля Users, но и подписи пользователя. Для каждого изменённого пользователя после сохранения записываются локализованные ФИО (UserHelpersProcedures.UpdateUserLocalizationValues), а затем пересобираются отображаемые имена в dbo.UserNameModes — по каждому языку пользователя, хранимой процедурой dbo.RefreshUserNameModes. Порядок обязателен: для языка с заведённой локализацией имя берётся именно из неё (в dbo.fn_UserAllDisplayNames — isnull(username.lastName, u.LastName)), и перестановка шагов вернула бы прежнюю подпись. Такой порядок действует и на быстром пути синхронизации, и на обычном; пересборка выполняется по каждому изменённому пользователю. Сразу после пересборки сбрасывается кэш отображаемых имён, поэтому новое ФИО сразу видно в подписях и поиске, без ожидания истечения кэша.

Группы OpenLDAP

Для профиля OpenLDAP плановое задание выполняет те же шаги групп — создание, данные, членство, вложенность — по тем же флагам профиля, что и для Active Directory. Классы групп и атрибут членства задаёт схема каталога сервиса (directorySchema, см. раздел 7); по умолчанию это groupOfNames с членством по различительному имени в member и posixGroup с членством по логину в memberUid. Логин из memberUid переводится в DN по пользователям каталога, поэтому для OpenLDAP кеш пользователей готовится раньше групп, а логин, не найденный среди пользователей каталога, в членство не попадает. Вложенность групп строится только из членства по DN: в memberUid лежат логины пользователей, а не групп.

Группа OpenLDAP связывается с группой 1Формы полем Groups.ADSID — идентификатором записи каталога, а в Groups.Domain пишется постоянный ключ службы openldap:{guid} из Guid настроек сервиса (DirectoryServiceKey), а не редактируемое описание сервиса. Группа без имени или идентификатора и группа с неизвестным видом членства пропускаются с предупреждением в журнале; если ключ службы определить не удалось, группы профиля не синхронизируются. Синхронизация пользователей профиля в обоих случаях продолжается.

Чтение каталога по схеме. Профиль OpenLDAP читает каталог нейтральными сущностями пользователя и группы поверх общего запросного слоя; путь Active Directory со статическим соответствием атрибутов не затронут. Пользователи читаются фильтром по классу из схемы, атрибуты запрашиваются явным списком: класс, различительное имя, атрибут логина и атрибут идентификатора. Явный список нужен потому, что идентификатор записи — служебный атрибут, и по выборке «все атрибуты» каталог его не отдаёт. Группы всех классов схемы читаются одним запросом: для одного класса это обычный фильтр по классу, для нескольких — объединение через «или». Разбирается запись по фактическому классу из её же атрибутов; если у записи несколько классов из схемы, берётся первый подходящий в порядке схемы. Атрибуты, которых каталог не вернул, остаются пустыми, запись с классом вне схемы не интерпретируется, а ненастроенная или неполная схема приводит к ошибке ещё до запроса в каталог.

Пользователи OpenLDAP. Логин учётной записи берётся из атрибута логина схемы, а в Users.SID пишется идентификатор записи каталога — строковый, а не двоичный Windows-SID. Домен у профиля OpenLDAP не вычисляется, поэтому флаг добавления домена к логину на него не действует и доменный префикс не подставляется. Проверка формата Windows-SID выполняется только для профилей Active Directory: учётную запись каталога OpenLDAP можно создать и отредактировать вручную, идентификатор в виде строки ошибки не вызывает. Если администратор создал такого пользователя без идентификатора, он подставляется по логину — поиском записи каталога с тем же логином.

Ручные входы по пользователям и подразделениям. Импорт пользователей из каталога в администрировании, дерево подразделений каталога в настройках профиля, привязка существующего пользователя к записи каталога и синхронизация одного пользователя из карточки строят подключение по типу профиля и читают объекты по схеме каталога, поэтому доменный контроллер профилю OpenLDAP не нужен. Для синхронизации одного пользователя профиль выбирается по Users.DomainController: при заполненном имени службы берётся активный профиль OpenLDAP с таким именем, при нескольких одноимённых активных профилях вход отвечает ошибкой выбора, а если профиля OpenLDAP с таким именем нет — ищется профиль Active Directory. При пустом Users.DomainController берётся единственный активный профиль OpenLDAP, а если их несколько, вход также отвечает ошибкой выбора. Имя службы каталога записывается в Users.DomainController при синхронизации пользователя.

3. База данных

Синхронизация опирается на две группы таблиц: служебные таблицы профилей и настроек и AD-поля в основных таблицах пользователей и групп.

Таблицы профилей и настроек

Профили синхронизации и их параметры хранятся в связанных таблицах:

ServicesSettings (ServiceType=ActiveDirectory|OpenLDAP)
  ├── LDAPServicesCredentials (FK ServiceId)
  ├── OpenLDAPServicesCredentials (FK ServiceId)
  └── SynchronizationProfiles (FK ServiceId, UNIQUE)
        ├── SynchronizationProfilesADSettings (FK SynchronizationProfileId, 1:1)
        │     ├── SynchronizationProfilesADOrgUnits (FK SynchronizationProfileADSettingsId)
        │     └── ADGroupSyncMasks (FK SynchronizationProfileADSettingsId)
        └── ADPropertyMapping (FK SynchronizationProfileId)
Таблица Назначение
ServicesSettings Реестр внешних сервисов. ServiceType = ActiveDirectory, OpenLDAP, SAML, OAuth, Radius
LDAPServicesCredentials Домен, логин, пароль (зашифрован), IsDomainHasForestState
OpenLDAPServicesCredentials Аналогично для OpenLDAP
SynchronizationProfiles Профиль: IsActive, SyncType, ServiceId, CustomerId
SynchronizationProfilesADSettings Все флаги синхронизации (per-profile)
SynchronizationProfilesADOrgUnits Фильтр по OU
ADPropertyMapping Маппинг: OfProperty (поле 1Ф) → AdProperty (атрибут AD), per-profile
ADGroupSyncMasks Шаблоны имён (wildcards) для автосоздания групп

AD-поля в основных таблицах и таблицы, затрагиваемые синхронизацией

AD-идентификаторы хранятся в основных таблицах пользователей и групп:

Таблица Колонки Назначение
Users SID (VARCHAR 200, index IX_Users_SID), DomainController Идентификатор пользователя в AD
Groups ADSID (VARCHAR 8000), EnableADSync (computed: ADSID IS NOT NULL AND LEN > 0), Domain Идентификатор группы в AD
UserADNickHistory UserID, history data История смены AD-логинов

В ходе синхронизации записи создаются и обновляются в следующих таблицах:

Операция Таблица
Создание групп Groups
Обновление групп Groups
Вложенность GroupParents
Членство UserGroupsActual
Обновление пользователей Users
Создание пользователей Users
Ext-свойства UserInfoExtValues

Хранимые процедуры и алгоритм tc_NormalizeGroupUsersMembership

В синхронизации задействованы хранимые процедуры (колонка PG — поддержка на PostgreSQL):

SP Назначение PG
tc_NormalizeGroupUsersMembership Рекурсивный CTE по GroupParents → нормализует UserGroups и GroupUsersMembership с учётом вложенности Нет
sp_SyncAutoManagedGroup Синхронизация автоуправляемых групп ?
RefreshUserNames Обновление кэша имён пользователей Да
RefreshUserNameModes Пересборка отображаемых имён одного пользователя по всем его языкам Да

Процедура нормализует членство с учётом вложенности групп в четыре шага:

  1. Вставляет отсутствующие записи в GroupUsersMembership из UserGroups
  2. CTE NestingMembershipProjection — рекурсивный обход GroupParents, добавляет membership с учётом вложенности
  3. Закрывает устаревшие записи membership (EndDate)
  4. Синхронизирует UserGroups: удаляет не подтверждённые через вложенность, добавляет новые

Механизм записи на MS SQL и PostgreSQL различается — см. раздел 8.

Настройки в таблице Settings (устаревшие, мигрированы в профили)

Раньше настройки синхронизации хранились в общей таблице Settings. Сейчас они перенесены в профили; таблица соответствия:

Колонка По умолчанию Мигрирована в
MaximumADSyncRows 10 SynchronizationProfilesADSettings.MaximumADSyncRows
ADSyncGroupCreation true .SyncGroupCreation
ADSyncGroupData true .SyncGroupData
ADSyncGroupNesting true .SyncGroupNesting
ADSyncUserData true .SyncUserData
ADSyncUserGroupMembership true .SyncUserGroupMembership
ADSyncOrganizationUnits NULL .SynchronizationProfilesADOrgUnits
ADSyncUserDataBySchedule false .SyncUserDataBySchedule
IncludeDomainInNick — .IncludeDomainInNick
DomainController NULL LDAPServicesCredentials.DomainController
DomainControllerUser NULL LDAPServicesCredentials.DomainControllerUser
DomainControllerPassword NULL LDAPServicesCredentials.DomainControllerPassword

4. Флаги настроек (SynchronizationProfilesADSettings)

Поведение синхронизации задаётся флагами профиля. Настраиваются в администрировании: Подключения → Синхронизации → настройки профиля (форма synchronization-settings).

Флаг Тип Описание
SyncExistingUsers bool Главный выключатель. Все шаги (кроме 2) требуют true
SyncUserData bool Синхронизировать данные пользователей (шаг 4)
SyncGroupData bool Синхронизировать данные групп (шаг 3)
SyncGroupCreation bool Создавать отсутствующие группы (шаг 1)
SyncUserGroupMembership bool Синхронизировать членство user-group (шаги 6, 7)
SyncGroupNesting bool Синхронизировать вложенность групп (шаг 5)
CreateUsers bool Разрешить создание пользователей при логине
SyncUserDataBySchedule bool Создавать новых пользователей при плановой синхронизации (шаг 2)
SyncOnlyADEntity bool Синхронизировать только AD-сущности в оргструктуре
IncludeDomainInNick bool Добавлять домен к нику (DOMAIN\user)
MaximumADSyncRows int? Лимит строк (защита от массового повреждения). По умолчанию в Settings = 10
UsersADFilter string LDAP-фильтр пользователей (max 1000 символов)
GroupsADFilter string LDAP-фильтр групп
ChangePasswordException string Исключения смены пароля
EwsServiceSettingsId int? FK на EWS-настройки (Exchange)

5. Маппинг свойств AD → 1Ф

Маппинг определяет, какие атрибуты Active Directory переносятся в поля пользователя 1Ф. Настраивается в профиле синхронизации (Подключения → Синхронизации) на вкладках «Общие настройки» и «Расширенные настройки пользователя».

Блоки маппинга (UI)

В интерфейсе настройки маппинга поля сгруппированы в блоки:

Блок Поля 1Ф
Рабочая информация Динамические типы оргединиц (OrgUnitType{Id})
Личные данные Nick, LastName, FirstName, MiddleName, BirthDate, DisplayName, ImgAvatar, MaidenName, EnglishDisplayName, UserText
География Country, City, Room
Контакты Phone, Phone2, Phone3, CellPhone, HomePhone, Fax, Email, ExternalEmail, ICQ (Telegram), Skype, LiveJournal (—), Twitter (WhatsApp), SIP, TimeZone
Функционал BusinessFunctions
Прочее Notes

Стандартные AD-атрибуты и расширенные свойства (UserInfoExt)

Стандартный набор атрибутов AD, доступных для сопоставления:

assistant, comment, company, department, description, displayName, division,
givenName, homePhone, mail, manager, mobile, sn, telephoneNumber, otherTelephone,
title, userPrincipalName, physicalDeliveryOfficeName, co, l

Для каждого расширенного свойства пользователя (UserInfoExt) с заполненным AdProperty значение копируется из AD в UserInfoExtValues. Настраивается через вкладку «Расширенные настройки пользователя» в UI синхронизации.

6. Синхронизация аватара пользователя

При импорте thumbnailPhoto (или иного binary-атрибута фото по маппингу) сохраняется в Users.AvatarFileId через UserAvatarService.SyncAvatarFromAd.

Дедупликация по хешу. Считается SHA-256 исходных байт AD-фото (HashHelper.ComputeSHA256Hex), результат хранится в системном расширенном свойстве ADSyncAvatarHash (UserInfoExt/UserInfoExtValue). Если хеш совпадает с сохранённым И у пользователя есть AvatarFileId — sync ничего не делает: не перезаливает файл, не бампает LastPersonalInfoUpdateTime, не инвалидирует кеши файлов.

Восстановление после ручного удаления. При отсутствии AvatarFileId (пользователь удалил аватар вручную) фото восстанавливается из AD даже при совпадении хеша.

Multi-instance nuance. Текущий AvatarFileId берётся из БД (UsersEntityService), не из UsersCache — иначе кеш может отставать в кластере.

Версионирование файла аватара

Новое фото из AD заливается новой версией существующего файла, а не отдельным файлом: Users.AvatarFileId не меняется, LatestVersionId увеличивается на единицу, предыдущая версия остаётся в истории файла. Ранее выданные ссылки вида /api/files/download/{fileId}/{versionId} продолжают отдавать своё изображение. Новый файл создаётся только если у пользователя ещё нет аватара либо AvatarFileId указывает на удалённый файл (File.IsExist).

Приоритет AD над ручным аватаром

Дедупликация врезана в ветку синхронизации, а не в InsertPersonImageAvatar — тот же метод используется ручной загрузкой из профиля и мобильного приложения. При ручной загрузке аватара и при его удалении сохранённый ADSyncAvatarHash сбрасывается, поэтому следующая синхронизация не попадает в пропуск и применяет фото из AD.

AD — источник истины. Если у пользователя в AD есть фото, ручная замена аватара в 1Ф будет перезаписана при следующей синхронизации. Чтобы пользователи могли ставить свои аватары, снимите маппинг свойства фото в профиле синхронизации.

Поведение по сценариям

Сценарий Результат
Фото в AD не менялось, аватар есть Пропуск: файл, версия, LastPersonalInfoUpdateTime, хеш не меняются
Фото в AD заменено AvatarFileId прежний, LatestVersionId +1, хеш обновлён
У пользователя нет аватара Создаётся новый файл, заполняются AvatarFileId и хеш
Аватар удалён вручную Хеш сброшен, AvatarFileId пуст — синк восстанавливает фото из AD
Аватар заменён вручную Хеш сброшен — синк создаёт новую версию и перезатирает ручное фото из AD
Фото в AD отсутствует SyncAvatarFromAd не вызывается, текущий аватар остаётся

7. REST API

Администрирование синхронизации доступно через REST API. Ниже — основные группы endpoint'ов.

Профили синхронизации — /api/sync-ad (admin)

Управление настройками профиля, навигацией по дереву AD и связыванием пользователей:

HTTP Route Назначение
GET profiles/{id}/settings Получить настройки профиля
POST profiles/{id}/settings/update Обновить настройки профиля
GET profiles/{id}/domain/folders-tree Дерево OU домена
GET profiles/{id}/domain/root-folder Корневые папки домена
POST profiles/{id}/domain/folder Загрузить папку дерева
POST profiles/{id}/domain/unload-users Выгрузить пользователей
POST profiles/{id}/users-for-link Пользователи для связывания
POST link-users Связать пользователей с AD
GET ad-properties-names Список AD-атрибутов
GET profiles/{id}/sync-properties/blocks Блоки маппинга свойств
POST profiles/{id}/sync-properties/update Обновить маппинг свойств
GET extra-sync-properties Расширенные свойства синка
POST extra-sync-properties/update Обновить расширенное свойство

Лес доменов. Если у сервиса Active Directory установлен признак «Домен является корнем леса», domain/root-folder возвращает все домены леса, а раскрытие дерева и выгрузка идут по каждому домену отдельно: к дочернему домену открывается собственное подключение по его DNS-имени, переход по рефералам в режиме леса выключен. Поэтому DNS-имя каждого домена леса должно резолвиться и быть доступно с сервера приложения. Домен, отключённый в таблице использования доменов (DomainUsages), в обход не попадает; ошибка на одном домене леса пишется в журнал и не прерывает обход остальных.

Тип службы и схема каталога в настройках профиля. Настройки профиля (profiles/{id}/settings) содержат два поля сверх прежнего набора. serviceType — тип службы каталогов; сервер вычисляет его по сервису профиля и из запроса не применяет. Поле nullable намеренно: у перечисления типов нет члена со значением 0, поэтому незаполненное не-nullable поле уходило клиенту числом 0 — значением вне перечисления, — тогда как метод настроек отдаёт его именем. Теперь незаполненное приходит как null, а заполненное всегда именем. directorySchema — схема каталога OpenLDAP; на чтении заполняется только для профилей OpenLDAP, у Active Directory остаётся пустой. Схема хранится в SettingsCustom по ключу сервиса: если значения нет, отдаётся набор по умолчанию, если сохранённое значение не разбирается — метод завершается ошибкой. На сохранении схема принимается только для сервиса OpenLDAP, проверяется на корректность и записывается через настройки этого сервиса, после успешной транзакции кэш схемы сбрасывается.

Страница настроек профиля в SPA. Форма настроек синхронизации (sync-ad-settings) определяет тип службы по настройкам профиля и для профиля OpenLDAP прячет всё AD-специфичное: кнопки «Выгрузить из AD» и «Связь с AD», флаги «Логины при синке с AD сохранять в формате Domain\Nick», «Только орг. единиц из Active Directory» и «Маска для создания групп из AD», дерево организационных единиц и селект «Настройки EWS». Дерево OU для такого профиля не запрашивается вовсе (domain/folders-tree не вызывается), а при сохранении список организационных единиц уходит пустым. Подписи остальных полей читаются в «каталожной» редакции: «Синхронизация с каталогом», «Создавать пользователей из каталога», «Фильтр пользователей каталога (LDAP)», «Фильтр групп каталога (LDAP)», «Максимально допустимое количество обновлений при синхронизации с каталогом», «Информация о требуемой сложности пароля в каталоге». У профиля Active Directory ничего из перечисленного не скрывается: форма выводится полностью, подписи полей — обычные, не каталожные.

Схема каталога в форме настроек. Вместо скрытых блоков у профиля OpenLDAP появляется форма схемы каталога: у пользователей — objectClass, атрибут логина и атрибут идентификатора; у каждой группы — objectClass, атрибут названия, атрибут идентификатора и членство (атрибут и тип значения — dn или uid). Групп может быть несколько: строки добавляются и удаляются, но список не может быть пустым и последнюю строку удалить нельзя. Сохранение у профиля OpenLDAP требует валидной схемы помимо основной формы; у Active Directory схема в запрос не попадает. Схема из настроек профиля подставляется в форму как есть; если поле схемы в ответе пустое, форма заполняется набором по умолчанию — пользователи inetOrgPerson / uid / entryUUID, группы groupOfNames (членство member, тип dn) и posixGroup (членство memberUid, тип uid).

Недоступный объект в дереве OU. При раскрытии узла дерева OU в настройках профиля Active Directory дочерний объект, который при чтении отвечает ошибкой «There is no such object on the server» (0x80072030), пропускается: в журнал пишется предупреждение «Пропущен недоступный объект каталога» с его LDAP-путём, остальные дочерние объекты узла отображаются. Другие ошибки каталога прерывают раскрытие узла.

Дерево каталога профиля OpenLDAP. Для профиля OpenLDAP profiles/{id}/domain/folders-tree и profiles/{id}/domain/folder отбирают узлы по объектным классам схемы каталога профиля, а не по фиксированным классам Active Directory: учитываются корневые контейнеры каталога, подразделения и класс учётной записи из схемы. Поэтому в дереве видны и подразделения, и записи пользователей, а отдельного пользователя OpenLDAP можно отметить и выгрузить точечно — так же, как в дереве Active Directory.

Поиск в LDAP, прочие endpoints и MCP-серверы

Поиск пользователей и групп непосредственно в каталоге LDAP:

HTTP Route Назначение
GET providers Активные профили AD-синхронизации
POST search-users Поиск пользователя в LDAP
POST search-groups Поиск групп в LDAP

Точечная синхронизация и управление настройками сервиса:

HTTP Route Назначение
POST /api/admin/users/{userId}/sync-with-ad Синхронизировать одного пользователя
GET /api/admin/groups/domains Список доменов групп
POST /api/admin/groups/ad/sync-all-groups Синхронизировать группы всех активных профилей — Active Directory и OpenLDAP
CRUD /api/admin/services-settings/{id} Управление настройками сервиса (GET/POST/PUT/DELETE)

Недоступно в 1F Certificate Edition (ФСТЭК-сборка UniForm.Barebone). Группы api/admin/ldap (providers, search-users, search-groups), api/sync-ad (13 методов), маршрут POST /api/admin/users/{userId}/sync-with-ad и GET /api/admin/groups/domains в сборку не входят: обращения отвечают 404. Маршрут POST /api/admin/groups/ad/sync-all-groups в этой части не менялся.

Ручная синхронизация групп и поиск групп. sync-all-groups проходит по всем активным профилям синхронизации — Active Directory и OpenLDAP: создаёт отсутствующие группы и синхронизирует членство пользователей по флагам профиля, вложенность групп — всегда; шаг данных групп в ручной синхронизации не выполняется. Каждый профиль обрабатывается в собственном скоупе, транзакции и контексте базы, поэтому сбой одного профиля пишется в журнал ошибок и не прерывает остальные; после обхода запрос завершается ошибкой с номерами упавших профилей: «Не удалось синхронизировать группы по профилям синхронизации: … Профили без ошибок синхронизированы, подробности — в журнале ошибок.».

Для профиля OpenLDAP groups/domains и ldap/providers отдают в поле домена ключ службы openldap:{guid} (у Active Directory — контроллер домена), а ldap/search-groups ищет группу по схеме каталога без учёта регистра имени (у Active Directory — точное совпадение отображаемого имени). При входе пользователя OpenLDAP его членство в группах синхронизируется по тем же флагам, что у Active Directory: «Членство пользователей» при включённой синхронизации существующих пользователей.

Операции синхронизации также доступны через MCP-серверы:

Сервер Тулов Доступ
mcp-admin-api-ldap 3 admin
mcp-user-api-v2-sync-ad 13 user
mcp-admin-api-services-settings 6 admin

MCP в 1F Certificate Edition не поднимается (см. ../ai/backend.md § «Проект UniForm.MCP»). Дополнительно пакеты mcp-admin-api-ldap и mcp-user-api-v2-sync-ad не регистрируются в любом случае: их инструменты строятся по снятым контроллерам LdapController и SyncAdController.

Смарт-действие синхронизации: action_sync_user_with_a_d — параметры: param_0 (UserID), param_1 (SynchronizationProfileADDto, optional). Действие работает и с пользователями OpenLDAP: профиль берётся из переданного параметра, иначе ищется по имени службы каталога из Users.DomainController, а при незаполненном имени — по единственному активному профилю OpenLDAP.

8. Конфигурация

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

Источник Ключ Описание
Configuration UsePostgreSQLDatabase Переключатель MSSQL/PG
Configuration ExcludeAdSubdomains Исключённые поддомены при навигации по дереву AD
Configuration SystemRobotId ID робота для контекста выполнения ADSyncJob
web.config ActiveDirectoryAuthenticationMode Режим аутентификации: DirectoryServices (по умолчанию) или PrincipalContext (для одноимённых учёток в лесе)
SettingsCustom LDAP_AdGlobalCatalogHosts Оптимизация: список GC-хостов для ускорения загрузки дерева AD

9. MSSQL vs PostgreSQL — различия

Ограничение: PG не поддерживает полный AD sync

На PostgreSQL часть операций синхронизации работает иначе, чем на MS SQL:

Область MSSQL PG
Нормализация членства (tc_NormalizeGroupUsersMembership) Выполняется (шаг 7) Не выполняется
Создание новых групп из AD Работает Ограничено
Финальное сохранение изменений Да Нет

Последствия отсутствия tc_NormalizeGroupUsersMembership на PG:

  • Вложенность групп не нормализуется после синхронизации
  • UserGroups не получает записи, основанные на рекурсивном обходе GroupParents
  • GroupUsersMembership не ведёт историю
  • Создание новых групп из AD на PG не работает

Обход леса AD поддерживается и на PostgreSQL. Перечисление доменов леса, раскрытие дерева и выгрузка из дочерних доменов выполняются в том числе в linux-контуре, где работу с каталогом обеспечивает прокси на библиотеке Novell LDAP. Перечисленные выше ограничения относятся к шагам синхронизации, а не к обходу леса.

10. Поток данных

Схема показывает, каким таблицам и колонкам базы 1Ф соответствуют объекты и атрибуты Active Directory:

Active Directory (LDAP)              1Forma DB
=======================              =========

Users                                Users
  objectSid         ──────────────►    SID
  sAMAccountName    ──────────────►    Nick
  givenName         ──────────────►    FirstName
  sn                ──────────────►    LastName
  mail              ──────────────►    Email
  (mapped attrs)    ──────────────►    (mapped fields)
  (ext attrs)       ──────────────►    UserInfoExtValues

Groups                               Groups
  objectSid         ──────────────►    ADSID
  displayName       ──────────────►    Descr
  (domain)          ──────────────►    Domain

Membership
  group.member      ──────────────►    UserGroupsActual (прямое)
                    ──[SP]────────►    UserGroups (развёрнутое с учётом вложенности)
                    ──[SP]────────►    GroupUsersMembership (история)

Nesting
  group.memberOf    ──────────────►    GroupParents (parent-child)

11. Диагностика

SQL-запросы для проверки состояния AD sync

Запросы ниже помогают быстро оценить состояние синхронизации; выполняются в базе данных 1Ф:

-- Активные профили синхронизации
SELECT sp.Id, ss.Description, ss.ServiceType,
       ads.SyncUserData, ads.SyncGroupData, ads.SyncGroupCreation,
       ads.SyncUserGroupMembership, ads.SyncGroupNesting,
       ads.MaximumADSyncRows
FROM SynchronizationProfiles sp
JOIN ServicesSettings ss ON sp.ServiceId = ss.Id
JOIN SynchronizationProfilesADSettings ads ON ads.SynchronizationProfileId = sp.Id
WHERE sp.IsActive = 1;

-- Пользователи с SID (привязаны к AD)
SELECT COUNT(*) AS TotalLinked FROM Users WHERE SID IS NOT NULL AND LEN(LTRIM(RTRIM(SID))) > 0;

-- Группы с AD sync
SELECT GroupID, Descr, ADSID, Domain, EnableADSync
FROM Groups WHERE EnableADSync = 1;

-- Маски для создания групп
SELECT m.Wildcard, ads.SynchronizationProfileId
FROM ADGroupSyncMasks m
JOIN SynchronizationProfilesADSettings ads ON m.SynchronizationProfileADSettingsId = ads.Id;

-- Маппинг свойств
SELECT OfProperty, AdProperty, Entity
FROM ADPropertyMapping WHERE SynchronizationProfileId = @profileId;

-- Последний запуск ADSyncJob (Quartz)
SELECT TOP 1 * FROM QRTZ_FIRED_TRIGGERS WHERE JOB_NAME LIKE '%ADSync%' ORDER BY FIRED_TIME DESC;

Типовые проблемы

Наиболее частые проблемы синхронизации и способы их диагностики:

Симптом Причина Диагностика
Группы не создаются из AD PG: SP отсутствует; MSSQL: маска не задана или SyncGroupCreation = false Проверить флаги + маски + UsePostgreSQLDatabase
Пользователи не синхронизируются SyncExistingUsers = false (главный выключатель) SELECT SyncExistingUsers FROM SynchronizationProfilesADSettings
Тихий сбой (нет ошибок и нет изменений) MaximumADSyncRows = 10 (по умолчанию), строк больше лимита → откат Проверить значение лимита, увеличить
Дубликаты SID → ошибка В Users две записи с одинаковым SID SELECT SID, COUNT(*) FROM Users GROUP BY SID HAVING COUNT(*) > 1
Ошибка обращения к глобальному каталогу AD Некорректная настройка LDAP_AdGlobalCatalogHosts Проверить список GC-хостов; если не помогает — обратиться в поддержку 1Ф
Фильтр OU не работает Известный баг: при включённом фильтре OU пользователи не создаются Обратиться в поддержку 1Ф
После смены ФИО в каталоге подписи пользователя остались прежними Не пересобраны отображаемые имена (dbo.UserNameModes) select UserID, LanguageID, UserNameMode, DisplayName from dbo.UserNameModes where UserID = @userId — после синхронизации должны быть новые ФИО по каждому языку пользователя

12. Известные ограничения и edge cases

При планировании и настройке синхронизации учитывайте следующие ограничения:

  1. PG: нет tc_NormalizeGroupUsersMembership — вложенность групп не нормализуется автоматически. Создание новых групп из AD на PG не работает.
  2. OS Mutex Global\ADSyncJob — защита от параллельного ручного и автоматического запуска.
  3. Транзакция 300 минут — при большом количестве пользователей может не хватить.
  4. MaximumADSyncRows по умолчанию = 10 — очень мало для реальных площадок, часто вызывает тихий откат без ошибок.
  5. SID-дубликаты — при дубликатах SID в БД синхронизация завершается ошибкой.
  6. Динамические группы AD не поддерживаются.
  7. Символы # и & в логинах/паролях — запрещены, вызывают ошибку при синхронизации.
  8. ActiveDirectoryAuthenticationMode — при одноимённых учётках в лесе AD нужен PrincipalContext, иначе ошибки аутентификации.
  9. Инвалидация токенов при смене пароля — при смене пароля в AD фоновый процесс проверки провайдеров аутентификации (каждые 15 минут) автоматически инвалидирует ранее выданные токены.

  10. ObjectDisposedException при чтении отдельных AD-атрибутов на Windows — устранено на уровне платформы; см. support-guide-ad-sso.md, п. 1.5.

  11. 1F Certificate Edition: синхронизация с каталогом изъята — нет задачи расписания, нет четырёх административных маршрутов (404), нет автосоздания пользователя и синхронизации членства при входе; профили и записи расписания остаются в базе. Подробности — раздел 13.

13. Синхронизация с каталогом в 1F Certificate Edition

В сертифицированной сборке (1F Certificate Edition, профиль решения UniForm.Barebone, символ условной компиляции BAREBONE) синхронизация с каталогом из состава сборки изъята. Механизм — условная компиляция: правки в core не меняют поведение полной сборки UniForm/UniForm.slnx ни на одну строку.

13.1 Что отсутствует в сборке

Точка входа Где Поведение в профиле
Задача расписания ADSyncJob TCClassLib/QuartzJobs/Jobs/ADSyncJob.cs, регистрация в SchedulerFactory.JobsDictionary (поле BuiltinJobsDictionary) Класс не компилируется, регистрация обёрнута #if !BAREBONE. Ежедневного запуска в 20:00 нет; соседние задачи расписания на месте
api/admin/ldap/* LdapController (search-users, search-groups, providers), файл исключён из Uniform.Admin.Api.csproj Маршрутов нет, ответ 404
api/sync-ad/* SyncAdController (13 действий), папка исключена из UniForm.Api.csproj Маршрутов нет, ответ 404
POST api/admin/users/{id}/sync-with-ad UserManageController.SyncWithAd Метод под #if !BAREBONE, маршрута нет, ответ 404
GET api/admin/groups/domains GroupsController.GetDomains Метод под #if !BAREBONE, маршрута нет, ответ 404
Синхронизация членства при входе UserActiveDirectoryLookupService (файл исключён из TCClassLib.csproj), ветка отказа и метод CreateUser в WinClaimsTransformation Файл не компилируется, обращения к каталогу при входе не происходит
Класс параметра смарт-действия синхронизации ActionParameterSyncUserWithAD + регион SynchronizationProfileADDto в PacksLogic Исключены, в списке доступных действий их нет

Ядро синхронизации в этот профиль не компилируется: из TCClassLib исключены DirectoryInterop/** и Synchronization/ActiveDirectory/**, из Valhalla.ExternalServices — ActiveDirectory/**, OpenLDAP/**, DirectorySearch/LDAP/** и DirectorySearch/ActiveDirectory/** вместе с конфигураторами провайдеров. Условная ссылка на пакеты каталога (System.DirectoryServices*, Novell.Directory.Ldap.NETStandard, AntiLdapInjection) в профиле снята. Обращения к снятым типам вне этого кода отвязаны условной компиляцией: сервис запроса статуса пользователя в каталоге UserActiveDirectoryStatusRequestService не компилируется целиком, а потребители — управление пользователями и группами, поставщик свойств пользователя, обработчик смарт-действий синхронизации — работают без обращений к каталогу.

Прочие маршруты тех же контроллеров работают как прежде. Отдельных ответов и специальных кодов не заведено: 404 — общее правило каркаса.

13.2 Вход через Windows-идентичность

Пользователь разрешается только по локальной базе 1Формы: по SID (UserClaimsMapperBySid) либо по UPN при соответствующей настройке Negotiate. Поиск пользователя в каталоге, автосоздание пользователя при первом входе и синхронизация его членства в группах в профиле отсутствуют: если пользователя с таким SID в базе 1Ф нет, вход не проходит (UserNotFoundException).

13.3 Что остаётся

  • Таблицы профилей и настроек синхронизации (SynchronizationProfiles, SynchronizationProfilesADSettings, ADPropertyMapping, LDAPServicesCredentials / OpenLDAPServicesCredentials), записи расписания, поля Users.SID / Groups.ADSID и ресурсы-строки — без изменений. Профиль, настроенный до перехода на сертифицированную сборку, остаётся в базе, но больше не читается: ни задача, ни маршруты к нему не обращаются.
  • Клиентский (фронтовый) раздел администрирования синхронизации по решению владельца не менялся: он остаётся в интерфейсе и ведёт в снятые маршруты (404). Сокрытие AD-специфичных действий на клиенте — отдельная работа.
  • Удаление участника группы в профиле проходит только по общему пути AdminGroups, где стоит проверка последнего администратора: попытка удалить единственного администратора отклоняется (Language.errCantDeleteLastUser). Синхронизационного кода, менявшего членство в обход этой проверки, в сборке нет.
  • Состав сборки проверяют сторожа DirectorySyncBareboneAbsenceTests (в профиле проверяет отсутствие изъятых типов ядра каталога и задачи ADSyncJob в BuiltinJobsDictionary) и BareboneTestSourcesIsolationTests (проверяет, что тесты, ссылающиеся на изъятые имена, исключены из компиляции). Оба лежат в Tests/TCClassLib.Tests.Unit.Core/Barebone/.