SecurityHeaderService.java

package fr.paris.lutece.portal.service.securityheader;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import fr.paris.lutece.portal.business.securityheader.SecurityHeader;
import fr.paris.lutece.portal.business.securityheader.SecurityHeaderHome;
import fr.paris.lutece.portal.business.securityheader.SecurityHeaderPageCategory;
import fr.paris.lutece.portal.business.securityheader.SecurityHeaderType;
import fr.paris.lutece.util.ReferenceList;

/**
 * This class provides a service that offers methods to manage security headers. 
 */
public class SecurityHeaderService 
{
	
	//This map contains the security headers loaded from the database. It prevents to make database calls when data are up to date.
	//It must be refreshed each time that an action is performed. Currently, These actions are creating a new header, modifying an 
	//existing header, removing a header and enabling or disabling a header
	private Map<String, SecurityHeader> _mapSecurityHeaders = new HashMap<String, SecurityHeader>( );
	
	Map<String, Map<String, List<SecurityHeader>>> _mapActiveSecurityHeadersForFilters = new HashMap<String, Map<String, List<SecurityHeader>>>( );
	
	//This boolean indicates if a refresh has been done on the security headers map. In other words, it tells if the map is up to date
	//with data from database
	private boolean _bMapRefreshDone = false;
	
	private Logger _logger = LogManager.getLogger( "lutece.securityHeader" );
	
	/**
	 * Returns all the security headers that match specified name, type and page category (if not null for the latter).
	 * If page category is null (REST api case), this criterion is ignored.
	 * 
	 * @param strName
	 *           The name of the security header
	 * @param strType
	 *           The type of the security header
	 * @param pageCategory
	 *           The page category of the security header
	 * @return list od security headers matching the criteria
	 */
	public List<SecurityHeader> find( String strName, String strType, String pageCategory )
	{
		ArrayList<SecurityHeader> securityHeadersResultList = new ArrayList<SecurityHeader>( );
		
		for( SecurityHeader securityHeader : findAll( ) )
		{
			if( securityHeader.getName( ).equals( strName ) && securityHeader.getType( ).equals( strType ) )
			{
				if( pageCategory == null || securityHeader.getPageCategory( ).equals( pageCategory ) )
				{
					securityHeadersResultList.add( securityHeader );
				}				
			}
		}
		
		return securityHeadersResultList;
	}
	
	/**
	 * Returns all security headers from the specified type and the specified category (if not null)
	 * that are active (attribute is_enable = true).
	 * 
	 * @param strType
	 * @param strPageCategory
	 * @return collection on security headers
	 */
	public Collection<SecurityHeader> findActive( String strType, String strPageCategory )
	{
		if( !_bMapRefreshDone )
		{
			refreshSecurityHeadersMap( SecurityHeaderHome.findAll() );
		}
		if( _mapActiveSecurityHeadersForFilters.get(strType) != null )
		{
			return _mapActiveSecurityHeadersForFilters.get( strType ).get( strPageCategory );
		}
		return null;
	}
	
	/**
	 * Returns a collection of all security headers.
	 * Security headers are fetched from database (if data are stale) or from security headers map in memory (if the data in map are still up to date).
	 * 
	 * @return collection of security headers
	 */
	public Collection<SecurityHeader> findAll( )
    {
		Collection<SecurityHeader> securityHeadersList = null;
		
		if( _bMapRefreshDone )
		{
			securityHeadersList = _mapSecurityHeaders.values( );
		} 
		else
		{			
			securityHeadersList = SecurityHeaderHome.findAll( );
			refreshSecurityHeadersMap( securityHeadersList );	
		}
		
		return securityHeadersList;
    }
	
