Синхронизация с Active Directory (AD Sync) — справочник¶
Синхронизация пользователей и групп из Active Directory / OpenLDAP в 1Форму. Односторонняя: AD → 1Форма. Справочник описывает, как устроена синхронизация, какие таблицы и настройки задействованы, какие настройки и endpoint'ы доступны администратору и как диагностировать проблемы.
Сервис подключения к 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 |
Порядок первичной настройки:
- Создать сервис каталога (Подключения → Сервисы): тип Active Directory, домен, учётная запись для доступа.
- Создать профиль синхронизации (Подключения → Синхронизации) и привязать его к сервису.
- В настройках профиля включить нужные флаги (раздел 4), при необходимости задать фильтр OU, LDAP-фильтры и лимит обновлений.
- Настроить маппинг свойств (раздел 5): сопоставить атрибуты AD полям пользователя 1Ф.
- Запустить синхронизацию — вручную (кнопка в профиле или 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 |
Пересборка отображаемых имён одного пользователя по всем его языкам | Да |
Процедура нормализует членство с учётом вложенности групп в четыре шага:
- Вставляет отсутствующие записи в
GroupUsersMembershipизUserGroups - CTE
NestingMembershipProjection— рекурсивный обходGroupParents, добавляет membership с учётом вложенности - Закрывает устаревшие записи membership (
EndDate) - Синхронизирует
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не получает записи, основанные на рекурсивном обходеGroupParentsGroupUsersMembershipне ведёт историю- Создание новых групп из 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¶
При планировании и настройке синхронизации учитывайте следующие ограничения:
- PG: нет
tc_NormalizeGroupUsersMembership— вложенность групп не нормализуется автоматически. Создание новых групп из AD на PG не работает. - OS Mutex
Global\ADSyncJob— защита от параллельного ручного и автоматического запуска. - Транзакция 300 минут — при большом количестве пользователей может не хватить.
- MaximumADSyncRows по умолчанию = 10 — очень мало для реальных площадок, часто вызывает тихий откат без ошибок.
- SID-дубликаты — при дубликатах SID в БД синхронизация завершается ошибкой.
- Динамические группы AD не поддерживаются.
- Символы
#и&в логинах/паролях — запрещены, вызывают ошибку при синхронизации. ActiveDirectoryAuthenticationMode— при одноимённых учётках в лесе AD нуженPrincipalContext, иначе ошибки аутентификации.-
Инвалидация токенов при смене пароля — при смене пароля в AD фоновый процесс проверки провайдеров аутентификации (каждые 15 минут) автоматически инвалидирует ранее выданные токены.
-
ObjectDisposedExceptionпри чтении отдельных AD-атрибутов на Windows — устранено на уровне платформы; см. support-guide-ad-sso.md, п. 1.5. -
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/.