Журнал технических изменений

Версия 2.6

Работа с задачами

Для задач на backend добавлено поле "Инициатор". Для задач, созданных в более ранних версиях, поле будет отсутствовать в индексе - потребуется переиндексация бизнес-процессов (операция будет доступна в следующих релизах).

Комментирование задач

  • Удален сервис для работы с комментариями com.unidata.mdm.worfklow.core.service.WorkflowCommentsService.

  • Удалено REST API для работы с комментариями БП (эндпоинты /v2/workflow/core/comment)

  • При обновлении системы будет выполнена миграция, которая перенесет старые комментарии в модуль marks.

SSO

  • Поддержан кастомный header в запросах "Single-Sign-On"

  • При получении пустых логина и пароля не будет возвращена ошибка, если пользователь уже аутентифицирован и аутентификация могла быть проверена с помощью одного из AuthenticationProvider в имеющихся SecurityDataSource.

Переиндексация бизнес-процессов

  • Добавлены методы для массовой индексации в WorkflowIndexingComponent.

  • Добавлены методы для конвертации Camunda объектов в Workflow[Process|Task|Attachment|Comment]Converter.

Клонирование записей справочника

  • Добавлена новая точка расширения DgCloneRecordParameters.

Физический слой данных

  • Эндпоинт для удаления/очистки информационных систем (ИС) был изменен на /dg/data/clean/information-system с параметрами informationSystemName (название ИС) и drop (булевое значение: false - очистка ИС, true - очистка и удаление ИС).

Последовательный запуск операций

Реализована возможность запуска новой операции после удачного/неуспешного завершения другой операции.

Запуск новой операции реализован с помощью TriggerExecutionListener.

В JobDefinitionStore имплементирован JobTriggersStore, реализующий настройку цепочек операций.

Добавлены API модули:

  • PUT /v2/core/jobs/triggers/{jobDefinitionId} - создание новой цепочки; сохранение идентификаторов связанных операций в таблице job_trigger.

  • GET /v2/core/jobs/triggers/{jobDefinitionId} - получение всех цепочек операций.

  • DELETE /v2/core/jobs/{jobDefinitionId}/triggers/{triggerId} - удаление цепочки операций.

Права доступа

Добавлены ресурсы:

  • Группы пользователей / User groups (вкладка Безопасность / Security).

  • Каталог доступа/ Access directory (вкладка Система / System).

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

Добавлен статус 401 Unauthorized:

  1. В группе пользователей:

Если нет прав на чтение групп пользователей:

  • GET /v1/commercial-core/users-group/{groupName}/check-users

  • GET /v1/commercial-core/users-group/{groupName}

  • GET /v1/commercial-core/users-group/{groupName}/users

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

  • POST /v1/commercial-core/users-group

  • PUT /v1/commercial-core/users-group/{groupName}

  • DELETE /v1/commercial-core/users-group/{groupName}

  • POST /v1/commercial-core/users-group/move

  1. Каталог доступа:

Если нет прав к каталогу доступа:

  • POST /v1/ldap/configuration/sandbox

  • GET /v1/ldap/configuration

  • POST /v1/ldap/configuration

  • GET /v1/ldap/configuration/{id}

  • PUT /v1/ldap/configuration/{id}

  • DELETE /v1/ldap/configuration/{id}

  • POST /v1/ldap/configuration/preview

  • POST /v1/ldap/configuration/preview

  1. Остались открытыми (чтобы у пользователя была возможность просмотреть свои группы в карточке):

  • GET /v1/commercial-core/users-group/by-user-login/{login}

  • GET /v1/commercial-core/users-group

Версия 2.5

Потоки выполнения

  • В поток выполнения [SCANNER_RESULT_START] добавлен новый сегмент [SCANNER_CLEAR_DELETED] типа Point, удаляющий активы и связи, которые ранее были удалены из сторонней информационной системы.

    • Удаление технических активов и связей выполняется после завершения загрузки данных краулером. В отчете работы краулера будет указано количество удаленных активов и связей (отчет доступен для просмотра в Уведомлениях системы).

  • Сегмент [SCANNER_CALCULATE_BREADCRUMBS] потока выполнения [SCANNER_RESULT_START] объявлен deprecated и будет удален в следующем релизе.