	/**
	 * Returns a list of all security headers used in manage security headers page. They are sorted by type, page category and name.
	 * 
	 * @param locale
	 *           The locale
	 * @return list of all security headers
	 */
	public List<SecurityHeader> findAllSorted( Locale locale )
    {
		List<SecurityHeader> securityHeadersList = findAll( ).stream().collect( Collectors.toList( ) );   	
		Collections.sort( securityHeadersList, Comparator.comparing( SecurityHeader::getType )
				                                         .thenComparing( SecurityHeader::getPageCategory, Comparator.nullsLast( Comparator.naturalOrder( ) ) )
				                                         .thenComparing( SecurityHeader::getName ) );
    	
		return securityHeadersList;
    }
	
	/**
	 * Returns the reference list of security headers types
	 * 
	 * @return ReferenceList object with security headers types
	 */
	public ReferenceList getTypeList( )
	{
		ReferenceList listTypes = new ReferenceList( );
		
		for (SecurityHeaderType type : SecurityHeaderType.values()) { 
			listTypes.addItem( type.getCode(), type.getCode() );
		}
		
		return listTypes;
	}
	
	/**
	 * Returns the reference list of security headers page categories
	 * 
	 * @return ReferenceList object with security headers page categories
	 */
	public ReferenceList getPageCategoryList( )
	{
		ReferenceList listPageCategory = new ReferenceList( );
		
		for (SecurityHeaderPageCategory pageCategory : SecurityHeaderPageCategory.values()) { 
			listPageCategory.addItem( pageCategory.getCode(), pageCategory.getCode() );
		}
		
		return listPageCategory;
	}
	
	/**
	 * Create the security header given in parameter. Like all the actions, this one invalidates the map containing the security headers.
	 * 
	 * @param securityHeader
	 *                The security header to create
	 */
	public void create( SecurityHeader securityHeader )
	{
		_logger.debug( "Security header to create : name : {}, value : {}, type : {}, page category : {}", securityHeader.getName( ), securityHeader.getValue( ), securityHeader.getType( ), securityHeader.getPageCategory( ) );
		SecurityHeaderHome.create( securityHeader );
		clearMapSecurityHeaders( );
		_logger.debug( "Security header created" );
	}

	/**
	 * Update the security header given in parameter. Like all the actions, this one invalidates the map containing the security headers.
	 * 
	 * @param securityHeader
	 *                The security header to update
	 */
	public void update( SecurityHeader securityHeader )
	{
		_logger.debug( "Security header to update : id : {}, name : {}, value : {}, type : {}, page category : {}", securityHeader.getId( ), securityHeader.getName( ), securityHeader.getValue( ), securityHeader.getType( ), securityHeader.getPageCategory( ) );
		SecurityHeaderHome.update( securityHeader );
		clearMapSecurityHeaders( );
		_logger.debug( "Security header updated" );
	}
	
	/**
	 * Remove the security header given in parameter. Like all the actions, this one invalidates the map containing the security headers.
	 * 
	 * @param securityHeader
	 *                The security header to remove
	 */
	public void remove( int nSecurityHeaderId )
	{
		SecurityHeader securityHeader = _mapSecurityHeaders.get( String.valueOf( nSecurityHeaderId ) );
		_logger.debug( "Security header to delete : id : {}, name : {}, value : {}, type : {}, page category : {}", securityHeader.getId( ), securityHeader.getName( ), securityHeader.getValue( ), securityHeader.getType( ), securityHeader.getPageCategory( ) );
		SecurityHeaderHome.remove( nSecurityHeaderId );
		clearMapSecurityHeaders( );
		_logger.debug( "Security header deleted" );
	}
	
	/**
	 * Enable the security header with the id given in parameter. Like all the actions, this one invalidates the map containing the security headers.
	 * 
	 * @param nSecurityHeaderId
	 *                The id of the security header to enable
	 */
	public void enable( int nSecurityHeaderId )
	{
		SecurityHeader securityHeader = _mapSecurityHeaders.get( String.valueOf( nSecurityHeaderId ) );
		_logger.debug( "Security header to enable : id : {}, name : {}, value : {}, type : {}, page category : {}", securityHeader.getId( ), securityHeader.getName( ), securityHeader.getValue( ), securityHeader.getType( ), securityHeader.getPageCategory( ) );
		SecurityHeaderHome.updateIsActive( nSecurityHeaderId, true );
		clearMapSecurityHeaders( );
		_logger.debug( "Security header enabled" );
	}
	
