View Javadoc
1   /*
2    * Copyright (c) 2002-2025, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.portal.service.securityheader;
35  
36  import java.util.ArrayList;
37  import java.util.Collection;
38  import java.util.Collections;
39  import java.util.Comparator;
40  import java.util.HashMap;
41  import java.util.List;
42  import java.util.Locale;
43  import java.util.Map;
44  import java.util.stream.Collectors;
45  
46  import org.apache.logging.log4j.LogManager;
47  import org.apache.logging.log4j.Logger;
48  
49  import fr.paris.lutece.portal.business.securityheader.SecurityHeader;
50  import fr.paris.lutece.portal.business.securityheader.SecurityHeaderHome;
51  import fr.paris.lutece.portal.business.securityheader.SecurityHeaderPageCategory;
52  import fr.paris.lutece.portal.business.securityheader.SecurityHeaderType;
53  import fr.paris.lutece.util.ReferenceList;
54  
55  /**
56   * This class provides a service that offers methods to manage security headers. 
57   */
58  public class SecurityHeaderService 
59  {
60  	
61  	//This map contains the security headers loaded from the database. It prevents to make database calls when data are up to date.
62  	//It must be refreshed each time that an action is performed. Currently, These actions are creating a new header, modifying an 
63  	//existing header, removing a header and enabling or disabling a header
64  	private Map<String, SecurityHeader> _mapSecurityHeaders = new HashMap<String, SecurityHeader>( );
65  	
66  	Map<String, Map<String, List<SecurityHeader>>> _mapActiveSecurityHeadersForFilters = new HashMap<String, Map<String, List<SecurityHeader>>>( );
67  	
68  	//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
69  	//with data from database
70  	private boolean _bMapRefreshDone = false;
71  	
72  	private Logger _logger = LogManager.getLogger( "lutece.securityHeader" );
73  	
74  	/**
75  	 * Returns all the security headers that match specified name, type and page category (if not null for the latter).
76  	 * If page category is null (REST api case), this criterion is ignored.
77  	 * 
78  	 * @param strName
79  	 *           The name of the security header
80  	 * @param strType
81  	 *           The type of the security header
82  	 * @param pageCategory
83  	 *           The page category of the security header
84  	 * @return list od security headers matching the criteria
85  	 */
86  	public List<SecurityHeader> find( String strName, String strType, String pageCategory )
87  	{
88  		ArrayList<SecurityHeader> securityHeadersResultList = new ArrayList<SecurityHeader>( );
89  		
90  		for( SecurityHeader securityHeader : findAll( ) )
91  		{
92  			if( securityHeader.getName( ).equals( strName ) && securityHeader.getType( ).equals( strType ) )
93  			{
94  				if( pageCategory == null || securityHeader.getPageCategory( ).equals( pageCategory ) )
95  				{
96  					securityHeadersResultList.add( securityHeader );
97  				}				
98  			}
99  		}
100 		
101 		return securityHeadersResultList;
102 	}
103 	
104 	/**
105 	 * Returns all security headers from the specified type and the specified category (if not null)
106 	 * that are active (attribute is_enable = true).
107 	 * 
108 	 * @param strType
109 	 * @param strPageCategory
110 	 * @return collection on security headers
111 	 */
112 	public Collection<SecurityHeader> findActive( String strType, String strPageCategory )
113 	{
114 		if( !_bMapRefreshDone )
115 		{
116 			refreshSecurityHeadersMap( SecurityHeaderHome.findAll() );
117 		}
118 		if( _mapActiveSecurityHeadersForFilters.get(strType) != null )
119 		{
120 			return _mapActiveSecurityHeadersForFilters.get( strType ).get( strPageCategory );
121 		}
122 		return null;
123 	}
124 	
125 	/**
126 	 * Returns a collection of all security headers.
127 	 * 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).
128 	 * 
129 	 * @return collection of security headers
130 	 */
131 	public Collection<SecurityHeader> findAll( )
132     {
133 		Collection<SecurityHeader> securityHeadersList = null;
134 		
135 		if( _bMapRefreshDone )
136 		{
137 			securityHeadersList = _mapSecurityHeaders.values( );
138 		} 
139 		else
140 		{			
141 			securityHeadersList = SecurityHeaderHome.findAll( );
142 			refreshSecurityHeadersMap( securityHeadersList );	
143 		}
144 		
145 		return securityHeadersList;
146     }
147 	
148 	/**
149 	 * Returns a list of all security headers used in manage security headers page. They are sorted by type, page category and name.
150 	 * 
151 	 * @param locale
152 	 *           The locale
153 	 * @return list of all security headers
154 	 */
155 	public List<SecurityHeader> findAllSorted( Locale locale )
156     {
157 		List<SecurityHeader> securityHeadersList = findAll( ).stream().collect( Collectors.toList( ) );   	
158 		Collections.sort( securityHeadersList, Comparator.comparing( SecurityHeader::getType )
159 				                                         .thenComparing( SecurityHeader::getPageCategory, Comparator.nullsLast( Comparator.naturalOrder( ) ) )
160 				                                         .thenComparing( SecurityHeader::getName ) );
161     	
162 		return securityHeadersList;
163     }
164 	
165 	/**
166 	 * Returns the reference list of security headers types
167 	 * 
168 	 * @return ReferenceList object with security headers types
169 	 */
170 	public ReferenceList getTypeList( )
171 	{
172 		ReferenceList listTypes = new ReferenceList( );
173 		
174 		for (SecurityHeaderType type : SecurityHeaderType.values()) { 
175 			listTypes.addItem( type.getCode(), type.getCode() );
176 		}
177 		
178 		return listTypes;
179 	}
180 	
181 	/**
182 	 * Returns the reference list of security headers page categories
183 	 * 
184 	 * @return ReferenceList object with security headers page categories
185 	 */
186 	public ReferenceList getPageCategoryList( )
187 	{
188 		ReferenceList listPageCategory = new ReferenceList( );
189 		
190 		for (SecurityHeaderPageCategory pageCategory : SecurityHeaderPageCategory.values()) { 
191 			listPageCategory.addItem( pageCategory.getCode(), pageCategory.getCode() );
192 		}
193 		
194 		return listPageCategory;
195 	}
196 	
197 	/**
198 	 * Create the security header given in parameter. Like all the actions, this one invalidates the map containing the security headers.
199 	 * 
200 	 * @param securityHeader
201 	 *                The security header to create
202 	 */
203 	public void create( SecurityHeader securityHeader )
204 	{
205 		_logger.debug( "Security header to create : name : {}, value : {}, type : {}, page category : {}", securityHeader.getName( ), securityHeader.getValue( ), securityHeader.getType( ), securityHeader.getPageCategory( ) );
206 		SecurityHeaderHome.create( securityHeader );
207 		clearMapSecurityHeaders( );
208 		_logger.debug( "Security header created" );
209 	}
210 
211 	/**
212 	 * Update the security header given in parameter. Like all the actions, this one invalidates the map containing the security headers.
213 	 * 
214 	 * @param securityHeader
215 	 *                The security header to update
216 	 */
217 	public void update( SecurityHeader securityHeader )
218 	{
219 		_logger.debug( "Security header to update : id : {}, name : {}, value : {}, type : {}, page category : {}", securityHeader.getId( ), securityHeader.getName( ), securityHeader.getValue( ), securityHeader.getType( ), securityHeader.getPageCategory( ) );
220 		SecurityHeaderHome.update( securityHeader );
221 		clearMapSecurityHeaders( );
222 		_logger.debug( "Security header updated" );
223 	}
224 	
225 	/**
226 	 * Remove the security header given in parameter. Like all the actions, this one invalidates the map containing the security headers.
227 	 * 
228 	 * @param securityHeader
229 	 *                The security header to remove
230 	 */
231 	public void remove( int nSecurityHeaderId )
232 	{
233 		SecurityHeader securityHeader = _mapSecurityHeaders.get( String.valueOf( nSecurityHeaderId ) );
234 		_logger.debug( "Security header to delete : id : {}, name : {}, value : {}, type : {}, page category : {}", securityHeader.getId( ), securityHeader.getName( ), securityHeader.getValue( ), securityHeader.getType( ), securityHeader.getPageCategory( ) );
235 		SecurityHeaderHome.remove( nSecurityHeaderId );
236 		clearMapSecurityHeaders( );
237 		_logger.debug( "Security header deleted" );
238 	}
239 	
240 	/**
241 	 * Enable the security header with the id given in parameter. Like all the actions, this one invalidates the map containing the security headers.
242 	 * 
243 	 * @param nSecurityHeaderId
244 	 *                The id of the security header to enable
245 	 */
246 	public void enable( int nSecurityHeaderId )
247 	{
248 		SecurityHeader securityHeader = _mapSecurityHeaders.get( String.valueOf( nSecurityHeaderId ) );
249 		_logger.debug( "Security header to enable : id : {}, name : {}, value : {}, type : {}, page category : {}", securityHeader.getId( ), securityHeader.getName( ), securityHeader.getValue( ), securityHeader.getType( ), securityHeader.getPageCategory( ) );
250 		SecurityHeaderHome.updateIsActive( nSecurityHeaderId, true );
251 		clearMapSecurityHeaders( );
252 		_logger.debug( "Security header enabled" );
253 	}
254 	
255 	/**
256 	 * Disable the security header with the id given in parameter. Like all the actions, this one invalidates the map containing the security headers.
257 	 * 
258 	 * @param nSecurityHeaderId
259 	 *                The id of the security header to disable
260 	 */
261 	public void disable( int nSecurityHeaderId )
262 	{
263 		SecurityHeader securityHeader = _mapSecurityHeaders.get( String.valueOf( nSecurityHeaderId ) );
264 		_logger.debug( "Security header to disable : id : {}, name : {}, value : {}, type : {}, page category : {}", securityHeader.getId( ), securityHeader.getName( ), securityHeader.getValue( ), securityHeader.getType( ), securityHeader.getPageCategory( ) );
265 		SecurityHeaderHome.updateIsActive( nSecurityHeaderId, false );
266 		clearMapSecurityHeaders( );
267 		_logger.debug( "Security header disabled" );
268 	}
269 	
270 	/**
271 	 * Clears the map containing the security headers
272 	 * 
273 	 */
274 	private void clearMapSecurityHeaders( )
275 	{
276 		_mapSecurityHeaders.clear( );
277 		_mapActiveSecurityHeadersForFilters.clear( );
278 		_bMapRefreshDone = false;
279 		_logger.debug( "Security header maps cleared" );
280 	}
281 	
282 	/**
283 	 * 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.
284 	 * 
285 	 * @param securityHeadersList
286 	 *                 The security headers collection
287 	 */
288 	private void refreshSecurityHeadersMap( Collection<SecurityHeader> securityHeadersList )
289 	{
290 		for( SecurityHeader securityHeader : securityHeadersList )
291 		{
292 			_mapSecurityHeaders.put( String.valueOf( securityHeader.getId( ) ), securityHeader );
293 			
294 			if( securityHeader.isActive() )
295 			{
296 				_mapActiveSecurityHeadersForFilters.put( securityHeader.getType( ), addHeaderToTypeMap( securityHeader ) );
297 			}					
298 		}
299 		_bMapRefreshDone = true;
300 		_logger.debug( "Security header map refreshed" );
301 	}
302 	
303 	/**
304 	 * Adds a security header in the map of active security headers of the same type as the security header passed in parameter
305 	 * 
306 	 * @param securityHeader Security header to add to the map
307 	 * @return map of active security headers of the same type as the security header passed in parameter updated with this security header
308 	 */
309 	private Map<String, List<SecurityHeader>> addHeaderToTypeMap( SecurityHeader securityHeader )
310 	{
311 		//In _mapActiveSecurityHeadersForFilters, 2 keys are necessary to retrieve a list of security headers.
312 		//For Page headers, those keys are respectively type and page category
313 		//For Rest api headers, those keys are respectively type and null value. 
314 		//As page category is not irrelevant for rest api headers, they are grouped using the null key. 
315 		String firstKey = securityHeader.getType( );
316 		Map<String, List<SecurityHeader>> mapHeadersForType = _mapActiveSecurityHeadersForFilters.get( firstKey );
317 		List<SecurityHeader> headersListToUpdate = null;
318 		
319 		String secondKey = null;					
320 		if( securityHeader.getType( ).equals( SecurityHeaderType.PAGE.getCode( ) ) )
321 		{
322 			secondKey = securityHeader.getPageCategory( );
323 		}
324 		
325 		if( mapHeadersForType == null )
326 		{
327 			mapHeadersForType = new HashMap<String, List<SecurityHeader>>( );
328 			headersListToUpdate = new ArrayList<SecurityHeader>( );					
329 		}
330 		else
331 		{					
332 			headersListToUpdate = mapHeadersForType.get( secondKey );
333 			if( headersListToUpdate == null )
334 			{
335 				headersListToUpdate = new ArrayList<SecurityHeader>( );							
336 			}
337 		}
338 		headersListToUpdate.add( securityHeader );
339 		mapHeadersForType.put( secondKey, headersListToUpdate );
340 		
341 		return mapHeadersForType;
342 	}
343 }