Синхронизация пользователей по ODBC: как устроена
Приложение UnSpot ODBC SCIM Adapter переносит в UnSpot данные сотрудников из вашей базы: карточки пользователей, оргструктуру и статусы в расписании. Эта статья описывает устройство обмена — кто его инициирует, какие данные и по какому протоколу уходят из вашего контура, что при этом не передаётся, где приложение хранит токены и что попадает в его журнал. Она адресована службе информационной безопасности, которая согласовывает использование интеграции. Пошаговая установка, подготовка данных и настройка описаны в статье «Настройка синхронизации по ODBC (UnSpot ODBC SCIM Adapter)».
Как работает приложение
Приложение состоит из двух частей в одном исполняемом файле: окна настройки и службы Windows UnSpotOdbcScimService, которая и выполняет синхронизацию. Администратор один раз задаёт параметры в окне настройки и запускает службу — дальше она работает в фоне без участия пользователя: раз в заданный интервал (от 1 до 24 часов) подключается к вашей базе данных через ODBC, забирает подготовленные данные и передаёт их в UnSpot по HTTPS. Первый цикл начинается сразу после запуска службы.
За один цикл служба последовательно синхронизирует до трёх наборов данных:
- Пользователи (всегда) — карточки сотрудников: создание, обновление и блокировка по протоколу SCIM 2.0.
- Оргструктура (если включена) — дерево подразделений компании с руководителями, тоже по SCIM.
- Статусы расписания (если включены) — отметки «удалённая работа», «отпуск», «больничный» и другие нерабочие статусы в расписании сотрудников, через External API UnSpot.
Источником данных служат три представления (view) или таблицы с фиксированными именами и колонками, которые вы создаёте в своей базе поверх реальных данных: users_for_unspot, org_structure_for_unspot и user_schedule_for_unspot. Благодаря этому приложению не важно, как устроена ваша схема данных: любую структуру можно привести к требуемому виду обычным SQL-представлением. Состав колонок и примеры представлений — в статье «Настройка синхронизации по ODBC».

Описанное ниже поведение соответствует версии приложения 1.3.0.
Направление и инициатор обмена
Инициатор обмена всегда один — служба на вашей машине. UnSpot к вашему контуру не обращается: вебхуков, обратных вызовов и подписок на события в этой интеграции нет, и служба не открывает входящих портов — сетевых слушателей в ней не заведено.
| Соединение | Кто инициирует | Что происходит | Когда |
|---|---|---|---|
| Приложение → ваша база (ODBC) | приложение | Чтение подготовленных представлений. В базу приложение ничего не пишет: за весь цикл выполняются только запросы SELECT | каждый цикл |
| Приложение → UnSpot (SCIM 2.0) | приложение | Чтение текущего списка пользователей UnSpot, затем создание, обновление и удаление карточек; при включённой оргструктуре — замена дерева подразделений | каждый цикл |
| Приложение → UnSpot (External API) | приложение | Передача дневных статусов расписания, по одному запросу на статус | каждый цикл, если включены «Статусы расписания» |
| UnSpot → ваш контур | — | Входящих подключений нет | — |
Обратный поток данных в интеграции есть, но только в одну сторону — в оперативную память приложения. Перед сверкой служба постранично забирает из UnSpot текущий список пользователей: идентификатор, externalId, логин (email), имя и фамилию, должность, телефон, подразделение, руководителя, номер пропуска, табельный номер и признак активности. Эти данные нужны, чтобы понять, что изменилось; на диск они не сохраняются и в вашу базу не записываются — цикл заканчивается, список исчезает вместе с ним.
Жёстко заданных адресов в приложении нет: единственные исходящие соединения — те, что вы указали в настройках (источник данных ODBC, адрес SCIM и адрес External API). Телеметрии, обращений к серверам разработчика и автообновления в приложении нет.
Как работает синхронизация

