AdminAuthenticationService.java

/*
 * Copyright (c) 2002-2022, City of Paris
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice
 *     and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice
 *     and the following disclaimer in the documentation and/or other materials
 *     provided with the distribution.
 *
 *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
 *     contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * License 1.0
 */
package fr.paris.lutece.portal.service.admin;

import fr.paris.lutece.portal.service.security.AccessLogService;
import java.util.Collection;
import java.util.Enumeration;
import java.util.stream.Collectors;

import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;

import fr.paris.lutece.portal.business.user.AdminUser;
import fr.paris.lutece.portal.business.user.AdminUserHome;
import fr.paris.lutece.portal.business.user.authentication.AdminAuthentication;
import fr.paris.lutece.portal.business.user.authentication.LuteceDefaultAdminAuthentication;
import fr.paris.lutece.portal.business.workgroup.AdminWorkgroupHome;
import fr.paris.lutece.portal.service.security.AccessLoggerConstants;
import fr.paris.lutece.portal.service.security.UserNotSignedException;
import fr.paris.lutece.portal.service.spring.SpringContextService;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.util.url.UrlItem;

/**
 * This class provides a security service to register and check user authentication
 */
public final class AdminAuthenticationService
{
    /**
     * Session attribute that stores the AdminUser object attached to the session
     */
    private static final String ATTRIBUTE_ADMIN_USER = "lutece_admin_user";
    private static final String ATTRIBUTE_ADMIN_LOGIN_NEXT_URL = "luteceAdminLoginNextUrl";

    private static final String BEAN_ADMIN_AUTHENTICATION_MODULE = "adminAuthenticationModule";

    private static final String CONSTANT_ACTION_LOGIN_ADMINUSER = "user.loginAdminUser";
    private static final String CONSTANT_ACTION_LOGOUT_ADMINUSER = "user.logoutAdminUser";
    private static final String CONSTANT_BO = "BO";

    private static AdminAuthenticationService _singleton = new AdminAuthenticationService( );
    private static AdminAuthentication _authentication;
    private static boolean _bUseDefaultModule;

    /**
     * Private constructor
     */
    private AdminAuthenticationService( )
    {
    }

    /**
     * Initialize service
     */
    public static synchronized void init( )
    {
        _authentication = SpringContextService.getBean( BEAN_ADMIN_AUTHENTICATION_MODULE );
        AppLogService.info( "Authentication module loaded : {}", _authentication.getAuthServiceName( ) );

        if ( _authentication.getClass( ).equals( LuteceDefaultAdminAuthentication.class ) )
        {
            _bUseDefaultModule = true;
        }
    }

    /**
     * Get the unique instance of the Security Service
     * 
     * @return The instance
     */
    public static AdminAuthenticationService getInstance( )
    {
        return _singleton;
    }

    /**
     * Check whether the authentication service is configurer on the default module.
     * 
     * @return true if the default module is in use, false if another module is used
     */
    public boolean isDefaultModuleUsed( )
    {
        return _bUseDefaultModule;
    }

    /**
     * Gets the AdminUser attached to the current Http session
     *
     * @return A valid AdminUser object if found
     * @param request
     *            The Http request
     * @throws AccessDeniedException
     *             If the user cannot have access
     * @throws UserNotSignedException
     *             If the user is not signed
     */
    public AdminUser getRemoteUser( HttpServletRequest request ) throws UserNotSignedException, AccessDeniedException
    {
        AdminUser user = getRegisteredUser( request );

        if ( _authentication.isExternalAuthentication( ) )
        {
            if ( user == null )
            {
                // User is not registered by Lutece, but it may be authenticated by another system
                user = _authentication.getHttpAuthenticatedUser( request );
                registerUser( request, user );
                AdminUserService.updateDateLastLogin( user.getUserId( ) );

                // Start a new session
                throw new UserNotSignedException( );
            }
            else
            {
                // user is already in session. for external auth, we check that the user hasn't changed in the external module .
                AdminUser newUser = _authentication.getHttpAuthenticatedUser( request );

                if ( newUser == null )
                {
                    throw new AccessDeniedException( "User not found while retrieving from external authentication" );
                }
                else
                    if ( !newUser.getAccessCode( ).equals( user.getAccessCode( ) ) )
                    {
                        unregisterUser( request );
                        registerUser( request, newUser );
                        AdminUserService.updateDateLastLogin( user.getUserId( ) );

                        // Start a new session
                        throw new UserNotSignedException( );
                    }
            }
        }
        else
        // if not external authentication, just check if user is null or not
        {
            if ( user == null )
            {
                // user is not signed
                throw new UserNotSignedException( );
            }
        }

        return user;
    }

