AD Sync — справочник настройки
Синхронизация пользователей и групп из Active Directory / OpenLDAP в 1Форму. Односторонняя: AD → 1Форма.
1. Архитектура
Точки запуска синхронизации
| Триггер |
Класс |
Что синхронизирует |
| По расписанию (ежедневно 20:00) |
ADSyncJob (Quartz) |
Все активные профили: пользователи + группы + членство + вложенность |
| При логине пользователя |
UserActiveDirectoryLookupService |
Один пользователь (on-demand), может создать нового |
| SmartAction |
StandartActionSyncUserWithAD |
Один пользователь (по UserID), может привязать SID |
| API (admin) |
UserManageController.SyncWithAd |
Один пользователь |
| API (admin) |
GroupsController.SyncAllGroups |
Все группы |
Ключевые классы
| Класс |
Namespace |
Файл |
Роль |
ADSyncJob |
TCClassLib.QuartzJobs.Jobs |
TCClassLib/QuartzJobs/Jobs/ADSyncJob.cs |
Quartz job, точка входа для расписания |
ADSync |
TCClassLib.DirectoryInterop |
TCClassLib/DirectoryInterop/ADSync.cs |
Ядро (~1400 строк, partial class). Вся логика синхронизации |
ADSyncFactory |
— |
Там же |
Фабрика контекстов ADSync через delegate injection |
DirectoryContextFactory |
TCClassLib.DirectoryInterop |
TCClassLib/DirectoryInterop/DirectoryContextFactory.cs |
Подключение к LDAP (System.DirectoryServices) |
SynchronizationProfilesService |
TCClassLib.Synchronization.ActiveDirectory |
TCClassLib/Synchronization/ActiveDirectory/SynchronizationProfilesService.cs |
CRUD профилей синхронизации |
SynchronizationPropertiesMappingService |
— |
TCClassLib/Synchronization/ActiveDirectory/SynchronizationPropertiesMappingService.cs |
Маппинг свойств AD → поля 1Ф |
LinkUsersService |
— |
TCClassLib/Synchronization/ActiveDirectory/LinkUsersService.cs |
Массовое связывание пользователей (батчи по 30 LDAP-запросов) |
DomainEntriesTreeService |
— |
TCClassLib/Synchronization/ActiveDirectory/DomainEntriesTreeService.cs |
Навигация по дереву OU для UI |
UserActiveDirectoryLookupService |
TCClassLib.Users |
TCClassLib/Users/UserActiveDirectoryLookupService.cs |
On-demand поиск/синк при логине |
UserAdminService |
TCClassLib.Users |
TCClassLib/Users/UserAdminService.cs |
SyncUserWithAd() — синк одного пользователя |
AdminGroups |
TCClassLib |
TCClassLib/AdminGroups.cs |
Синхронизация групп с AD |
Partial-классы ADSync (провайдеры данных)
| Файл |
Роль |
CachedUserProvider.cs |
Кэширующий провайдер пользователей (bulk-операции) |
NonCachedUserProvider.cs |
Некэширующий провайдер (единичные операции) |
CachedGroupsProvider.cs |
Кэширующий провайдер групп |
NonCachedGroupProvider.cs |
Некэширующий провайдер групп |
IUserProvider.cs |
Интерфейс провайдера пользователей |
IGroupProvider.cs |
Интерфейс провайдера групп |
Линковка DB ↔ AD: через SID (Users.SID = objectSid, Groups.ADSID = objectSid).
Фильтрация для синка:
- Пользователи: SID != null && SID.Trim().Length > 0 && IsFired_2 == false
- Группы: ADSID != null && ADSID.Trim().Length > 0 && CustomerID == current
2. ADSyncJob — порядок выполнения
Cron: DailyAtHourAndMinute(20, 00). Атрибут: [DisallowConcurrentExecution] + OS Mutex Global\ADSyncJob.
Timeout транзакции: 300 минут. Контекст: SystemRobot.
Шаги SyncGroupsAndUsers()
| # |
Шаг |
Флаг (SyncSettings) |
Метод ADSync |
| 1 |
Создание отсутствующих групп |
SyncGroupCreation && SyncExistingUsers |
CreateAbsentGroups() |
| 2 |
Создание новых пользователей из AD |
SyncUserDataBySchedule |
FetchActiveDirectoryUsers() |
| 3 |
Синхронизация данных групп (SID, DisplayName, Domain) |
SyncGroupData && SyncExistingUsers |
SyncGroups() |
| 4 |
Синхронизация данных пользователей (по маппингу) |
SyncUserData && SyncExistingUsers |
SyncUsers() |
| 5 |
Синхронизация вложенности групп (GroupParents) |
SyncGroupNesting && SyncExistingUsers |
SyncNestingFromAD() |
| 6 |
Синхронизация членства user-group |
SyncUserGroupMembership && SyncExistingUsers |
SyncActualUsersMembership() |
| 7 |
Нормализация (SP) |
SyncUserGroupMembership && SyncExistingUsers |
NormalizeGroupUsersMembershipProcedureService.Launch() |
| 8 |
Проверка лимита строк |
MaximumADSyncRows |
throw TCLogicException если rows > limit |
| 9 |
Перезагрузка кэша пользователей |
всегда |
TCUser.ReloadAllUsersCache() |
| 10 |
Smart events (созданные) |
для новых пользователей |
EventsActions.ExecuteUserActions(AfterUserWasCreated) |
| 11 |
Smart events (обновлённые) |
для изменённых |
EventsActions.ExecuteUserActions(AfterUserWasUpdated) |
Методы ADSync (детально)
| Метод |
Что делает |
CreateAbsentGroups() |
Читает маски из ADGroupSyncMasks, находит группы в AD, создаёт в Groups с заполненным ADSID |
SyncGroups() |
Обновляет ADSID, DisplayName, Domain у существующих групп |
SyncNestingFromAD() |
Синхронизирует GroupParents (parent-child связи групп) |
SyncActualUsersMembership() |
Синхронизирует UserGroupsActual (прямое членство user-group) |
SyncUsers() |
Обновляет поля пользователей по маппингу ADPropertyMapping |
FetchActiveDirectoryUsers() |
Создаёт новых пользователей (фильтр по OU и UserAccountControl) |
SyncUserInternal() |
Синк одного пользователя: Nick, SID, mapped-свойства, ext-свойства, оргструктура |
3. База данных
Таблицы профилей и настроек
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-связанные поля в основных таблицах
| Таблица |
Колонки |
Назначение |
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-логинов |
Хранимые процедуры
| SP |
Назначение |
PG |
tc_NormalizeGroupUsersMembership |
Рекурсивный CTE по GroupParents → нормализует UserGroups и GroupUsersMembership с учётом вложенности |
Нет |
sp_SyncAutoManagedGroup |
Синхронизация автоуправляемых групп |
? |
RefreshUserNames |
Обновление кэша имён пользователей |
Да |
tc_NormalizeGroupUsersMembership — алгоритм (4 шага)
- Вставляет отсутствующие записи в
GroupUsersMembership из UserGroups
- CTE
NestingMembershipProjection — рекурсивный обход GroupParents, добавляет membership с учётом вложенности
- Закрывает устаревшие записи membership (
EndDate)
- Синхронизирует
UserGroups: удаляет не подтверждённые через вложенность, добавляет новые
ORM-операции (без SP)
| Операция |
Таблица |
MSSQL |
PG |
| Создание групп |
Groups |
DbContext.Groups.AddObject() (EF) |
GroupsEntityService.Insert() (LinqToDB) |
| Обновление групп |
Groups |
EF tracking + SaveChanges() |
GroupsEntityService.Update() |
| Вложенность |
GroupParents |
EntityService |
EntityService |
| Членство |
UserGroupsActual |
EntityService |
EntityService |
| Обновление пользователей |
Users |
DbContext.SaveChanges() |
UsersEntityService.Update() |
| Создание пользователей |
Users |
UserAdminService.AddUser() |
UserAdminService.AddUser() |
| Ext-свойства |
UserInfoExtValues |
EntityService |
EntityService |
Настройки в таблице Settings (legacy, [Obsolete])
| Колонка |
Default |
Мигрирована в |
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)
| Флаг |
Тип |
Описание |
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 |
Создавать новых пользователей при schedule-синке (шаг 2) |
SyncOnlyADEntity |
bool |
Синхронизировать только AD-сущности в оргструктуре |
IncludeDomainInNick |
bool |
Добавлять домен к нику (DOMAIN\user) |
MaximumADSyncRows |
int? |
Лимит строк (защита от массового повреждения). Default в Settings = 10 |
UsersADFilter |
string |
LDAP-фильтр пользователей (max 1000 символов) |
GroupsADFilter |
string |
LDAP-фильтр групп |
ChangePasswordException |
string |
Исключения смены пароля |
EwsServiceSettingsId |
int? |
FK на EWS-настройки (Exchange) |
5. Маппинг свойств AD → 1Ф
Стандартные AD-атрибуты
assistant, comment, company, department, description, displayName, division,
givenName, homePhone, mail, manager, mobile, sn, telephoneNumber, otherTelephone,
title, userPrincipalName, physicalDeliveryOfficeName, co, l
Блоки маппинга (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 |
Расширенные свойства (UserInfoExt)
В SyncUserInternal(): для каждого UserInfoExt с заполненным AdProperty значение копируется из AD в UserInfoExtValues. Настраивается через вкладку «Расширенные настройки пользователя» в UI синхронизации.
6. REST API
SyncAdController — /api/sync-ad (admin)
| HTTP |
Route |
Метод |
Сервис |
| GET |
profiles/{id}/settings |
GetProfileSettings |
SynchronizationProfilesService.GetAdSyncProfileSettings |
| POST |
profiles/{id}/settings/update |
Update |
SynchronizationProfilesService.UpdateSettings |
| GET |
profiles/{id}/domain/folders-tree |
GetDomainTree |
DomainEntriesTreeService.GetOrgUnitFoldersTree |
| GET |
profiles/{id}/domain/root-folder |
GetRootFolder |
DomainEntriesTreeService.GetDomainRootFolders |
| POST |
profiles/{id}/domain/folder |
GetRootFolder |
DomainEntriesTreeService.LoadFolder |
| POST |
profiles/{id}/domain/unload-users |
UnloadUsers |
LinkUsersService.UnloadUsers |
| POST |
profiles/{id}/users-for-link |
GetRootFolder |
LinkUsersService.GetUserForLink |
| POST |
link-users |
LinkUsers |
LinkUsersService.LinkUsers |
| GET |
ad-properties-names |
GetAdPropertiesNames |
SynchronizationPropertiesMappingService.GetAdPropertiesNames |
| GET |
profiles/{id}/sync-properties/blocks |
GetSynchronizationPropertyBlocks |
SynchronizationPropertiesMappingService.GetSynchronizationPropertyBlocks |
| POST |
profiles/{id}/sync-properties/update |
Update |
SynchronizationPropertiesMappingService.UpdateSynchronizationProperties |
| GET |
extra-sync-properties |
GetExtraSyncProperties |
SynchronizationPropertiesMappingService.GetExtraSyncPropirties |
| POST |
extra-sync-properties/update |
UpdateExtraSyncProperty |
SynchronizationPropertiesMappingService.UpdateExtraSyncProperty |
LdapController — /api/admin/ldap (admin)
| HTTP |
Route |
Метод |
Сервис |
| GET |
providers |
GetDomains |
SynchronizationProfilesService.GetActiveADSyncProfiles |
| POST |
search-users |
FindUserInLdap |
UserActiveDirectoryLookupService.FindADUserByName |
| POST |
search-groups |
FindGroupsInLdap |
UserActiveDirectoryLookupService.FindAdGroupByName |
Прочие endpoints
| HTTP |
Route |
Метод |
| POST |
/api/admin/users/{userId}/sync-with-ad |
UserManageController.SyncWithAd |
| GET |
/api/admin/groups/domains |
GroupsController.GetDomains |
| POST |
/api/admin/groups (sync all) |
GroupsController.SyncAllGroups |
| CRUD |
/api/admin/services-settings/{id} |
ServicesSettingsController (GET/POST/PUT/DELETE) |
MCP-серверы
| Сервер |
Тулов |
Доступ |
mcp-admin-api-ldap |
3 |
admin |
mcp-user-api-v2-sync-ad |
13 |
user |
mcp-admin-api-services-settings |
6 |
admin |
SmartScript action: action_sync_user_with_a_d — параметры: param_0 (UserID), param_1 (SynchronizationProfileADDto, optional).
7. Конфигурация
| Источник |
Ключ |
Описание |
Configuration |
UsePostgreSQLDatabase |
Переключатель MSSQL/PG |
Configuration |
ExcludeAdSubdomains |
Исключённые поддомены при навигации по дереву AD |
Configuration |
SystemRobotId |
ID робота для контекста выполнения ADSyncJob |
web.config |
ActiveDirectoryAuthenticationMode |
Режим аутентификации: DirectoryServices (default) или PrincipalContext (для одноимённых учёток в лесе) |
| SettingsCustom |
LDAP_AdGlobalCatalogHosts |
Оптимизация: список GC-хостов для ускорения загрузки дерева AD |
8. MSSQL vs PostgreSQL — различия
Критический пробел: PG не поддерживает полный AD sync
| Область |
MSSQL |
PG |
| Persistence |
EF ObjectContext (legacy) + SaveChanges() |
LinqToDB через EntityService |
SP tc_NormalizeGroupUsersMembership |
Вызывается (шаг 7) |
Не вызывается (if (!db.IsPostgreDB)) |
| Создание групп |
DbContext.Groups.AddObject() |
GroupsEntityService.Insert() |
| Обновление пользователей |
EF tracking + SaveChanges() |
UsersEntityService.Update() (AutoMapper) |
SaveChanges() в финале |
Да |
Нет (if (!Configuration.UsePostgreSQLDatabase)) |
Последствия отсутствия tc_NormalizeGroupUsersMembership на PG:
- Вложенность групп не нормализуется после синхронизации
- UserGroups не получает записи, основанные на рекурсивном обходе GroupParents
- GroupUsersMembership не ведёт историю
- Создание новых групп из AD на PG не работает
9. Поток данных
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)
10. Диагностика
SQL-запросы для проверки состояния AD sync
-- Активные профили синхронизации
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 |
| Silent failure (нет ошибок, нет изменений) |
MaximumADSyncRows = 10 (default), rows > limit → rollback |
Проверить значение лимита, увеличить |
| Дубликаты SID → exception |
В Users две записи с одинаковым SID |
SELECT SID, COUNT(*) FROM Users GROUP BY SID HAVING COUNT(*) > 1 |
| NullReferenceException в GlobalCatalogHostsForce |
Некорректная настройка LDAP_AdGlobalCatalogHosts |
Разбор: ServiceFlow/adsync-nullref-fix.md |
| Фильтр OU не работает |
Баг: при включённом фильтре OU пользователи не создаются |
Разбор: ServiceFlow/archive/adsync-ou-filter-broken.md |
11. Связанные документы
| Документ |
Содержание |
../integrations/support-guide-1c.md |
Пользовательская документация: настройки синхронизации, маппинг, фильтры |
| архивной инструкции по AD-синхронизации пользователей |
Возможности и ограничения, последовательность настройки, удаление записей |
admin.md |
Настройки сервиса AD |
| архивной инструкции по AD-синхронизации групп |
Синхронизация групп |
docs/domains/auth/support-guide-ad-sso.md |
Support guide для ТП 1-й линии |
docs/domains/auth/admin.md |
Таблицы, dbadmin-формы, контроллеры |
docs/domains/auth/database.md |
Схема таблиц auth |
docs/platform/quartz-jobs.md |
ADSyncJob в реестре заданий |
ServiceFlow/adsync-nullref-fix.md |
Разбор NullReferenceException в GlobalCatalogHostsForce |
ServiceFlow/archive/adsync-ou-filter-broken.md |
Разбор бага с фильтром OU |
12. Известные ограничения и edge cases
- PG: нет
tc_NormalizeGroupUsersMembership — вложенность групп не нормализуется автоматически. Создание новых групп из AD на PG не работает.
- Два параллельных пути persistence — на MSSQL: EF ObjectContext (legacy), на PG: LinqToDB через EntityService. Код содержит
if (UsePostgreSQLDatabase) ветвления.
- OS Mutex
Global\ADSyncJob — защита от параллельного ручного + автоматического запуска.
- Транзакция 300 минут — при большом количестве пользователей может не хватить.
- MaximumADSyncRows default = 10 — очень мало для реальных площадок, часто вызывает silent rollback.
- SID-дубликаты —
CachedUserProvider бросает исключение при дубликатах SID в БД.
- Динамические группы AD не поддерживаются (п.15 users-ad.md).
- Символы
# и & в логинах/паролях — запрещены, вызывают ошибку при синхронизации.
ActiveDirectoryAuthenticationMode — при одноимённых учётках в лесе AD нужен PrincipalContext, иначе ошибки аутентификации.
- Refresh-token invalidation — при смене пароля в AD фоновый процесс (
UserAuthenticationProviderVerificationJob, каждые 15 мин) автоматически инвалидирует ранее выданные токены.