	/**
	 * Disable the security header with the id given in parameter. Like all the actions, this one invalidates the map containing the security headers.
	 * 
	 * @param nSecurityHeaderId
	 *                The id of the security header to disable
	 */
	public void disable( int nSecurityHeaderId )
	{
		SecurityHeader securityHeader = _mapSecurityHeaders.get( String.valueOf( nSecurityHeaderId ) );
		_logger.debug( "Security header to disable : id : {}, name : {}, value : {}, type : {}, page category : {}", securityHeader.getId( ), securityHeader.getName( ), securityHeader.getValue( ), securityHeader.getType( ), securityHeader.getPageCategory( ) );
		SecurityHeaderHome.updateIsActive( nSecurityHeaderId, false );
		clearMapSecurityHeaders( );
		_logger.debug( "Security header disabled" );
	}
	
	/**
	 * Clears the map containing the security headers
	 * 
	 */
	private void clearMapSecurityHeaders( )
	{
		_mapSecurityHeaders.clear( );
		_mapActiveSecurityHeadersForFilters.clear( );
		_bMapRefreshDone = false;
		_logger.debug( "Security header maps cleared" );
	}
	
	/**
	 * Refreshes the map of security headers with the list given in parameter. After a call to this method, data of the map are up to date with database data.
	 * 
	 * @param securityHeadersList
	 *                 The security headers collection
	 */
	private void refreshSecurityHeadersMap( Collection<SecurityHeader> securityHeadersList )
	{
		for( SecurityHeader securityHeader : securityHeadersList )
		{
			_mapSecurityHeaders.put( String.valueOf( securityHeader.getId( ) ), securityHeader );
			
			if( securityHeader.isActive() )
			{
				_mapActiveSecurityHeadersForFilters.put( securityHeader.getType( ), addHeaderToTypeMap( securityHeader ) );
			}					
		}
		_bMapRefreshDone = true;
		_logger.debug( "Security header map refreshed" );
	}
	
	/**
	 * Adds a security header in the map of active security headers of the same type as the security header passed in parameter
	 * 
	 * @param securityHeader Security header to add to the map
	 * @return map of active security headers of the same type as the security header passed in parameter updated with this security header
	 */
	private Map<String, List<SecurityHeader>> addHeaderToTypeMap( SecurityHeader securityHeader )
	{
		//In _mapActiveSecurityHeadersForFilters, 2 keys are necessary to retrieve a list of security headers.
		//For Page headers, those keys are respectively type and page category
		//For Rest api headers, those keys are respectively type and null value. 
		//As page category is not irrelevant for rest api headers, they are grouped using the null key. 
		String firstKey = securityHeader.getType( );
		Map<String, List<SecurityHeader>> mapHeadersForType = _mapActiveSecurityHeadersForFilters.get( firstKey );
		List<SecurityHeader> headersListToUpdate = null;
		
		String secondKey = null;					
		if( securityHeader.getType( ).equals( SecurityHeaderType.PAGE.getCode( ) ) )
		{
			secondKey = securityHeader.getPageCategory( );
		}
		
		if( mapHeadersForType == null )
		{
			mapHeadersForType = new HashMap<String, List<SecurityHeader>>( );
			headersListToUpdate = new ArrayList<SecurityHeader>( );					
		}
		else
		{					
			headersListToUpdate = mapHeadersForType.get( secondKey );
			if( headersListToUpdate == null )
			{
				headersListToUpdate = new ArrayList<SecurityHeader>( );							
			}
		}
		headersListToUpdate.add( securityHeader );
		mapHeadersForType.put( secondKey, headersListToUpdate );
		
		return mapHeadersForType;
	}
}