Пользователи
Каждый цикл — это полная сверка: приложение забирает из UnSpot текущий список пользователей по SCIM и сравнивает его со строками представления users_for_unspot. Сопоставление идёт в два прохода: сначала по идентификатору (колонка id ↔ поле externalId в UnSpot), затем оставшиеся — по email без учёта регистра. По результатам сверки:
- Новые сотрудники (есть в базе, нет в UnSpot) — создаются. Email становится логином, значение
idзаписывается в externalId. - Изменившиеся — обновляются. Сравниваются только те поля, которые отмечены в настройках приложения: имя, фамилия, email и включённые дополнительные поля (отдел, руководитель, должность, телефон, номер пропуска, табельный номер).
- Исчезнувшие из выборки (уволенные или отфильтрованные) — удаляются из UnSpot: карточка сотрудника архивируется, пропадает из списков и теряет доступ. Активные брони снимаются, но данные и история остаются в системе.
- Вернувшиеся в выборку — если удалённый ранее сотрудник снова появился в данных, его учётная запись восстанавливается из архива по email автоматически. Настройки доступа задаются заново (группы по умолчанию, новый пароль), а если включены приветственные письма — сотрудник получит новое приглашение.
Пользователи, заведённые в UnSpot вручную (не через синхронизацию), приложение не трогает и не удаляет. Если email такого пользователя совпал с email сотрудника из базы, карточка «подхватывается» синхронизацией: в неё записывается externalId, и дальше она обновляется из базы как обычно.
Руководитель указывается в данных как email. Если на момент создания сотрудника его руководителя ещё нет в UnSpot, приложение сначала создаст всех сотрудников, а затем вторым проходом проставит руководителей — поэтому иерархия любой глубины корректно загружается за один цикл, порядок строк в выборке не важен.
Ошибка на одном сотруднике не останавливает цикл: она записывается в журнал, и приложение переходит к следующей записи. А вот ошибка чтения из базы (например, NULL в обязательной колонке или отсутствующая колонка) прерывает синхронизацию пользователей целиком: в этом цикле в UnSpot не уйдёт ни одной карточки. На оргструктуру и статусы расписания это не влияет — они читаются отдельными запросами и синхронизируются независимо.
Оргструктура
Если в настройках отмечена «Оргструктура», после пользователей приложение отправляет в UnSpot дерево подразделений из представления org_structure_for_unspot. Синхронизация односторонняя и полная: переданный список целиком заменяет оргструктуру в UnSpot одним запросом. Каждая строка — это путь подразделения от корня с разделителем «/» (например, Компания/Департамент продаж/Отдел B2B), при необходимости — с описанием и email руководителя подразделения.
Статусы расписания
Если отмечены «Статусы расписания», приложение дополнительно передаёт в UnSpot дневные статусы сотрудников из представления user_schedule_for_unspot: удалённая работа, отпуск, больничный, командировка и любые другие нерабочие отметки, а также сброс статуса (возврат к обычному рабочему дню). Статусы отображаются в расписании сотрудника и видны коллегам. Этот поток идёт не по SCIM, а через External API UnSpot (метод обновления статуса в расписании) и требует отдельного токена — см. справочник «External API: обновление статуса в расписании».
Особенности этого потока:
- Передаются только статусы с датой в окне ±45 дней от текущего дня.
- Выборка инкрементальная: после первого успешного прогона приложение запоминает момент синхронизации и дальше берёт только строки, у которых
updated_atне старше этой отметки. Отметка берётся по часам машины с приложением, а сравнение выполняет СУБД — следите, чтобы часы обеих машин были синхронизированы, иначе часть изменений может не попасть в выборку. - Статусы отправляются по одному с паузой в 1 секунду — так устроено ограничение частоты запросов External API (1 запрос в секунду).
- Если UnSpot ответил кодом 401 (неверный или истёкший токен) или 429 (превышен лимит запросов), передача останавливается, отметка времени не сдвигается, и в следующем цикле приложение повторит незавершённую порцию.
- Учитывайте: установка статуса на дату — как и его сброс — автоматически снимает собственные брони рабочих столов сотрудника на эту дату (будущая бронь удаляется, уже начавшаяся останавливается). Броней переговорных комнат и парковочных мест это не затрагивает.
Что передаётся
Из базы читаются только перечисленные ниже колонки трёх представлений — других запросов приложение не выполняет. Состав колонок в запросе зависит от галочек в окне настройки: невыбранные поля не читаются и не передаются.
Карточки сотрудников — представление users_for_unspot:
| Колонка в базе | Что содержит | Куда попадает в UnSpot | Когда читается |
|---|---|---|---|
| id | Идентификатор сотрудника в вашей системе | externalId | всегда |
| Рабочая почта — персональные данные | userName (логин), приводится к нижнему регистру | всегда | |
| first_name | Имя — персональные данные | name.givenName | всегда |
| last_name | Фамилия — персональные данные | name.familyName | всегда |
| department | Подразделение сотрудника — персональные данные | department в расширении enterprise | при поле «Отдел» |
| manager | Email руководителя — персональные данные | manager в расширении enterprise | при поле «Руководитель» |
| position | Должность — персональные данные | title | при поле «Должность» |
| phone | Рабочий телефон — персональные данные | phoneNumbers с типом work | при поле «Телефон» |
| number_pass | Номер пропуска в СКУД — персональные данные | numberPass в расширении UnSpot | при поле «Номер пропуска» |
| employee_id | Табельный номер — персональные данные | employeeId в расширении UnSpot | при поле «Табельный номер» |
Итого из вашего контура уходят такие персональные данные сотрудников: фамилия, имя, рабочий email, а при включении соответствующих полей — рабочий телефон, должность, подразделение, email руководителя, табельный номер и номер пропуска в системе контроля доступа. Отчество, дата рождения, домашний адрес, личный телефон, паспортные данные, оклад, фотография и любые другие атрибуты сотрудника приложение из базы не запрашивает.
Дерево подразделений — представление org_structure_for_unspot. Все три колонки читаются всегда, когда включена «Оргструктура»:
| Колонка в базе | Что содержит | Куда попадает в UnSpot |
|---|---|---|
| path | Полный путь подразделения от корня с разделителем «/» | path |
| description | Описание подразделения | description |
| managedBy | Email руководителя подразделения — персональные данные | manager |
Дневные статусы — представление user_schedule_for_unspot. Читается и передаётся только при включённых «Статусах расписания»:
| Колонка в базе | Что содержит | Куда попадает в UnSpot |
|---|---|---|
| user_email | Email сотрудника — персональные данные | userEmail |
| date | Дата статуса | date |
| status_type | Тип статуса: REMOTE_WORK, NOT_WORKING или пусто (сброс) | statusType |
| status_name | Отображаемое название статуса, например «Отпуск» | statusName |
| status_code | Короткий код статуса, например «ОТ» | statusCode |
| updated_at | Момент последнего изменения строки | не передаётся — колонка используется только в условии выборки |
Причина отсутствия сотрудника или подразделения в выборке приложению не сообщается и в UnSpot не уходит: передаётся сам факт, что записи больше нет. Что делает UnSpot с исчезнувшей записью, описано выше, в разделе «Пользователи».
Что не передаётся
Отдельно перечислим то, чего в обмене нет:
- Пароли сотрудников — ни в каком виде. Приложение не читает их из базы и не отправляет в UnSpot; пароль новому пользователю UnSpot назначает сам.
- Фотографии и аватары — такого поля в обмене нет.
- Любые колонки базы, кроме перечисленных выше — приложение формирует запрос из фиксированного списка колонок, «выбрать всё» оно не умеет.
- Группы, роли и права доступа UnSpot — они назначаются в UnSpot и синхронизацией не управляются.
- Брони, расписание, аналитика и другие данные UnSpot — обратно в вашу базу не возвращаются. Приложение выполняет к базе только запросы на чтение.
- Учётные данные вашей СУБД — строка подключения ODBC используется только на вашей машине и в UnSpot не уходит.
- Токены — в журнал приложения не записываются: они передаются заголовком авторизации, а в журнал попадает только тело запроса.
Протокол, порт и шифрование
| Соединение | Куда | Порт | Аутентификация |
|---|---|---|---|
| SCIM 2.0 | адрес из поля «SCIM URL», обычно https://ваш-домен.unspot.ru/api/scim; приложение дописывает к нему /Users и /OrgUnits | 443 | заголовок Authorization, схема Bearer, токен SCIM |
| External API | адрес из поля «URL API UnSpot», обычно https://ваш-домен.unspot.ru/api/external/v1/user-schedule | 443 | заголовок Authorization, схема Bearer, токен «Импорт расписания сотрудника» |
| ODBC | сервер вашей СУБД — адрес берётся из системного DSN или из строки подключения | по вашей СУБД | по настройкам источника данных: логин и пароль СУБД либо Windows-аутентификация |
| Входящие | нет | — | — |
- Адрес UnSpot используется ровно в том виде, в каком он сохранён в настройках: приложение не подставляет ни схему, ни порт и не проверяет, что адрес начинается с
https://. Указывайте адрес со схемой HTTPS — иначе запросы уйдут в открытом виде. - Проверка сертификата — стандартная для Windows: приложение работает через штатный HTTPS-клиент .NET с настройками по умолчанию. Обработчика, который отключал бы проверку сертификата, в приложении нет, и в настройках такой возможности тоже нет. Самоподписанный сертификат приведёт к ошибке подключения.
- Версия TLS и набор шифров согласуются средствами Windows на этой машине — в приложении они не фиксируются.
- Тело запросов — JSON: тип
application/scim+jsonдля SCIM иapplication/jsonдля статусов расписания. - Канал до СУБД шифруется настройками ODBC-драйвера и источника данных (для Microsoft SQL Server это, например, параметры
EncryptиTrustServerCertificate). Приложение в эти параметры не вмешивается и своих значений не подставляет — задайте их сами при создании источника данных.
Где хранятся токены и учётные данные
Все параметры приложения лежат в одном файле config.json в каталоге приложения — по умолчанию C:\Program Files\Umbrella IT\UnSpotOdbcScim. Это обычный текстовый JSON. Реестр Windows, Windows Credential Manager и защиту DPAPI приложение не использует.
| Что хранится | Где | В каком виде |
|---|---|---|
| Строка подключения ODBC — вместе с логином и паролем СУБД, если вы указали их прямо в ней | config.json | открытый текст |
| Токен SCIM 2.0 | config.json | открытый текст |
| Токен External API для статусов расписания | config.json | открытый текст |
| Адреса SCIM и External API, набор синхронизируемых полей, интервал, признак автозапуска | config.json | открытый текст |
| Логин и пароль СУБД, если они заданы в системном источнике данных | параметры системного DSN, которые ведёт Windows | по правилам ODBC-драйвера |
Шифрования файла настроек в приложении нет — оба токена и строка подключения хранятся в открытом виде. Ограничьте доступ к каталогу приложения средствами операционной системы: при стандартных разрешениях каталога Program Files изменять файл может только администратор, а прочитать — любой локальный пользователь. В окне настройки токены тоже показываются открытым текстом: поля ввода не маскируются.
Служба регистрируется командой sc.exe create без указания учётной записи, поэтому Windows запускает её от NT AUTHORITY\SYSTEM — самой привилегированной локальной учётной записи. Именно ей нужно выдавать права в базе при Windows-аутентификации. Приложение регистрирует службу только если её ещё нет, поэтому учётную запись можно сменить штатными средствами Windows — приложение это не переопределит. Если вы её меняете, выдайте новой учётной записи право на чтение каталога приложения и на запись в подкаталог logs. Само окно настройки запускается с правами администратора: без них служба не регистрируется и не управляется.
Что хранится на машине с приложением
Приложение не использует собственную базу данных. Всё, что оно оставляет на диске, — три объекта в каталоге приложения:
| Файл или каталог | Что внутри | Срок жизни | Как очистить |
|---|---|---|---|
| config.json | Параметры приложения, включая оба токена и строку подключения ODBC | до удаления приложения | удалить приложение — файл стирается автоматически |
| user_schedule_updated_at.json | Одна отметка времени: момент последней успешной синхронизации статусов расписания, в UTC. Персональных данных в файле нет | до удаления приложения | удалить файл при остановленной службе — следующий цикл возьмёт статусы за всё окно ±45 дней |
| logs\service-log.txt и архивы | Журнал работы службы: текущий файл и до 7 суточных архивов | ротация раз в сутки, хранятся 7 архивов | удалить файлы; при удалении приложения каталог удаляется целиком |
Выбранные из базы данные нигде не кэшируются: список сотрудников, дерево подразделений, статусы и ответ UnSpot существуют только в памяти процесса на время цикла и на диск не записываются.
Что попадает в журнал. На уровне Info, который включён по умолчанию, пишутся служебные сообщения цикла (сколько строк выбрано из базы, сколько пользователей создано, обновлено и удалено, ошибки подключения) и по каждому изменению — email и идентификатор сотрудника, например: «Создаём пользователя: i.petrov@company.ru (7f3a91c2-44b0)». То же при обновлении карточки, смене руководителя и удалении.
На уровне Debug журнал становится существенно подробнее — оцените это до того, как включать его:
- тексты выполняемых SQL-запросов и значения их параметров;
- тела всех запросов к UnSpot целиком — то есть карточки сотрудников с фамилией, именем, email, телефоном, должностью, подразделением, табельным номером и номером пропуска, а также передаваемые статусы расписания;
- тела ответов UnSpot на создание, обновление и удаление;
- какое именно поле изменилось у конкретного сотрудника.
Иными словами, Debug-журнал — это выгрузка персональных данных сотрудников в текстовый файл на диске. Токены в него не попадают, но всё остальное попадает. Включайте этот уровень только на время разбора проблемы и удаляйте файлы журнала после. Как переключить уровень и где лежит журнал — в статье «Настройка синхронизации по ODBC».
Требования к сети и правам
| Что нужно | Где | Зачем |
|---|---|---|
| Исходящий HTTPS, порт 443 | с машины приложения к домену вашей компании в UnSpot | запросы SCIM 2.0 и External API |
| Доступ к серверу СУБД | с машины приложения, порт по вашей СУБД | чтение трёх представлений |
| Входящие правила | не нужны | приложение не принимает подключений |
| Права в базе | право SELECT на три представления | приложение выполняет к базе только запросы на чтение |
| Права в Windows | локальный администратор на машине с приложением | установка, регистрация службы и управление ею |
| Роль в UnSpot | Владелец или Администратор интеграций | создание токена SCIM 2.0 и токена «Импорт расписания сотрудника» |
О минимальных привилегиях. Пример настройки прав из поставки приложения выдаёт учётной записи службы роли sysadmin и db_owner — для синхронизации этого не требуется. Приложению достаточно права SELECT на три представления, и в промышленной среде права стоит сузить до этого минимума. Ещё лучше не использовать Windows-аутентификацию от NT AUTHORITY\SYSTEM, а завести отдельную учётную запись СУБД только на чтение и указать её в источнике данных.
Отдельно учитывайте объём прав в UnSpot: токен SCIM 2.0 позволяет создавать, изменять и удалять пользователей рабочего пространства, а токен «Импорт расписания сотрудника» — менять статусы в расписании любого сотрудника. Оба лежат на машине с приложением в открытом виде, поэтому машина с адаптером по уровню защиты приравнивается к рабочему месту администратора UnSpot.
Полный список требований к машине, среде выполнения и дистрибутиву — в статье «Настройка синхронизации по ODBC».