View Javadoc
1   /*
2    * Copyright (c) 2002-2022, 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.web.user;
35  
36  import java.io.IOException;
37  import java.util.Enumeration;
38  import java.util.HashSet;
39  import java.util.Set;
40  import java.util.StringTokenizer;
41  
42  import javax.servlet.Filter;
43  import javax.servlet.FilterChain;
44  import javax.servlet.FilterConfig;
45  import javax.servlet.ServletException;
46  import javax.servlet.ServletRequest;
47  import javax.servlet.ServletResponse;
48  import javax.servlet.http.HttpServletRequest;
49  import javax.servlet.http.HttpServletResponse;
50  
51  import org.apache.commons.lang3.StringUtils;
52  import fr.paris.lutece.portal.service.admin.AccessDeniedException;
53  import fr.paris.lutece.portal.service.admin.AdminAuthenticationService;
54  import fr.paris.lutece.portal.service.admin.AdminUserService;
55  import fr.paris.lutece.portal.service.admin.PasswordResetException;
56  import fr.paris.lutece.portal.service.message.AdminMessage;
57  import fr.paris.lutece.portal.service.message.AdminMessageService;
58  import fr.paris.lutece.portal.service.security.SecurityTokenService;
59  import fr.paris.lutece.portal.service.security.UserNotSignedException;
60  import fr.paris.lutece.portal.service.util.AppLogService;
61  import fr.paris.lutece.portal.service.util.AppPathService;
62  import fr.paris.lutece.portal.service.util.AppPropertiesService;
63  import fr.paris.lutece.portal.web.constants.Messages;
64  import fr.paris.lutece.portal.web.constants.Parameters;
65  import fr.paris.lutece.util.url.UrlItem;
66  
67  /**
68   * Filter to prevent unauthenticated access to admin
69   */
70  public class AuthenticationFilter implements Filter
71  {
72      private static final String PROPERTY_URL_PREFIX = "path.jsp.admin.public.";
73      private static final String PROPERTY_URL_SUFFIX_LIST = "list";
74      private static final String CONSTANT_LIST_SEPARATOR = ",";
75      private static final String PROPERTY_RESET_EXCEPTION_MESSAGE = "User must reset his password.";
76      private static final String PROPERTY_JSP_URL_ADMIN_LOGOUT = "lutece.admin.logout.url";
77      private static final String JSP_URL_ADMIN_LOGIN = "jsp/admin/AdminLogin.jsp";
78  
79      /**
80       * {@inheritDoc}
81       */
82      @Override
83      public void init( FilterConfig config ) throws ServletException
84      {
85          // Do nothing
86      }
87  
88      /**
89       * {@inheritDoc}
90       */
91      @Override
92      public void destroy( )
93      {
94          // Do nothing
95      }
96  
97      /**
98       * {@inheritDoc}
99       */
100     @Override
101     public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException
102     {
103         HttpServletRequest req = (HttpServletRequest) request;
104         HttpServletResponse resp = (HttpServletResponse) response;
105 
106         AppLogService.debug( "Accessing url : {}", ( ) -> getResquestedUrl( req ) );
107 
108         if ( isPrivateUrl( req ) )
109         {
110             try
111             {
112                 filterAccess( req );
113             }
114             catch( UserNotSignedException e )
115             {
116                 AdminAuthenticationService.getInstance( ).setLoginNextUrl( req );
117 
118                 String strRedirectUrl = null;
119 
120                 if ( AdminAuthenticationService.getInstance( ).isExternalAuthentication( ) )
121                 {
122                     AppLogService.debug( "New session behind external authentication : {}", ( ) -> getResquestedUrl( req ) );
123 
124                     strRedirectUrl = AdminMessageService.getMessageUrl( req, Messages.MESSAGE_USER_NEW_SESSION, getRedirectUrlExternalAuthentication( req ),
125                             AdminMessage.TYPE_INFO );
126                 }
127                 else
128                 {
129                     AppLogService.debug( "Access NOT granted to url : {}", ( ) -> getResquestedUrl( req ) );
130 
131                     strRedirectUrl = AdminMessageService.getMessageUrl( req, Messages.MESSAGE_USER_NOT_AUTHENTICATED, getRedirectUrl( req ),
132                             AdminMessage.TYPE_WARNING );
133                 }
134 
135                 resp.sendRedirect( getAbsoluteUrl( req, strRedirectUrl ) );
136 
137                 return;
138             }
139             catch( AccessDeniedException e )
140             {
141                 AppLogService.debug( "Access NOT granted to url : {}", getResquestedUrl( req ) );
142 
143                 String strRedirectUrl = AdminMessageService.getMessageUrl( req, Messages.MESSAGE_AUTH_FAILURE, getRedirectUrl( req ), AdminMessage.TYPE_ERROR );
144                 resp.sendRedirect( getAbsoluteUrl( req, strRedirectUrl ) );
145 
146                 return;
147             }
148             catch( PasswordResetException e )
149             {
150                 if ( !getResquestedUrl( req ).equals( getChangePasswordUrl( req ) ) && !getResquestedUrl( req ).equals( getLoginUrl( req ) ) )
151                 {
152                     String strRedirectUrl = AdminMessageService.getMessageUrl( req, Messages.MESSAGE_USER_MUST_CHANGE_PASSWORD, getChangePasswordUrl( req ),
153                             AdminMessage.TYPE_ERROR );
154                     resp.sendRedirect( getAbsoluteUrl( req, strRedirectUrl ) );
155 
156                     return;
157                 }
158             }
159         }
160 
161         chain.doFilter( request, response );
162     }
163 
164     /**
165      * Build the url to redirect to if not logged. This is actually the login page of the authentication module, completed with the request parameters.
166      * 
167      * @param request
168      *            the http request
169      * @return the string representation of the redirection url - absolute - with request parameters.
170      */
171     private String getRedirectUrl( HttpServletRequest request )
172     {
173         String strLoginUrl = getLoginUrl( request );
174 
175         if ( strLoginUrl == null )
176         {
177             return null;
178         }
179 
180         UrlItem/url/UrlItem.html#UrlItem">UrlItem url = new UrlItem( strLoginUrl );
181 
182         return url.getUrl( );
183     }
184 
185     /**
186      * Get the absolute login url
187      *
188      * @param request
189      *            the http request
190      * @return the login url, in its absolute form
191      *
192      */
193     private String getLoginUrl( HttpServletRequest request )
194     {
195         String strLoginUrl = AdminAuthenticationService.getInstance( ).getLoginPageUrl( );
196 
197         return getAbsoluteUrl( request, strLoginUrl );
198     }
199 
200     /**
201      * Gets the logout url.
202      *
203      * @param request
204      *            the request
205      * @return the logout url
206      */
207     private String getLogoutUrl( HttpServletRequest request )
208     {
209         return getAbsoluteUrl( request, AppPropertiesService.getProperty( PROPERTY_JSP_URL_ADMIN_LOGOUT ) );
210     }
211 
212     /**
213      * Get the absolute login url
214      *
215      * @param request
216      *            the http request
217      * @return the login url, in its absolute form
218      *
219      */
220     private String getChangePasswordUrl( HttpServletRequest request )
221     {
222         String strChangePasswordUrl = AdminAuthenticationService.getInstance( ).getChangePasswordPageUrl( );
223 
224         return getAbsoluteUrl( request, strChangePasswordUrl );
225     }
226 
227     /**
228      * Check wether a given url is to be considered as private (ie that needs a successful authentication to be accessed) or public (ie that can be access
229      * without being authenticated)
230      * 
231      * @param request
232      *            the http request
233      * @return true if the url needs to be authenticated, false otherwise
234      *
235      */
236     private boolean isPrivateUrl( HttpServletRequest request )
237     {
238         String strUrl = getResquestedUrl( request );
239         Set<String> allowedUrlSet = createAllowedUrlSet( request );
240         return !allowedUrlSet.contains( strUrl ) && !isInPublicUrlList( request, strUrl );
241     }
242 
243     private Set<String> createAllowedUrlSet( HttpServletRequest request )
244     {
245         Set<String> set = new HashSet<>( );
246         set.add( getAbsoluteUrl( request, JSP_URL_ADMIN_LOGIN ) );
247         set.add( getLoginUrl( request ) );
248         set.add( getLogoutUrl( request ) );
249         return set;
250     }
251 
252     /**
253      * check that the access is granted
254      * 
255      * @param request
256      *            The HTTP request
257      * @throws AccessDeniedException
258      *             If the user is not allowed
259      * @throws UserNotSignedException
260      *             If the user is not signed
261      *
262      **/
263     private static void filterAccess( HttpServletRequest request ) throws UserNotSignedException, AccessDeniedException
264     {
265         if ( AdminAuthenticationService.getInstance( ).isExternalAuthentication( ) )
266         {
267             // The authentication is external
268             // Should register the user if it's not already done
269             AdminAuthenticationService.getInstance( ).getRemoteUser( request );
270         }
271         else
272         {
273             if ( AdminAuthenticationService.getInstance( ).getRegisteredUser( request ) == null )
274             {
275                 // Authentication is required to access to the admin
276                 throw new UserNotSignedException( );
277             }
278 
279             if ( AdminUserService.getAdminUser( request ).isPasswordReset( ) )
280             {
281                 throw new PasswordResetException( PROPERTY_RESET_EXCEPTION_MESSAGE );
282             }
283         }
284     }
285 
286     /**
287      * Checks if the requested is in the list of urls that are under jsp/admin but shouldn't be protected
288      * 
289      * @param request
290      *            the http request (provides the base path if needed)
291      * @param strRequestedUrl
292      *            the url to test : it should start with "http://" is absolute, or should be relative to the webapp root otherwise
293      * @return true if the url is in the list, false otherwise
294      *
295      */
296     private boolean isInPublicUrlList( HttpServletRequest request, String strRequestedUrl )
297     {
298         // recovers list from the
299         String strList = AppPropertiesService.getProperty( PROPERTY_URL_PREFIX + PROPERTY_URL_SUFFIX_LIST );
300 
301         // extracts each item (separated by a comma) from the includes list
302         StringTokenizer strTokens = new StringTokenizer( strList, CONSTANT_LIST_SEPARATOR );
303 
304         while ( strTokens.hasMoreTokens( ) )
305         {
306             String strName = strTokens.nextToken( );
307             String strUrl = AppPropertiesService.getProperty( PROPERTY_URL_PREFIX + strName );
308             strUrl = getAbsoluteUrl( request, strUrl );
309 
310             if ( strRequestedUrl.equals( strUrl ) )
311             {
312                 return true;
313             }
314         }
315 
316         return false;
317     }
318 
319     /**
320      * 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
321      * url should be given relatively to the webapp root.
322      * 
323      * @param request
324      *            the http request (provides the base path if needed)
325      * @param strUrl
326      *            the url to transform
327      * @return the corresponding absolute url
328      *
329      */
330     private String getAbsoluteUrl( HttpServletRequest request, String strUrl )
331     {
332         if ( ( strUrl != null ) && !strUrl.startsWith( "http://" ) && !strUrl.startsWith( "https://" ) )
333         {
334             return AppPathService.getBaseUrl( request ) + strUrl;
335         }
336 
337         return strUrl;
338     }
339 
340     /**
341      * Return the absolute representation of the requested url
342      * 
343      * @param request
344      *            the http request (provides the base path if needed)
345      * @return the requested url has a string
346      *
347      */
348     private String getResquestedUrl( HttpServletRequest request )
349     {
350         return AppPathService.getBaseUrl( request ) + request.getServletPath( ).substring( 1 );
351     }
352 
353     /**
354      * Build the url to redirect after opening a new session when using external admin authentication. This is actually the requested url if provided; else the
355      * admin autentication admin menu.
356      * 
357      * @param request
358      *            the http request
359      * @return the string representation of the redirection url - absolute - when using external admin authentication.
360      */
361     private String getRedirectUrlExternalAuthentication( HttpServletRequest request )
362     {
363         String strNextUrl = AdminAuthenticationService.getInstance( ).getLoginNextUrl( request );
364 
365         if ( StringUtils.isEmpty( strNextUrl ) )
366         {
367             strNextUrl = AppPathService.getAdminMenuUrl( );
368         }
369 
370         return strNextUrl;
371     }
372 }