Impersonating user in middleware tests

Hi

We have defined a standard group & roles structures for our tenants, using constraints on groups to filter data presented depending on user roles.

To write tests for that we need to impersonate user in order to check if the constraints are correctly defined and applied.

We have written middleware tests, and used Authentication.withUser(String login, […]) to impersonate a user but discovered that this does not change the session if already authenticated, which is the case in tests with ‘test_admin’.

So how could we write this kind of tests ?

Mike

Hi,

Authentication is used only for middleware beans that can be invoked without security context, for instance - JMX. In your case you can simply login with system credentials and set SecurityContext.

@Inject
private AuthenticationManager authenticationManager;
...
UserSession session = authenticationManager.authenticate(new SystemUserCredentials(login)).getSession();
AppContext.withSecurityContext(new SecurityContext(session), () -> {
    
});

In this case you will be able to make changes on behalf of user. There is no need to perform logout because session is not stored in the distributed cache after authenticate.

I’ve totally missed one handy method: Authentication.withUser(login, operation). You can use it without setting SecurityContext manually.

Thanks Youriy.

I tried both solutions but none works for our use case, I’m writing tests checking that specific constraints based on authenticated user have been applied to session.

None of the solution proposed is changing the session held by UserSessionSource, which is the one and only one that constraints are pulled from by RdbmsStore (see below)

Having created a User with some constraints defined, when using:

UserSession session = authenticationManager.authenticate(new SystemUserCredentials(login)).getSession();
AppContext.withSecurityContext(new SecurityContext(session), () -> {
[...]
});

Then the session will indeed have the constraints, this is done by UserSessionManager.createSession().

But In RdbmsStore:

protected boolean needToApplyByPredicate(LoadContext context) {
        return isAuthorizationRequired() && userSessionSource.getUserSession().hasConstraints()
                && needToApplyByPredicate(context, metaClass -> security.hasConstraints(metaClass));
    }

userSessionSource.getUserSession() will not yield the session created previously, but the one still held by UserSessionSource, which in our case has no constraints.

=> 1st solution outcome : there is impersonation at security level but constraints are not applied

For the second solution, it’s worse. As the test code is already authenticated (session user ‘test_admin’), nothing happens at all, see below Authentication.begin() which is called by withUser().

public UserSession begin(@Nullable String login) {
[...]
        // check if a current thread session exists, that is we got here from authenticated code
        SecurityContext securityContext = AppContext.getSecurityContext();
        if (securityContext != null) {
            UserSession userSession = userSessions.getAndRefresh(securityContext.getSessionId());
            if (userSession != null) {
                log.trace("Already authenticated, do nothing");
                cleanupCounter.set(cleanupCounter.get() + 1);
                if (log.isTraceEnabled()) {
                    log.trace("New cleanup counter value: {}", cleanupCounter.get());
                }
                return userSession;
            }
        }
[...]
}

=> 2nd solution outcome : no impersonation at all as the calling test code is already authenticated

BTW : I think method withUser() & begin() contracts are confusing (I saw the javadoc). As an API user when I write Authentication.withUser(“toto”) or begin(“toto”) I’m requesting that after that I’m logged as ‘toto’.

I understand that this API targets unauthenticated code like JMX calls but then, there should be an API for this kind of testing. This could be as simple as a boolean to force login in any case : withUser(String login, boolean forceLogin), begin(String login, boolean forceLogin)

As a workararound, in order to tests constraints I had to do impersonation myself like that:

        // save current test session and replace it with user's one
        TestUserSessionSource userSessionSource = (TestUserSessionSource) AppBeans.get(UserSessionSource.class);
        UserSession savedSession = userSessionSource.getUserSession();
        UserSessionManager userSessionManager = AppBeans.get(UserSessionManager.class);
        final User finalUser = user;
        UserSession constrainedSession = persistence.createTransaction().execute(em -> {
            return userSessionManager.createSession(finalUser, Locale.FRANCE, false);
        });
        userSessionSource.setUserSession(constrainedSession);
`       // no AfterAuthenticationEvent published to cause constraints to be applied to session
        // by tenant manager, so we need do it manually
        tenantManager.applyUserConstraints(constrainedSession, true);

        // code here is will apply session constraints

        // restore test session
        userSessionSource.setUserSession(savedSession);`

PS : preview of a new post seems broken for some time, however preview when editing works

Take a look at com/haulmont/cuba/core/sys/UserSessionSourceImpl.java:50 it does not hold system sessions.

UserSessionSourceImpl simply returns session stored in SecurityContext. Constraints are applied on middleware if you use DataManager.secured().

UserSessionSourceImpl simply returns session stored in SecurityContext

=> True, but not for com/haulmont/cuba/testsupport/TestUserSessionSource, which is the one we have in middleware tests.

EDIT : by looking at CUBA team similar testing, like for instance com\haulmont\cuba\security\DataManagerCommitConstraintTest.java, I’ve discovered that we follow quite the same way of testing constraints. So I’ve come up with something cleaner:

        // save current test session and login with user
        AuthenticationManager am = AppBeans.get(AuthenticationManager.NAME);
        UserSession constrainedSession = am.login(new SystemUserCredentials(user.getLogin())).getSession();
        TestUserSessionSource uss = (TestUserSessionSource) AppBeans.get(UserSessionSource.class);
        UserSession savedSession = uss.getUserSession();
        uss.setUserSession(constrainedSession);

        // code here will apply session constraints

        // restore previous session
        uss.setUserSession(savedSession);