SecurityService.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.security;

import fr.paris.lutece.portal.business.event.LuteceUserEvent;
import fr.paris.lutece.portal.service.datastore.DatastoreService;
import fr.paris.lutece.portal.service.event.LuteceUserEventManager;
import fr.paris.lutece.portal.service.init.LuteceInitException;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import fr.paris.lutece.util.url.UrlItem;

import java.security.Principal;

import java.util.Collection;
import java.util.Enumeration;
import java.util.List;

import javax.security.auth.login.LoginException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * This class provides a security service to register and check user authentication
 */
public final class SecurityService
{
    /**
     * Session attribute that stores the LuteceUser object attached to the session
     */
    private static final String ATTRIBUTE_LUTECE_USER = "lutece_user";

    private static final String PROPERTY_AUTHENTICATION_CLASS = "mylutece.authentication.class";
    private static final String PROPERTY_AUTHENTICATION_ENABLE = "mylutece.authentication.enable";
    private static final String PROPERTY_PORTAL_AUTHENTICATION_REQUIRED = "mylutece.portal.authentication.required";

    private static final String URL_INTERROGATIVE = "?";
    private static final String URL_AMPERSAND = "&";
    private static final String URL_EQUAL = "=";

    private static final String CONSTANT_ACTION_LOGIN_USER = "user.loginUser";
    private static final String CONSTANT_ACTION_LOGOUT_USER = "user.logoutUser";
    private static final String CONSTANT_FO = "FO";

    private static SecurityService _singleton = new SecurityService( );
    private static LuteceAuthentication _authenticationService;
    private static boolean _bEnable;

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

    /**
     * Initialize service
     * 
     * @throws LuteceInitException
     *             if an error occurs
     */
    public static synchronized void init( ) throws LuteceInitException
    {
        _bEnable = false;

        String strEnable = AppPropertiesService.getProperty( PROPERTY_AUTHENTICATION_ENABLE, "false" );

        if ( strEnable.equalsIgnoreCase( "true" ) )
        {
            _authenticationService = getPortalAuthentication( );

            if ( _authenticationService != null )
            {
                _bEnable = true;
            }
        }
        else
        {
            // in case authentication is disabled after having been enabled
            _authenticationService = null;
        }
    }

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

    /**
     * Returns the authentication's activation : enable or disable
     * 
     * @return true if the authentication is active, false otherwise
     */
    public static boolean isAuthenticationEnable( )
    {
        return _bEnable;
    }

    /**
     * Gets the LuteceUser attached to the current Http session
     * 
     * @param request
     *            The Http request
     * @return A LuteceUser object if found
     * @throws UserNotSignedException
     *             If there is no current user
     */
    public LuteceUser getRemoteUser( HttpServletRequest request ) throws UserNotSignedException
    {
        LuteceUser user = getRegisteredUser( request );

        if ( user == null )
        {
            // User is not registered by Lutece, but it may be authenticated by another
            // system
            if ( _authenticationService.isExternalAuthentication( ) || _authenticationService.isMultiAuthenticationSupported( ) )
            {
                user = _authenticationService.getHttpAuthenticatedUser( request );

                if ( ( user == null ) && isPortalAuthenticationRequired( ) )
                {
                    throw new UserNotSignedException( );
                }

                registerUser( request, user );
            }
            else
            {
                throw new UserNotSignedException( );
            }
        }

        return user;
    }

    /**
     * Returns the user's principal
     * 
     * @param request
     *            The HTTP request
     * @return The user's principal
     * @throws UserNotSignedException
     *             The UserNotSignedException
     */
    public Principal getUserPrincipal( HttpServletRequest request ) throws UserNotSignedException
    {
        return getRemoteUser( request );
    }

    /**
     * Checks if the user is associated to a given role
     * 
     * @param request
     *            The Http request
     * @param strRole
     *            The Role name
     * @return Returns true if the user is associated to the given role
     */
    public boolean isUserInRole( HttpServletRequest request, String strRole )
    {
        LuteceUser user;

        if ( !isAuthenticationEnable( ) )
        {
            return true;
        }

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

        return _authenticationService.isUserInRole( user, request, strRole );
    }

