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.filter;
35
36 import java.io.IOException;
37 import java.util.Collection;
38
39 import javax.servlet.FilterChain;
40 import javax.servlet.FilterConfig;
41 import javax.servlet.ServletException;
42 import javax.servlet.ServletRequest;
43 import javax.servlet.ServletResponse;
44 import javax.servlet.http.HttpServletRequest;
45 import javax.servlet.http.HttpServletResponse;
46
47 import org.apache.logging.log4j.LogManager;
48 import org.apache.logging.log4j.Logger;
49
50 import fr.paris.lutece.portal.business.securityheader.SecurityHeader;
51 import fr.paris.lutece.portal.business.securityheader.SecurityHeaderPageCategory;
52 import fr.paris.lutece.portal.business.securityheader.SecurityHeaderType;
53 import fr.paris.lutece.portal.service.security.SecurityService;
54 import fr.paris.lutece.portal.service.securityheader.SecurityHeaderService;
55 import fr.paris.lutece.portal.service.spring.SpringContextService;
56 import fr.paris.lutece.portal.service.util.AppPathService;
57 import fr.paris.lutece.portal.service.util.AppPropertiesService;
58
59 /**
60 * Page security header filter
61 * This filter is used to add security headers to the response when the requested resource is a jsp page except
62 * for BO admin authenticated pages. For those pages, security headers adding is managed by AuthenticationFilter class.
63 * When the request resource is a API REST endpoint, security headers adding is done in RestApiSecurityHeaderFilter class
64 *
65 */
66 public class PageSecurityHeaderFilter implements javax.servlet.Filter
67 {
68
69 private Logger _logger = LogManager.getLogger( "lutece.securityHeader" );
70 private static final String PROPERTY_JSP_URL_PORTAL_LOGOUT = "mylutece.url.doLogout";
71
72 /**
73 * Initializes the filter
74 *
75 * @param filterConfig
76 * The filter config
77 * @throws ServletException
78 * If an error occured
79 */
80 public void init( FilterConfig filterConfig ) throws ServletException
81 {
82
83 }
84
85 /**
86 * Apply the filter
87 *
88 * @param request
89 * The HTTP request
90 * @param response
91 * The HTTP response
92 * @param filterChain
93 * The Filter Chain
94 * @throws IOException
95 * If an error occured
96 * @throws ServletException
97 * If an error occured
98 */
99 public void doFilter( ServletRequest request, ServletResponse response, FilterChain filterChain ) throws IOException, ServletException
100 {
101 HttpServletRequest req = ( HttpServletRequest ) request;
102 HttpServletResponse resp = ( HttpServletResponse )response;
103
104 addAllPagesHeaders( req, resp );
105 addFoAuthenticatedPagesHeaders( req, resp );
106 addFoLogoutPageHeaders( req, resp );
107
108 filterChain.doFilter( req, resp );
109 }
110
111 /**
112 * Adds active security headers that must be added to all pages to the response
113 *
114 * @param request
115 * The HTTP request
116 * @param response
117 * The HTTP response
118 */
119 private void addAllPagesHeaders( HttpServletRequest request, HttpServletResponse response )
120 {
121 addActiveHeaders( request, response, SecurityHeaderType.PAGE, SecurityHeaderPageCategory.ALL );
122 }
123
124 /**
125 * Adds active security headers that must be added to FO authenticated pages to the response
126 *
127 * @param request
128 * The HTTP request
129 * @param response
130 * The HTTP response
131 */
132 private void addFoAuthenticatedPagesHeaders( HttpServletRequest request, HttpServletResponse response )
133 {
134 if( SecurityService.getInstance( ).getRegisteredUser( request ) != null )
135 {
136 addActiveHeaders( request, response, SecurityHeaderType.PAGE, SecurityHeaderPageCategory.AUTHENTICATED_ADMIN_FRONT_OFFICE );
137 }
138 }
139
140 /**
141 * Adds active security headers that must be added to the logout page to the response
142 *
143 * @param request
144 * The HTTP request
145 * @param response
146 * The HTTP response
147 */
148 private void addFoLogoutPageHeaders( HttpServletRequest request, HttpServletResponse response )
149 {
150 String logoutPage = getAbsoluteUrl( request, AppPropertiesService.getProperty( PROPERTY_JSP_URL_PORTAL_LOGOUT ) );
151 if( logoutPage != null )
152 {
153 if( getRequestedUrl( request ).equals( logoutPage ) )
154 {
155 addActiveHeaders( request, response, SecurityHeaderType.PAGE, SecurityHeaderPageCategory.LOGOUT_FO );
156 }
157 }
158 }
159
160 /**
161 *
162 * Adds active security headers of the specified type and the specified category to the response
163 *
164 * @param request
165 * The HTTP request
166 * @param response
167 * The HTTP response
168 * @param typePage
169 * The type of page
170 * @param pageCategory
171 * The category of page (if type is equals to page)
172 */
173 private void addActiveHeaders( HttpServletRequest request, HttpServletResponse response, SecurityHeaderType typePage, SecurityHeaderPageCategory pageCategory )
174 {
175 Collection<SecurityHeader> securityHeadersToAddList = getSecurityHeaderService( ).findActive( typePage.getCode( ), pageCategory.getCode( ) );
176 if( securityHeadersToAddList != null )
177 {
178 for( SecurityHeader securityHeader : securityHeadersToAddList )
179 {
180 response.setHeader( securityHeader.getName( ), securityHeader.getValue( ) );
181 _logger.debug( "Security header added to page {} - name : {}, value : {} ", request.getServletPath( ), securityHeader.getName( ), securityHeader.getValue( ) );
182 }
183 }
184 }
185
186 /**
187 * Destroy the filter
188 */
189 public void destroy( )
190 {
191
192 }
193
194 /**
195 * Returns the security header service.
196 *
197 * @return the security header service
198 */
199 private SecurityHeaderService getSecurityHeaderService( )
200 {
201 return SpringContextService.getBean( "securityHeaderService" );
202 }
203
204 /**
205 * Return the absolute representation of the requested url
206 *
207 * @param request
208 * the http request (provides the base path if needed)
209 * @return the requested url has a string
210 *
211 */
212 private String getRequestedUrl( HttpServletRequest request )
213 {
214 return AppPathService.getBaseUrl( request ) + request.getServletPath( ).substring( 1 );
215 }
216
217 /**
218 * Returns the absolute url corresponding to the given one, if the later was found to be relative. An url starting with "http://" is absolute. A relative
219 * url should be given relatively to the webapp root.
220 *
221 * @param request
222 * the http request (provides the base path if needed)
223 * @param strUrl
224 * the url to transform
225 * @return the corresponding absolute url
226 *
227 */
228 private String getAbsoluteUrl( HttpServletRequest request, String strUrl )
229 {
230 if ( ( strUrl != null ) && !strUrl.startsWith( "http://" ) && !strUrl.startsWith( "https://" ) )
231 {
232 return AppPathService.getBaseUrl( request ) + strUrl;
233 }
234
235 return strUrl;
236 }
237 }