View Javadoc
1   /*
2    * Copyright (c) 2002-2021, 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.plugins.mylutece.web.security;
35  
36  import fr.paris.lutece.plugins.mylutece.service.security.AuthenticationFilterService;
37  import fr.paris.lutece.portal.service.message.SiteMessage;
38  import fr.paris.lutece.portal.service.message.SiteMessageException;
39  import fr.paris.lutece.portal.service.message.SiteMessageService;
40  import fr.paris.lutece.portal.service.security.LuteceUser;
41  import fr.paris.lutece.portal.service.security.SecurityService;
42  import fr.paris.lutece.portal.service.security.UserNotSignedException;
43  import fr.paris.lutece.portal.service.util.AppPathService;
44  import fr.paris.lutece.portal.service.util.AppPropertiesService;
45  import fr.paris.lutece.portal.web.PortalJspBean;
46  import fr.paris.lutece.portal.web.constants.Messages;
47  import fr.paris.lutece.util.url.UrlItem;
48  
49  import java.io.IOException;
50  import java.util.Arrays;
51  
52  import javax.security.auth.login.FailedLoginException;
53  import javax.servlet.Filter;
54  import javax.servlet.FilterChain;
55  import javax.servlet.FilterConfig;
56  import javax.servlet.ServletException;
57  import javax.servlet.ServletRequest;
58  import javax.servlet.ServletResponse;
59  import javax.servlet.http.HttpServletRequest;
60  import javax.servlet.http.HttpServletResponse;
61  
62  /**
63   * Filter to prevent unauthenticated access to site if site authentication is enabled
64   */
65  public class MyluteceAuthFilter implements Filter
66  {
67      private static final String URL_INTERROGATIVE = "?";
68      private static final String URL_AMPERSAND = "&";
69      private static final String URL_EQUAL = "=";
70      private static final String URL_STAR = "*";
71  
72      // Properties
73      private static final String PROPERTY_ACCESS_ROLE = "mylutece.role.AccessRole";
74  
75      // Attributes
76      private static final String ATTRIBUTE_ACCES_ROLE = AppPropertiesService.getProperty( PROPERTY_ACCESS_ROLE );
77  
78      /**
79       * {@inheritDoc}
80       */
81      @Override
82      public void init( FilterConfig config ) throws ServletException
83      {
84      }
85  
86      /**
87       * {@inheritDoc}
88       */
89      @Override
90      public void destroy( )
91      {
92          // Do nothing
93      }
94  
95      /**
96       * {@inheritDoc}
97       */
98      @Override
99      public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException
100     {
101         HttpServletRequest req = (HttpServletRequest) request;
102         HttpServletResponse resp = (HttpServletResponse) response;
103 
104         if ( SecurityService.isAuthenticationEnable( ) && SecurityService.getInstance( ).isPortalAuthenticationRequired( ) && isPrivateUrl( req ) )
105         {
106             try
107             {
108                 filterAccess( req );
109             }
110             catch( UserNotSignedException e )
111             {
112                 if ( SecurityService.getInstance( ).isExternalAuthentication( ) && !SecurityService.getInstance( ).isMultiAuthenticationSupported( ) )
113                 {
114                     try
115                     {
116                         SiteMessageService.setMessage( req, Messages.MESSAGE_USER_NOT_AUTHENTICATED, null, Messages.MESSAGE_USER_NOT_AUTHENTICATED, null, "",
117                                 SiteMessage.TYPE_STOP );
118                     }
119                     catch( SiteMessageException lme )
120                     {
121                         resp.sendRedirect( AppPathService.getSiteMessageUrl( req ) );
122                     }
123                 }
124                 else
125                 {
126                     resp.sendRedirect( PortalJspBean.redirectLogin( req ) );
127                 }
128 
129                 return;
130             }
131             catch( FailedLoginException e )
132             {
133                 try
134                 {
135                     SiteMessageService.setMessage( req, Messages.MESSAGE_AUTH_FAILURE, null, Messages.MESSAGE_AUTH_FAILURE, null, "", SiteMessage.TYPE_STOP );
136                 }
137                 catch( SiteMessageException lme )
138                 {
139                     resp.sendRedirect( AppPathService.getSiteMessageUrl( req ) );
140                 }
141             }
142         }
143 
144         chain.doFilter( request, response );
145     }
146 
147     /**
148      * 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
149      * without being authenticated)
150      *
151      * @param request
152      *            the http request
153      * @return true if the url needs to be authenticated, false otherwise
154      *
155      */
156     private boolean isPrivateUrl( HttpServletRequest request )
157     {
158         return !( ( isInSiteMessageUrl( request ) || ( isInPublicUrlList( request ) ) ) );
159     }
160 
161     /**
162      * check that the access is granted
163      *
164      * @param request
165      *            The HTTP request
166      *
167      * @throws UserNotSignedException
168      *             If the user is not signed
169      *
170      **/
171     private static void filterAccess( HttpServletRequest request ) throws UserNotSignedException, FailedLoginException
172     {
173         LuteceUser user = null;
174         // Try to register the user in case of external authentication
175         if ( SecurityService.getInstance( ).isExternalAuthentication( ) && !SecurityService.getInstance( ).isMultiAuthenticationSupported( ) )
176         {
177             // The authentication is external
178             // Should register the user if it's not already done
179             user = SecurityService.getInstance( ).getRegisteredUser( request );
180             if ( user == null )
181             {
182                 if ( ( SecurityService.getInstance( ).getRemoteUser( request ) == null )
183                         && ( SecurityService.getInstance( ).isPortalAuthenticationRequired( ) ) )
184                 {
185                     // Authentication is required to access to the portal
186                     throw new UserNotSignedException( );
187                 }
188             }
189         }
190         else
191         {
192             user = SecurityService.getInstance( ).getRegisteredUser( request );
193 
194             // no checks are needed if the user is already registered
195             if ( user == null )
196             {
197                 // if multiauthentication is supported, then when have to
198                 // check remote user before other check
199                 if ( SecurityService.getInstance( ).isMultiAuthenticationSupported( ) )
200                 {
201                     // getRemoteUser needs to be checked before any check so
202                     // the user is registered
203                     // getRemoteUser throws an exception if no user found,
204                     // but here we have to bypass this exception to display
205                     // login page.
206                     user = SecurityService.getInstance( ).getRemoteUser( request );
207                 }
208 
209                 // If portal authentication is enabled and user is null and
210                 // the requested URL
211                 // is not the login URL, user cannot access to Portal
212                 if ( user == null )
213                 {
214                     // Authentication is required to access to the portal
215                     throw new UserNotSignedException( );
216                 }
217             }
218         }
219         // check if the user have the right to access to the portal
220         if ( !isUserAccessRole( user ) )
221         {
222 
223             throw new FailedLoginException( );
224         }
225     }
226 
227     /**
228      * Checks if the requested is the url of site message
229      * 
230      * @param request
231      *            The HTTP request
232      * @return true if the requested is the url of site message
233      */
234     private boolean isInSiteMessageUrl( HttpServletRequest request )
235     {
236         return matchUrl( request, AppPathService.getSiteMessageUrl( request ) );
237     }
238 
239     /**
240      * Checks if the requested is in the list of urls defined in Security service that shouldn't be protected
241      *
242      * @param request
243      *            the http request
244      * 
245      * @return true if the url is in the list, false otherwise
246      *
247      */
248     private boolean isInPublicUrlList( HttpServletRequest request )
249     {
250         for ( String strPubliUrl : AuthenticationFilterService.getInstance( ).getPublicUrls( ) )
251         {
252             if ( matchUrl( request, strPubliUrl ) )
253             {
254                 return true;
255             }
256         }
257 
258         return false;
259     }
260 
261     /**
262      * method to test if the URL matches the pattern
263      * 
264      * @param request
265      *            the request
266      * @param strUrlPatern
267      *            the pattern
268      * @return true if the URL matches the pattern
269      */
270     private boolean matchUrl( HttpServletRequest request, String strUrlPatern )
271     {
272         boolean bMatch = false;
273 
274         if ( strUrlPatern != null )
275         {
276             UrlItem url = new UrlItem( getResquestedUrl( request ) );
277 
278             if ( strUrlPatern.contains( URL_INTERROGATIVE ) )
279             {
280                 for ( String strParamPatternValue : strUrlPatern.substring( strUrlPatern.indexOf( URL_INTERROGATIVE ) + 1 ).split( URL_AMPERSAND ) )
281                 {
282                     String [ ] arrayPatternParamValue = strParamPatternValue.split( URL_EQUAL );
283 
284                     if ( ( arrayPatternParamValue != null ) && ( request.getParameter( arrayPatternParamValue [0] ) != null ) )
285                     {
286                         url.addParameter( arrayPatternParamValue [0], request.getParameter( arrayPatternParamValue [0] ) );
287                     }
288                 }
289             }
290 
291             if ( strUrlPatern.contains( URL_STAR ) )
292             {
293                 String strUrlPaternLeftEnd = strUrlPatern.substring( 0, strUrlPatern.indexOf( URL_STAR ) );
294                 String strAbsoluteUrlPattern = getAbsoluteUrl( request, strUrlPaternLeftEnd );
295                 bMatch = url.getUrl( ).startsWith( strAbsoluteUrlPattern );
296             }
297             else
298             {
299                 String strAbsoluteUrlPattern = getAbsoluteUrl( request, strUrlPatern );
300                 bMatch = url.getUrl( ).equals( strAbsoluteUrlPattern );
301             }
302         }
303 
304         return bMatch;
305     }
306 
307     /**
308      * 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
309      * url should be given relatively to the webapp root.
310      *
311      * @param request
312      *            the http request (provides the base path if needed)
313      * @param strUrl
314      *            the url to transform
315      * @return the corresonding absolute url
316      *
317      */
318     private String getAbsoluteUrl( HttpServletRequest request, String strUrl )
319     {
320         if ( ( strUrl != null ) && !strUrl.startsWith( "http://" ) && !strUrl.startsWith( "https://" ) )
321         {
322             return AppPathService.getBaseUrl( request ) + strUrl;
323         }
324         else
325         {
326             return strUrl;
327         }
328     }
329 
330     /**
331      * Return the absolute representation of the requested url
332      *
333      * @param request
334      *            the http request (provides the base path if needed)
335      * @return the requested url has a string
336      *
337      */
338     private String getResquestedUrl( HttpServletRequest request )
339     {
340         return AppPathService.getBaseUrl( request ) + request.getServletPath( ).substring( 1 );
341     }
342 
343     /**
344      * Checks if the user have the role access
345      *
346      * @param user
347      *            the LuteceUser
348      * 
349      * @return true if the user have the access role
350      *
351      */
352     private static Boolean isUserAccessRole( LuteceUser user )
353     {
354         if ( ATTRIBUTE_ACCES_ROLE != null && !ATTRIBUTE_ACCES_ROLE.isEmpty( ) )
355         {
356             return Arrays.asList( user.getRoles( ) ).stream( ).anyMatch( str -> str.trim( ).equals( ATTRIBUTE_ACCES_ROLE.trim( ) ) );
357         }
358         return true;
359     }
360 }