    /**
     * Checks if the user is associated to a at least a role
     * 
     * @param request
     *            The Http request
     * @param listRoles
     *            The Role list
     * @return Returns true if the user is associated to any role
     */
    public boolean isUserInAnyRole( HttpServletRequest request, List<String> listRoles )
    {
        boolean bAutorized = false;
        for ( String strRole : listRoles )
        {
            if ( isUserInRole( request, strRole ) )
            {
                bAutorized = true;
                break;
            }
        }
        return bAutorized;
    }

    /**
     * get all roles for this user : - user's roles - user's groups roles
     *
     * @param user
     *            The user
     * @return Array of roles
     */
    public String [ ] getRolesByUser( LuteceUser user )
    {
        return _authenticationService.getRolesByUser( user );
    }

    /**
     * Checks user's login with the Authentication service.
     * 
     * @param request
     *            The Http request
     * @param strUserName
     *            The user's login
     * @param strPassword
     *            The user's password
     * @throws LoginException
     *             The LoginException
     * @throws LoginRedirectException
     *             if redirect exception
     */
    public void loginUser( HttpServletRequest request, final String strUserName, final String strPassword ) throws LoginException, LoginRedirectException
    {
        LuteceUser user = _authenticationService.login( strUserName, strPassword, request );

        _authenticationService.updateDateLastLogin( user, request );

        if ( _authenticationService.findResetPassword( request, strUserName ) )
        {
            String redirect = _authenticationService.getResetPasswordPageUrl( request );
            registerUser( request, user );
            AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_CONNECT, CONSTANT_ACTION_LOGIN_USER, user, null, CONSTANT_FO );

            throw new LoginRedirectException( redirect );
        }

        registerUser( request, user );

        AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_CONNECT, CONSTANT_ACTION_LOGIN_USER, user, null, CONSTANT_FO );

    }

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

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

        _authenticationService.logout( user );
        HttpSession session = request.getSession( false );
        if(session!=null)
        {
        	session.invalidate();
        }
        LuteceUserEventManager.getInstance().notifyListeners( new LuteceUserEvent( user,LuteceUserEvent.EventType.LOGOUT ) );
        AccessLogService.getInstance( ).info( AccessLoggerConstants.EVENT_TYPE_DISCONNECT, CONSTANT_ACTION_LOGOUT_USER, user, null, CONSTANT_FO );
    }

    /**
     * Retrieves the portal authentication service configured in the config.properties
     * 
     * @return A PortalAuthentication object
     * @throws LuteceInitException
     *             If an error occurred
     */
    private static LuteceAuthentication getPortalAuthentication( ) throws LuteceInitException
    {
        String strAuthenticationClass = AppPropertiesService.getProperty( PROPERTY_AUTHENTICATION_CLASS );
        LuteceAuthentication authentication = null;

        if ( ( strAuthenticationClass != null ) && !strAuthenticationClass.equals( "" ) )
        {
            try
            {
                authentication = (LuteceAuthentication) Class.forName( strAuthenticationClass ).newInstance( );
                AppLogService.info( "Authentication service loaded : {}", authentication.getAuthServiceName( ) );
            }
            catch( InstantiationException | IllegalAccessException | ClassNotFoundException e )
            {
                throw new LuteceInitException( "Error instantiating Authentication Class", e );
            }
        }

        return authentication;
    }

    /**
     * Register the user in the Http session
     * 
     * @param request
     *            The Http request
     * @param user
     *            The current user
     */
    public void registerUser( HttpServletRequest request, LuteceUser user )
    {
        HttpSession session = request.getSession( true );
        session.setAttribute( ATTRIBUTE_LUTECE_USER, user );
        
        if ( user != null )
        {
        	LuteceUserEventManager.getInstance().notifyListeners( new LuteceUserEvent( user, LuteceUserEvent.EventType.LOGIN_SUCCESSFUL ) );
        }
    }

    /**
     * Unregister the user in the Http session
     * 
     * @param request
     *            The Http request     
     */
    public void unregisterUser( HttpServletRequest request )
    {
        HttpSession session = request.getSession( true );
        LuteceUser user = (LuteceUser)session.getAttribute( ATTRIBUTE_LUTECE_USER );
        
        if ( user != null )
        {
        	session.removeAttribute( ATTRIBUTE_LUTECE_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 LuteceUser getRegisteredUser( HttpServletRequest request )
    {
        HttpSession session = ( request != null ) ? request.getSession( false ) : null;

        if ( session != null )
        {
            return (LuteceUser) session.getAttribute( ATTRIBUTE_LUTECE_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 _authenticationService.isExternalAuthentication( );
    }

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

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

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

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

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

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

    // Added in v1.3

    /**
     * Returns the access denied template
     * 
     * @return The template
     */
    public String getAccessDeniedTemplate( )
    {
        return _authenticationService.getAccessDeniedTemplate( );
    }

    /**
     * Returns the access controled template
     * 
     * @return The template
     */
    public String getAccessControledTemplate( )
    {
        return _authenticationService.getAccessControledTemplate( );
    }

    /**
     * Returns whether or not the portal needs authentication
     * 
     * @return true if the access needs authentication, otherwise
     * @since 1.3.1
     */
    public boolean isPortalAuthenticationRequired( )
    {
        String strAuthenticationRequired = DatastoreService.getDataValue( PROPERTY_PORTAL_AUTHENTICATION_REQUIRED, "false" );

        return strAuthenticationRequired.equals( "true" );
    }

    /**
     * Checks user's login with the Authentication service. Used during remote authentication validation We don't have to put user informations in session,
     * since it is only used in external applications
     * 
     * @param request
     *            the request
     * @param strUserName
     *            The user's login
     * @param strPassword
     *            The user's password
     * @return user's informations
     * @throws LoginException
     *             The LoginException
     * @throws LoginRedirectException
     *             The redirect exception
     */
    public LuteceUser remoteLoginUser( final HttpServletRequest request, final String strUserName, final String strPassword )
            throws LoginException, LoginRedirectException
    {
        return _authenticationService.login( strUserName, strPassword, request );
    }

    /**
     * Return true if the requested url is equal to LoginUrl
     * 
     * @param request
     *            The Http servlet request
     * @return True if the requested url is equal to LoginUrl, false else.
     */
    public boolean isLoginUrl( HttpServletRequest request )
    {
        if ( ( getLoginPageUrl( ) == null ) || ( request == null ) )
        {
            return false;
        }

        String strRequestUrl = request.getRequestURI( );
        UrlItem url = new UrlItem( strRequestUrl );

        for ( String strParamValueLoginPageUrl : getLoginPageUrl( ).substring( getLoginPageUrl( ).indexOf( URL_INTERROGATIVE ) + 1 ).split( URL_AMPERSAND ) )
        {
            String [ ] arrayParamValueLoginPageUrl = strParamValueLoginPageUrl.split( URL_EQUAL );
            Enumeration<String> enumParams = request.getParameterNames( );

            while ( enumParams.hasMoreElements( ) )
            {
                String strRequestParameter = enumParams.nextElement( );

                if ( arrayParamValueLoginPageUrl [0].equals( strRequestParameter )
                        && arrayParamValueLoginPageUrl [1].equals( request.getParameter( strRequestParameter ) ) )
                {
                    url.addParameter( strRequestParameter, request.getParameter( strRequestParameter ) );
                }
            }
        }

        return url.getUrl( ).endsWith( getLoginPageUrl( ) ) && !getLoginPageUrl( ).equals( "" );
    }

    /**
     * Tells whether or not the authentication service can provide a list of all its users
     * 
     * @return true if the service can return a users list
     */
    boolean isUsersListAvailable( )
    {
        return _authenticationService.isUsersListAvailable( );
    }

    /**
     * Returns all users managed by the authentication service if this feature is available.
     * 
     * @return A collection of Lutece users or null if the service doesn't provide a users list
     */
    public Collection<LuteceUser> getUsers( )
    {
        return _authenticationService.getUsers( );
    }

    /**
     * Returns user managed by the authentication service if this feature is available.
     * 
     * @param strUserLogin
     *            the user login
     * @return A Lutece user or null if the service doesn't provide LuteceUser
     */
    public LuteceUser getUser( String strUserLogin )
    {
        return _authenticationService.getUser( strUserLogin );
    }

    /**
     * <b>true</b> when the service provides multi authentication support
     * 
     * @return <code>true</code> if multi authentication is supported, <code>false</code> otherwise.
     */
    public boolean isMultiAuthenticationSupported( )
    {
        return _authenticationService.isMultiAuthenticationSupported( );
    }

    /**
     * Gets the actual authentication implementation
     * 
     * @return {@link LuteceAuthentication} implementation
     */
    public LuteceAuthentication getAuthenticationService( )
    {
        return _authenticationService;
    }
}