Граф связей

В org.unidata.mdm.graph.core.type.storage.GraphStorage добавлен новый метод org.unidata.mdm.graph.core.result.GraphGetResult traverse(org.unidata.mdm.graph.core.context.GraphTraverseContext), который необходимо реализовать для всех кастомных графовых хранилищ.

Операция очистки паролей

Добавлена операция очистки паролей org.unidata.mdm.core.service.impl.job.CleanInactivePasswordsJob. Параметры операции описаны по ссылке.

Конфигурация операции находится в org.unidata.mdm.core.configuration.CoreConfiguration: cleanInactivePasswordsJobDetail и cleanInactivePasswordsJobTrigger.

  • В реализацию org.unidata.mdm.core.service.impl.SecurityServiceImpl.login(AuthenticationRequestContext ctx) включено разрешение на аутентификацию через сервис org.unidata.mdm.core.service.AuthenticationAttemptCheckService.

  • В реализацию org.unidata.mdm.core.service.impl.SecurityServiceImpl добавлена зависимость org.unidata.mdm.core.service.AuthenticationAttemptCheckService.

Реестр компонентов

Сервис interface org.unidata.mdm.core.service.AuthenticationAttemptCheckComponentRegister

Реестр компонентов AuthenticationAttemptCheckComponent - компоненты реализуют проверки права на аутентификацию.

Текущая реализация org.unidata.mdm.core.service.impl.security.AuthenticationAttemptCounterServiceImpl реализует оба AuthenticationAttemptCheckService и AuthenticationAttemptCheckService.

Также реализация хранит лист компонентов. При регистрации компонента проверяет его наличие в списке и если не находит, то регистрирует и выводит в лог сообщение о регистрации с именем компонента. При удалении из типа актива нахождение не проверяется, но в лог пишется снятие с регистрации (даже если не был зарегистрирован).

Методы:

  • void register(AuthenticationAttemptCheckComponent component) - добавляет компонент в реестр.

  • void unregister(AuthenticationAttemptCheckComponent component) - удаляет компонент из типа актива.

Ошибка о блокировке доступа

Ошибка: exception org.unidata.mdm.core.exception BlockedAuthenticationAttemptException extends PlatformBusinessException.

В текущей реализации используется для UI сообщения пользователю о превышении лимита неудачных попыток аутентификации.

Примечание

На этом этапе пользователь обычно еще не авторизован, и получить его локаль может быть невозможно. Поэтому в конструкторе необходимо явно указывать локаль, в противном случае будет использоваться локаль системы, указанная в параметрах системы.

Класс для конкретных проверок

Класс abstract class org.unidata.mdm.core.service.AuthenticationAttemptCheckComponent implements AfterModuleStartup реализует конкретную проверку и должен быть зарегистрирован в типе актива, чтобы участвовать при проверке разрешения на аутентификацию.

После старта модуля регистрируется в типе актива AuthenticationAttemptCheckComponentRegister автоматически, если включен (по методу isEnabled()).

Текущие реализации:

  • org.unidata.mdm.core.service.impl.security.UsernameAuthenticationAttemptCheckComponent

  • org.unidata.mdm.core.service.impl.security.ClientIpAuthenticationAttemptCheckComponent

