UserAuthorizationRepository.java

package fr.metabocloud.core.repositories;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Repository;

import fr.metabocloud.core.model.AppInfos;
import fr.metabocloud.core.model.UserDetailsCore;
import fr.metabohub.users_apis_authorizations.ApiClient;
import fr.metabohub.users_apis_authorizations.ApiException;
import fr.metabohub.users_apis_authorizations.client.AuthenticationApi;
import fr.metabohub.users_apis_authorizations.model.CheckUserApiAuthorizationsRequest;

@Repository
public class UserAuthorizationRepository {

    /**
     * Define the class' logger
     */
    private static final Logger logger = LoggerFactory.getLogger(UserAuthorizationRepository.class);

    /**
     * Define the APP identifier
     */
    private static final String APP_ARTIFACT_ID = new AppInfos().getArtifactId();

    /**
     * Define the Authentication API
     */
    private final AuthenticationApi authenticationApi;

    private static final HashMap<String, UserDetailsCore> sessionUsersDetailsCore = new HashMap<>();
    private static final HashMap<String, Date> sessionUsersLastUsed = new HashMap<>();
    private static final int SESSION_VALIDITY = (120 * 60 * 1000); // 2 hours

    public UserAuthorizationRepository() {
        final var client = new ApiClient();
        client.setHost("unh-pfemlindev.ara.inrae.fr");
        client.setBasePath("/mth-users-api/");
        this.authenticationApi = new AuthenticationApi(client);
    }

    public UserAuthorizationRepository(final AuthenticationApi authenticationApi) {
        this.authenticationApi = authenticationApi;
    }

    ///////////////////////////////////////////////////////////////////////////

    /**
     * Get a user main information (ORCID, email, roles and authorization) for
     * current API from user's JWT or API-KEY (token) using a local cache checking
     * before a &quot;MTH-USERS-API&quot; call.
     *
     * @param userApiKey the user's JWT or API-KEY
     * @return user core information for authentication or <code>NULL</code> if
     *         invalid / not found / ...
     */
    public UserDetailsCore getUserDetails(final String userApiKey) {
        // step 1 - try fetch from session cache
        if (sessionUsersDetailsCore.containsKey(userApiKey)) {
            // check if valid cache
            if (sessionUsersDetailsCore.get(userApiKey) != null
                    && (new Date()).getTime() - (sessionUsersLastUsed.get(userApiKey)).getTime() <= SESSION_VALIDITY) {
                // update session cache last used (renew)
                sessionUsersLastUsed.put(userApiKey, (new Date()));
                // fetch from cache
                logger.info("user {} rights fetched from session cache", userApiKey);
                return sessionUsersDetailsCore.get(userApiKey);
            } else {
                // too old! clear cache
                logger.info("user {} session cache was too old ⇒ clean/renew it", userApiKey);
                sessionUsersDetailsCore.remove(userApiKey);
                sessionUsersLastUsed.remove(userApiKey);
            }
        }
        // step 2 - try fetch from database
        final var user = this.getUserDetailsFromMthUsersApi(userApiKey);
        if (user != null) {
            // extract and set in session cache
            sessionUsersDetailsCore.put(userApiKey, user);
            sessionUsersLastUsed.put(userApiKey, (new Date()));
            logger.info("user {} session cache loaded", userApiKey);
        }
        // step 3 - fetch from cache
        return sessionUsersDetailsCore.get(userApiKey) != null ? //
                sessionUsersDetailsCore.get(userApiKey) : //
                null;
    }

    /**
     * Get a user main information (ORCID, email, roles and authorization) for
     * current API from user's JWT or API-KEY (token) using a direct
     * &quot;MTH-USERS-API&quot; call.
     *
     * @param apiKey a {@link java.lang.String} object
     * @return user core information for authentication or <code>NULL</code> if
     *         invalid / not found / ...
     */
    public UserDetailsCore getUserDetailsFromMthUsersApi(final String apiKey) {
        // try to authenticate user
        try {
            // init DTO
            final var authDto = new CheckUserApiAuthorizationsRequest();
            authDto.setAccessToken(apiKey);
            authDto.setApi(APP_ARTIFACT_ID);
            // get authentication data
            final var userAuth = this.authenticationApi//
                    .checkUserApiAuthorization(authDto);
            // extract data
            final var userOrcid = userAuth.getUser().getOrcid();
            final var userEmail = userAuth.getUser().getEmail();
            final var userRoles = userAuth.getUserRoles();
            final var userAuthorizations = userAuth.getTokenAuthorizations();
            // init response
            final var user = new UserDetailsCore()//
                    .setOrcid(userOrcid)//
                    .setEmail(userEmail)//
                    .setRoles(new HashSet<>(userRoles))//
                    .setAuthorizations(new HashSet<>(userAuthorizations));
            // log
            logger.info("user '{}' authenticated with @orcid={}", userEmail,
                    userOrcid);
            // return
            return user;
        } catch (final ApiException e) {
            logger.error("unable to authenticate user: '{}'", e.getMessage());
        }
        return null;
    }

    /**
     * Clean old / deprecated sessions each 3 hours
     */
    @Scheduled(fixedRate = 3 * 60 * 60 * 1000)
    public void cronJobSch() {
        this.cleanUsersSessions(SESSION_VALIDITY);
    }

    private void cleanUsersSessions(final int sessionValidity) {
        // test each session
        for (var sessionUserEntry : sessionUsersLastUsed.entrySet()) {
            if (sessionUserEntry.getValue() != null
                    && (new Date()).getTime() - (sessionUserEntry.getValue()).getTime() > sessionValidity) {
                // session is deprecated - remove it!
                logger.info("Session for user {} deprecated.", //
                        sessionUserEntry.getKey());
                sessionUsersDetailsCore.remove(sessionUserEntry.getKey());
                sessionUsersLastUsed.remove(sessionUserEntry.getKey());
            }
        }
    }

}