Заполнение кастомных атрибутов сущности User

Всем привет!
Есть задача - заполнять кастомные вычисляемые атрибуты сущности User для того чтобы потом использовать их в предикатах Row-level Policy. Предполагаю, что эти атрибуты должны заполняться после прохождения аутентификации и авторизации. Куда можно встроить вызов кода для заполнения этих атрибутов.
Спасибо!

Нашёл такое событие AuthenticationSuccessEvent. Т.е. можно написать обработчик:

@Component
public class UserEventListener {

    @EventListener
    public void onAuthenticate(AuthenticationSuccessEvent event) {
        // process event
    }
}

Но есть одно неудобное обстоятельство. Мне нужно в этом обработчике выполнять запросы к БД, причем с учетом прав текущего пользователя. Т.е. нужно обращаться к dataManager. Но в этот момент в SecurityContext ещё не положен новый объект Authentication

А что такое “вычисляемые атрибуты сущности User”?
Вы расширили сущность User и добавили неперсистентные (transient) атрибуты?
Или эти вычисляемые атрибуты персистентные и хранятся в БД?
Если они неперсистентные, то заполнять их надо в обработчике события EntityLoadingEvent.
Только так вы гарантируете себе то, что где бы в приложении не загружался из БД экземпляр User - эти неперсистентные атрибуты будут заполнены, причем до применения Row-level Policy.

@andrey_vb, спасибо за ответ!
Всё верно, вычисляемыми атрибутами я назвал неперсистентные (transient) атрибуты объекта User. Но по смыслу мои атрибуты не являются атрибутами объекта User. Я размещаю их там только для того, чтобы использовать в JPQL и Groovy предикатах при описании Row-Level Roles. Там я могу к ним обратиться через конструкцию :current_user_<attribute>. Эти атрибуты являются скорее атрибутами сессии пользователя. Я не знаю, можно ли обращаться из предикатов JPQL и Groovy к чему-то ещё, помимо текущей сущности и текущего пользователя.
Так, например, мне не нужно заполнять эти атрибуты при получении списка пользователей в админке. Т.е. заполнение их в EntityLoadingEvent будет лишней нагрузкой (вычисление их доволньно затратно и требует обращения к БД).
В Cuba был такой объект UserSessionSource. Там хранились атрибуты сессии, в том числе текущий пользователь. Есть ли аналог этого объекта в Jmix с возможностью обращения к нему из JPQL и Groovy навроде того, как это сделано в Cuba через конструкцию :session?

Попробуйте событие org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent

Судя по реализации
io.jmix.securityui.authentication.LoginScreenSupport#onSuccessfulAuthentication
оно публикуется с уже установленным security context.

В Jmix аналога этой возможности пока что нет.

А в планах числится замена атрибутам сессии?
Мы тоже их активно пользовали.
Очень удобно и приятно, что их можно было прокинуть в Groovy и тот же JPQL в стандартном фильтре.

@albudarov, спасибо за совет!
Но, насколько я понял событие InteractiveAuthenticationSuccessEvent будет генерироваться только в UI-модуле. Мне нужно, чтобы код работал везде, в том числе и в REST-модуле и в тестах.
Я подумал, что я вполне обойдусь событием AuthenticationSuccessEvent и UnconstrainedDataManager -ом.

Реализовал макет своей идеи. Завёл атрибут в классе User:

@Transient
protected List<String> tagsToRead = new ArrayList<>();

Заполнил его тестовыми данными:

@Component
public class UserEventListener { 

    @EventListener
    public void onAuthenticate(AuthenticationSuccessEvent event) {
        User user = (User)event.getAuthentication().getPrincipal();
        user.setTagsToRead(List.of("T1","T2"));
    }
}

Сконфигурировал Row-Level Policy:

  • Type: JPQL
  • Action: Read
  • Where clause: t.name in :current_user_tagsToRead
  • Join clause: ,IN ({E}.tags) t

Назначил пользователю нужную роль с этой Policy. Запустил тест:

    @Test
    public void testRead() {
        systemAuthenticator.withUser("user1", ()->{
            callRead();
            return null;
        });    
    }

И оно работает! Вычитывает из БД только нужные сущности! :+1:
Всем большое спасибо за помощь!

1 симпатия

Нет, InteractiveAuthenticationSuccessEvent посылается и при аутентификации в REST.

Константин, спасибо!

Присоединюсь к вопросу @andrey_vb

Очень удобный механизм, который мы использовали.
Кроме того сущность пользователя сейчас не платформенная, что затрудняет использование функциональности в аддоне, т.е. я не могу вынести какой-то из атрибутов пользователя в аддон, чтобы впоследствии использовать в основном проекте.
Правильно понимаю, что общий для проектов персистентный объект куда можно было бы добавить атрибут сейчас отсутствует?

1 симпатия

Атрибуты сессии реализованы, в том числе с поддержкой доступа из JPQL-запросов.
См. Session Attributes.

1 симпатия

Константин, а планы по добавлению обвязки для хранения сессионных атрибутов к row-level группам имеются?

Она есть: Row-level Roles :: Jmix Documentation

Константин, я извиняюсь, может быть я не очень внимателен, но по приведенной ссылке мы используем сессионный атрибут в jpql, при этом я не вижу каким образом этот сессионный атрибут можно добавить пользователю (row-level role). В кубе был визуальный механизм:
image

или design-time:

    @SessionAttribute(name = "countryCode", value = "US", javaClass = String::class)
    override fun sessionAttributes(): MutableMap<String, Serializable> {
        return super.sessionAttributes()
    }

В jmix не вижу подобного…

А, извините, это я неправильно вас понял.

Нет, эту функциональность мы переносить в Jmix не планируем. Посчитали что с одной стороны она мало кому нужна, а с другой стороны легко создается в проекте: достаточно подписаться на слушатель InteractiveAuthenticationSuccessEvent и складывать в сессию то что нужно.

1 симпатия

Действительно не сложно, но что бы сложить что-то в сессию нужно это сначала где-то сохранить…
Но я услышал. Значит буду думать, как это реализовать наименьшей кровью.