Методы:

  • @Override public final void afterModuleStartup() - регистрирует компонент в типе актива после старта модуля, если компонент включен.

  • protected final void register() - регистрирует компонент в типе актива.

  • protected final void unregister() - снимает регистрацию в типе актива.

  • protected boolean isEnabled() - включение компонента. По умолчанию возвращает true.

  • public String getInternalComponentName() - внутреннее имя компонента. По умолчанию возвращает полное имя класса. В текущей реализации используется только в методе getExternalComponentName по умолчанию.

  • public String getExternalComponentName() - возвращает внешнее имя компонента, например, более читаемое для лога. По умолчанию вызывает getInternalComponentName(). Например, "Username authentication attempt count check component" для UsernameAuthenticationAttemptCheckComponent.

  • protected Locale locale(@NonNull AuthenticationRequestContext ctx) - достает из контекста локаль, присылаемую пользователем при попытке аутентификации, чтобы использовать в локализации текстов. Например, ошибки о превышении лимита неудачных попыток аутентификации для IP адреса. Поскольку пользователь еще не авторизовался - TextUtils возвращает локаль системы иначе.

  • public abstract boolean isAllowedToLogin(@NonNull AuthenticationRequestContext ctx) - проверяет разрешение на аутентификацию согласно правилам компонента.

  • public abstract void fail(@NonNull AuthenticationRequestContext ctx, Throwable cause) throws BlockedAuthenticationAttemptException - действие при неудачной попытке аутентификации с указанием причины. Например, увеличить счетчик неудачных попыток. Может бросать BlockedAuthenticationAttemptException. Может не бросать исключение, если нет необходимости передавать наружу событие. Чтобы локализовать текст BlockedAuthenticationAttemptException можно использовать метод locale выше для выборки из контекста.

  • public final void fail(@NonNull AuthenticationRequestContext ctx) throws BlockedAuthenticationAttemptException - вызывает метод выше с cause = null.

  • public abstract void success(@NonNull AuthenticationRequestContext ctx) - действие при успешной аутентификации. Например, сбросить счетчики неудачных попыток.

Пример

Задача: необходимо добавить список разрешенных для аутентификации IP адресов.

  1. Создайте реализацию AuthenticationAttemptCheckComponent.

  2. Реализуйте String getExternalComponentName() - например, пусть возвращает "IP address white list authentication attempt check component".

  3. Добавьте туда список разрешенных адресов.

  4. Реализуйте isEnabled(), если он не должен быть всегда включен.

    • Если включается/выключается в рантайме, то необходимо сделать метод, вызывающий register()/unregister().

  5. Реализуйте boolean isAllowedToLogin(@NonNull AuthenticationRequestContext ctx) - проверьте, находится ли CLIENT_IP из AuthenticationRequestContext в списке разрешенных адресов.

  6. Реализуйте fail(@NonNull AuthenticationRequestContext ctx, Throwable cause) - в этом случае можно ничего не делать или написать в лог.

  7. Реализуйте void success(@NonNull AuthenticationRequestContext ctx) - в этом случае можно ничего не делать.

Подсчет количества неудачных попыток

Класс class org.unidata.mdm.core.type.security.AuthenticationAttempt implements Serializable хранит информацию о количестве неудачных попыток и времени последней неудачной попытки.

Используется для компонентов проверки, которые реализуют счетчики попыток, основанные, например, на Map<Object, AuthenticationAttempt>.

Поля класса:

  • public static final AuthenticationAttempt EMPTY = new AuthenticationAttempt(0, LocalDateTime.MIN) - возвращать, если в истории нет неудачных попыток аутентификации, чтобы не возвращался null.

Поля объекта:

  • private final int tries - количество неудачных попыток.

  • private final LocalDateTime lastTime - время последней попытки.

Методы:

  • public AuthenticationAttempt(int tries, @NonNull LocalDateTime last) - конструктор.

  • public int getTries() - возвращает tries .

  • @NonNull public LocalDateTime getLastTime() - возвращает lastTime.

  • @Override public String toString() - возвращает "LoginAttempt{" + "tries=" + tries + ", lastTime=" + lastTime + '}'.

Реализации кэша

  • interface org.unidata.mdm.core.service.AuthenticationAttemptCache - кэш количества неудачных попыток аутентификации по определенному ключу, если их необходимо запоминать.

Методы:

  • @NonNull AuthenticationAttempt get(@NonNull Object key) - получение AuthenticationAttempt по ключу. Должен возвращать AuthenticationAttempt.EMPTY, если для ключа ничего нет.

  • void put(@NonNull Object key, @NonNull AuthenticationAttempt attempt) - кладет AuthenticationAttempt по ключу.

  • void remove(@NonNull Object key) - удаляет AuthenticationAttempt для ключа, например, при удачной аутентификации для сброса истории неудачных попыток.

  • void clear(long ttlMinutes) - очищает старые записи по TTL (в минутах) на основе AuthenticationAttempt.lastTime.

  • void clear() - очищает кэш.

  • abstract class org.unidata.mdm.core.service.impl.security.AbstractMapBasedAuthenticationAttemptCache implements AuthenticationAttemptCache - реализует кэш на основе Map<Object, AuthenticationAttempt>.