    /**
     * Checks user's login with the Authentication service.
     * 
     * @param request
     *            The Http request
     * @param strAccessCode
     *            The user's login
     * @param strPassword
     *            The user's password
     * @throws LoginException
     *             The LoginException
     */
    public void loginUser( HttpServletRequest request, final String strAccessCode, final String strPassword ) throws LoginException
    {
        AdminUser user = _authentication.login( strAccessCode, strPassword, request );

        AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_CONNECT, CONSTANT_ACTION_LOGIN_ADMINUSER, user, null, CONSTANT_BO );

        try
        {
            registerUser( request, user );
        }
        catch( UserNotSignedException | AccessDeniedException e )
        {
            throw new LoginException( );
        }

        AdminUserService.updateDateLastLogin( user.getUserId( ) );
    }

    /**
     * Logout the user
     * 
     * @param request
     *            The HTTP request
     */
    public void logoutUser( HttpServletRequest request )
    {
        AdminUser user;

        try
        {
            user = getRemoteUser( request );
        }
        catch( AccessDeniedException | UserNotSignedException e )
        {
            return;
        }

        _authentication.logout( user );
        unregisterUser( request );

        AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_CONNECT, CONSTANT_ACTION_LOGOUT_ADMINUSER, user, null, CONSTANT_BO );

    }

    /**
     * Bind user : complete module user with local settings (roles, etc)
     *
     * @param user
     *            The current user
     * @throws AccessDeniedException
     *             If the user cannot have access
     * @throws UserNotSignedException
     *             If the user is not signed
     * @return The AdminUser
     */
    private AdminUser bindUser( AdminUser user ) throws AccessDeniedException, UserNotSignedException
    {
        if ( user == null )
        {
            throw new UserNotSignedException( );
        }

        // retrieve the user in local system from the access code
        AdminUser bindUser = AdminUserHome.findUserByLogin( user.getAccessCode( ) );

        // only allow a user that is marked active
        if ( ( bindUser == null ) || ( !bindUser.isStatusActive( ) ) )
        {
            throw new AccessDeniedException( "User " + bindUser + " is null or not active" );
        }

        // set the rights for this user
        bindUser.setRights( AdminUserHome.getRightsListForUser( bindUser.getUserId( ) ) );

        // set the rights for this user
        bindUser.setRoles( AdminUserHome.getRolesListForUser( bindUser.getUserId( ) ) );

        // set the workgroups for this user
        bindUser.setUserWorkgroups( AdminWorkgroupHome.getUserWorkgroups( bindUser ).stream( ).map( x -> x.getCode( ) ).collect( Collectors.toList( ) ) );

        return bindUser;
    }

    /**
     * Register the user in the Http session
     *
     * @param request
     *            The Http request
     * @param user
     *            The current user
     * @throws AccessDeniedException
     *             If the user cannot have access
     * @throws UserNotSignedException
     *             If the user is not signed
     */
    public void registerUser( HttpServletRequest request, AdminUser user ) throws AccessDeniedException, UserNotSignedException
    {
        HttpSession session = request.getSession( true );
        session.setAttribute( ATTRIBUTE_ADMIN_USER, bindUser( user ) );
    }

    /**
     * Unregister the user in the Http session
     * 
     * @param request
     *            The Http request
     */
    public void unregisterUser( HttpServletRequest request )
    {
        HttpSession session = request.getSession( true );
        session.removeAttribute( ATTRIBUTE_ADMIN_USER );
    }

    /**
     * Gets the Lutece user registered in the Http session
     * 
     * @param request
     *            The HTTP request
     * @return The User registered or null if the user has not been registered
     */
    public AdminUser getRegisteredUser( HttpServletRequest request )
    {
        HttpSession session = request.getSession( );

        if ( session != null )
        {
            return (AdminUser) session.getAttribute( ATTRIBUTE_ADMIN_USER );
        }

        return null;
    }

    /**
     * Returns the authentication type : External or Lutece portal based
     * 
     * @return true if the user is already authenticated or false if it needs to login.
     */
    public boolean isExternalAuthentication( )
    {
        return _authentication.isExternalAuthentication( );
    }

    /**
     * Returns the Login page URL of the Authentication Service
     * 
     * @return The URL
     */
    public String getLoginPageUrl( )
    {
        return _authentication.getLoginPageUrl( );
    }

    /**
     * Returns the modification password page URL of the Authentication Service
     * 
     * @return The URL
     */
    public String getChangePasswordPageUrl( )
    {
        return _authentication.getChangePasswordPageUrl( );
    }

    /**
     * Returns the DoLogin URL of the Authentication Service
     * 
     * @return The URL
     */
    public String getDoLoginUrl( )
    {
        return _authentication.getDoLoginUrl( );
    }

    /**
     * Returns the DoLogout URL of the Authentication Service
     * 
     * @return The URL
     */
    public String getDoLogoutUrl( )
    {
        return _authentication.getDoLogoutUrl( );
    }

    /**
     * Returns the new account page URL of the Authentication Service
     * 
     * @return The URL
     */
    public String getNewAccountPageUrl( )
    {
        return _authentication.getNewAccountPageUrl( );
    }

    /**
     * Returns the view account page URL of the Authentication Service
     * 
     * @return The URL
     */
    public String getViewAccountPageUrl( )
    {
        return _authentication.getViewAccountPageUrl( );
    }

    /**
     * Returns the lost password URL of the Authentication Service
     * 
     * @return The URL
     */
    public String getLostPasswordPageUrl( )
    {
        return _authentication.getLostPasswordPageUrl( );
    }

    /**
     * Returns the lost login URL of the Authentication Service
     * 
     * @return The URL
     */
    public String getLostLoginPageUrl( )
    {
        return _authentication.getLostLoginPageUrl( );
    }

    /**
     * Returns the user list
     *
     * @return the collection of all users from the module
     * @param strLastName
     *            The last name
     * @param strFirstName
     *            The first name
     * @param strEmail
     *            The email
     */
    public Collection<AdminUser> getUserListFromModule( String strLastName, String strFirstName, String strEmail )
    {
        return _authentication.getUserList( strLastName, strFirstName, strEmail );
    }

    /**
     *
     * @param strAccessCode
     *            The login
     * @return The AdminUser
     */
    public AdminUser getUserPublicDataFromModule( String strAccessCode )
    {
        return _authentication.getUserPublicData( strAccessCode );
    }

    /**
     * Set the admin login next url
     * 
     * @param request
     *            the HTTP request
     */
    public void setLoginNextUrl( HttpServletRequest request )
    {
        String strNextUrl = request.getRequestURI( );
        UrlItem url = new UrlItem( strNextUrl );
        Enumeration enumParams = request.getParameterNames( );

        while ( enumParams.hasMoreElements( ) )
        {
            String strParamName = (String) enumParams.nextElement( );
            url.addParameter( strParamName, request.getParameter( strParamName ) );
        }

        HttpSession session = request.getSession( true );
        session.setAttribute( ATTRIBUTE_ADMIN_LOGIN_NEXT_URL, url.getUrl( ) );
    }

    /**
     * Returns the url (asked before login) to redirect after login
     * 
     * @param request
     *            The Http request
     * @return The url asked before login
     */
    public String getLoginNextUrl( HttpServletRequest request )
    {
        String strNextUrl = StringUtils.EMPTY;
        HttpSession session = request.getSession( false );

        if ( session != null )
        {
            strNextUrl = (String) session.getAttribute( ATTRIBUTE_ADMIN_LOGIN_NEXT_URL );
            session.removeAttribute( ATTRIBUTE_ADMIN_LOGIN_NEXT_URL );
        }

        return strNextUrl;
    }
}