Для hazelcast использование можно посмотреть в ClientIpAuthenticationAttemptCheckComponent или UsernameAuthenticationAttemptCheckComponent.

Абстрактные методы:

  • protected abstract Map<Object, AuthenticationAttempt> getMap() - получить карту, для использования в других методах, например, чтобы достать из hazelcast

Методы:

  • @NonNull @Override public AuthenticationAttempt get(@NonNull Object key) - по умолчанию getMap().getOrDefault(key, AuthenticationAttempt.EMPTY).

  • @Override public void put(@NonNull Object key, @NonNull AuthenticationAttempt attempt) - по умолчанию getMap().put(key, attempt).

  • @Override public void remove(@NonNull Object key) - по умолчанию getMap().remove(key).

  • @Override public void clear(long ttlMinutes) - по умолчанию фильтрует и удаляет записи, у которых истекло время жизни. Для hazelcast нужно переписать, например, используя destroy().

Предложения связей

Добавлены новые модули com.universe.dg.suggestions и com.universe.dg.rest.v1.suggestions для работы с предложениями связей. Подробную информацию о сервисах и примеры запросов см. по ссылке.

Комментарии и оценки карточки записи

Добавлен новый модуль org.universe.mdm.marks, позволяющий маркировать различные объекты системы. В текущей реализации модуль обслуживает функциональность оценок и комментариев карточки записи.

Комментарии и оценки присваиваются комбинации name space(register, asset, lookup, etc.), type name (имя сущности или "*" для всего пространства имен) и subject ID (идентификатор маркируемого объекта). Взаимодействие осуществляется через сервисы, позволяющие делать вставку, удаление и запросы к подсистемам. Также через сервисы происходит регистрация пользовательского кода на события добавления и удаления комментариев или оценок.

Оба сервиса реализуют 2 семейства операций: вставки и удаления, а также запросов - по namespace + typename (optional) + subject ID (идентификатор маркируемого объекта) и по ID(s) самого объекта комментария или оценки.

Комментарии:

Операция вставки всегда требует name space. Кроме режима like/dislike всегда требует subjectId. Наличие ID комментария обновляет объект комментария. Режим like/dislike не требует наличия subject ID.

Операция удаления всегда требует name space + subjectId или ID.

Сервис комментариев:

/**
 * Comments service.
 */
public interface CommentsService {
    /**
     * Upserts a comment.
     * @param ctx the upsert context
     */
    void upsert(CommentUpsertContext ctx);
    /**
    * Deletes a comment or a number of comments.
     * @param ctx the delete context
     */
    void delete(CommentDeleteContext ctx);
    /**
     * Queries comments.
     * @param ctx the context
     * @return query result
     */
    CommentQueryResult query(CommentQueryContext ctx);
    /**
     * Registers a lifecycle listener.
     * @param ns the name space
     * @param typeName the type name, may be null, "", or "*"
     * @param listener the listener
     */
    void register(NameSpace ns, String typeName, CommentLifecycleListener listener);
}

Оценки:

Операция вставки всегда требует name space. Кроме режима rescore всегда требует subjectId. Наличие ID оценки обновляет объект оценки. Режим rescore не требует наличия subject ID.

Операция удаления всегда требует name space + subjectId или ID.

Сервис оценок:

/**
* Score service.
 */
public interface ScoresService {
    /**
     * Upserts a score object.
     * @param ctx the upsert context
     */
    void upsert(ScoreUpsertContext ctx);
    /**
     * Deletes a score object or a number of score objects.
     * @param ctx the delete context
     */
    void delete(ScoreDeleteContext ctx);
    /**
     * Queries score objects.
     * @param ctx the context
     * @return query result
     */
    ScoresQueryResult query(ScoreQueryContext ctx);
    /**
     * Registers a lifecycle listener.
     * @param ns the name space
     * @param typeName the type name, may be null, "", or "*"
     * @param listener the listener
     */
    void register(@Nonnull NameSpace ns, String typeName, @Nonnull ScoreLifecycleListener listener);
}