View Javadoc
1   /*
2    * Copyright (c) 2002-2017, Mairie de 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.modules.cas.authentication;
35  
36  import fr.paris.lutece.plugins.mylutece.authentication.PortalAuthentication;
37  import fr.paris.lutece.plugins.mylutece.modules.cas.exception.CASAuthenticationException;
38  import fr.paris.lutece.plugins.mylutece.modules.cas.exception.CASUserKeyEmptyException;
39  import fr.paris.lutece.plugins.mylutece.modules.cas.exception.CASUserNotAuthorizedException;
40  import fr.paris.lutece.plugins.mylutece.modules.cas.service.CASPlugin;
41  import fr.paris.lutece.plugins.mylutece.modules.cas.service.ICASUserKeyService;
42  import fr.paris.lutece.portal.service.message.SiteMessage;
43  import fr.paris.lutece.portal.service.message.SiteMessageException;
44  import fr.paris.lutece.portal.service.message.SiteMessageService;
45  import fr.paris.lutece.portal.service.security.LoginRedirectException;
46  import fr.paris.lutece.portal.service.security.LuteceUser;
47  import fr.paris.lutece.portal.service.util.AppLogService;
48  import fr.paris.lutece.portal.service.util.AppPathService;
49  import fr.paris.lutece.portal.service.util.AppPropertiesService;
50  
51  import org.apache.commons.lang3.StringUtils;
52  
53  import org.jasig.cas.client.authentication.AttributePrincipal;
54  
55  import java.io.Serializable;
56  
57  import java.util.ArrayList;
58  import java.util.Arrays;
59  import java.util.HashMap;
60  import java.util.List;
61  import java.util.Map;
62  import java.util.Map.Entry;
63  
64  import javax.security.auth.login.LoginException;
65  
66  import javax.servlet.http.HttpServletRequest;
67  
68  
69  /**
70   * The class provides an implementation of the inherited abstract class
71   * PortalAuthentication based on CAS
72   *
73   */
74  public class CASAuthentication extends PortalAuthentication implements Serializable
75  {
76      // //////////////////////////////////////////////////////////////////////////////////////////////
77      // Constants
78  
79      /**
80           *
81           */
82      private static final long serialVersionUID = -4537783302819258998L;
83  
84      /** user roles key */
85      private static final String PROPRETY_ATTRIBUTE_ROLES = "mylutece-cas.attributeRoles";
86  
87      /** Lutece User Attributs */
88      public static final String PROPERTY_USER_MAPPING_ATTRIBUTES = "mylutece-cas.userMappingAttributes";
89      public static final String PROPERTY_USER_MANDATORY_ATTRIBUTES = "mylutece-cas.userMandatoryAttributes";
90      public static final String PROPERTY_ROLES_ASSOCIATIONS = "mylutece-cas.rolesAssociations";
91      public static final String PROPERTY_URL_ERROR_LOGIN_PAGE = "mylutece-cas.urlErrorLoginPage";
92      public static final String PROPERTY_BACK_URL_ERROR = "mylutece-cas.backUrlError";
93      public static final String PROPERTY_MESSAGE_ERROR_LOGIN = "module.mylutece.cas.message.error.login";
94  
95      /** Constants **/
96      public static final String CONSTANT_LUTECE_USER_PROPERTIES_PATH = "mylutece-cas.attribute";
97      public static final String CONSTANT_MANDATORY_ATTRIBUTE = "mylutece-cas.mandatoryAttribute";
98      public static final String CONSTANT_ROLE_ASSOCIATIONS_PATH = "mylutece-cas.roleAssociations";
99      public static final String CONSTANT_HTTP = "http://";
100     public static final String CONSTANT_HTTPS = "https://";
101     private static final String SEPARATOR = ",";
102     private String _strAuthServiceName;
103 
104     /** default role can be used and will be added to all users */
105     private String _strPropertyDefaultRoleName;
106     private String _strAttributeKeyUsername;
107     private ICASUserKeyService cASUserKeyService;
108 
109     /** Attributs */
110     private String[] ATTRIBUTE_ROLES;
111     private Map<String, String> USER_MANDATORY_ATTRIBUTES;
112     private Map<String, List<String>> ROLES_ASSOCIATIONS;
113     private Map<String, String> ATTRIBUTE_USER_MAPPING;
114 
115     /**
116      * Constructor
117      */
118     public CASAuthentication(  )
119     {
120         super(  );
121 
122         String strAttributes = AppPropertiesService.getProperty( PROPRETY_ATTRIBUTE_ROLES );
123 
124         if ( StringUtils.isNotBlank( strAttributes ) )
125         {
126             ATTRIBUTE_ROLES = strAttributes.split( SEPARATOR );
127         }
128         else
129         {
130             ATTRIBUTE_ROLES = new String[0];
131         }
132 
133         String strUserMappingAttributes = AppPropertiesService.getProperty( PROPERTY_USER_MAPPING_ATTRIBUTES );
134         ATTRIBUTE_USER_MAPPING = new HashMap<>(  );
135 
136         if ( StringUtils.isNotBlank( strUserMappingAttributes ) )
137         {
138             String[] tabUserProperties = strUserMappingAttributes.split( SEPARATOR );
139             String userPropertie;
140 
141             for ( int i = 0; i < tabUserProperties.length; i++ )
142             {
143                 userPropertie = AppPropertiesService.getProperty( CONSTANT_LUTECE_USER_PROPERTIES_PATH + "." +
144                         tabUserProperties[i] );
145 
146                 if ( StringUtils.isNotBlank( userPropertie ) )
147                 {
148                     ATTRIBUTE_USER_MAPPING.put( userPropertie, tabUserProperties[i] );
149                 }
150             }
151         }
152 
153         String strUserMandatoryAttributes = AppPropertiesService.getProperty( PROPERTY_USER_MANDATORY_ATTRIBUTES );
154         USER_MANDATORY_ATTRIBUTES = new HashMap<>(  );
155 
156         if ( StringUtils.isNotBlank( strUserMandatoryAttributes ) )
157         {
158             String[] tabUserMandatoryAttributes = strUserMandatoryAttributes.split( SEPARATOR );
159             String userMandatoryAttributes;
160 
161             for ( int i = 0; i < tabUserMandatoryAttributes.length; i++ )
162             {
163                 userMandatoryAttributes = AppPropertiesService.getProperty( CONSTANT_MANDATORY_ATTRIBUTE + "." +
164                         tabUserMandatoryAttributes[i] + ".value" );
165                 USER_MANDATORY_ATTRIBUTES.put( tabUserMandatoryAttributes[i], userMandatoryAttributes );
166             }
167         }
168 
169         String strRolesAssociations = AppPropertiesService.getProperty( PROPERTY_ROLES_ASSOCIATIONS );
170         ROLES_ASSOCIATIONS = new HashMap<>(  );
171 
172         if ( StringUtils.isNotBlank( strRolesAssociations ) )
173         {
174             String[] tabRolesAssociations = strRolesAssociations.split( SEPARATOR );
175             String strRoleAssociations;
176 
177             for ( int i = 0; i < tabRolesAssociations.length; i++ )
178             {
179                 strRoleAssociations = AppPropertiesService.getProperty( CONSTANT_ROLE_ASSOCIATIONS_PATH + "." +
180                         tabRolesAssociations[i] );
181 
182                 if ( StringUtils.isNotBlank( strRoleAssociations ) )
183                 {
184                     List<String> listAssociations = Arrays.asList( strRoleAssociations.split( SEPARATOR ) );
185                     ROLES_ASSOCIATIONS.put( tabRolesAssociations[i], listAssociations );
186                 }
187             }
188         }
189     }
190 
191     /**
192      * Gets the Authentication service name
193      *
194      * @return The name of the authentication service
195      */
196     public String getAuthServiceName(  )
197     {
198         if ( _strAuthServiceName == null )
199         {
200             _strAuthServiceName = AppPropertiesService.getProperty( "mylutece-cas.service.name" );
201         }
202 
203         return _strAuthServiceName;
204     }
205 
206     /**
207      * Gets the Authentication type
208      *
209      * @param request
210      *            The HTTP request
211      * @return The type of authentication
212      */
213     public String getAuthType( HttpServletRequest request )
214     {
215         return HttpServletRequest.BASIC_AUTH;
216     }
217     
218     
219     @Override
220     public LuteceUser login( String strUserName, String strUserPassword, HttpServletRequest request )
221         throws LoginException, LoginRedirectException
222     {
223     	LuteceUser user=null;
224     	try {
225     			user=getCasAuthenticatedUser(request);
226 			} catch (CASAuthenticationException e) {
227 		
228 			String strUrlErrorLoginPage = AppPropertiesService.getProperty( PROPERTY_URL_ERROR_LOGIN_PAGE );
229             String strBackUrlError = AppPropertiesService.getProperty( PROPERTY_BACK_URL_ERROR );
230 
231             if ( StringUtils.isEmpty( strUrlErrorLoginPage ) )
232             {
233                 try
234                 {
235                     SiteMessageService.setMessage( request, PROPERTY_MESSAGE_ERROR_LOGIN, null, " ", null, "",
236                         SiteMessage.TYPE_STOP, null, strBackUrlError );
237                 }
238                 catch ( SiteMessageException lme )
239                 {
240                     strUrlErrorLoginPage = SiteMessageService.setSiteMessageUrl( AppPathService.getPortalUrl(  ) );
241                 }
242             }
243 
244             if ( ( strUrlErrorLoginPage == null ) ||
245                     ( !strUrlErrorLoginPage.startsWith( CONSTANT_HTTP ) &&
246                     !strUrlErrorLoginPage.startsWith( CONSTANT_HTTPS ) ) )
247             {
248                 strUrlErrorLoginPage = AppPathService.getBaseUrl( request ) + strUrlErrorLoginPage;
249             }
250 
251             LoginRedirectException ex = new LoginRedirectException( strUrlErrorLoginPage );
252             throw ex;
253 		}
254 
255       
256         return user;
257     }
258 
259     /**
260      * Returns a Lutece user object if the user is already authenticated by the
261      * WSSO
262      *
263      * @param request
264      *            The HTTP request
265      * @return Returns A Lutece User
266      */
267     public LuteceUser getHttpAuthenticatedUser( HttpServletRequest request )
268     {
269     	LuteceUser user = null;
270     	try
271     	{
272     		user = getCasAuthenticatedUser( request );
273 		}
274     	catch ( CASAuthenticationException e )
275     	{
276     		AppLogService.error( e.getMessage( ), e );
277 		}
278 	
279 		 
280     	return user;
281     }
282     
283     
284     
285     /**
286      * Returns a user object if the user is already authenticated by the
287      * Cas
288      *
289      * @param request
290      *            The HTTP request
291      * @return Returns A Lutece User
292      */
293     private LuteceUser getCasAuthenticatedUser( HttpServletRequest request )throws CASAuthenticationException
294     {
295         AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal(  );
296 
297         if ( principal != null )
298         {
299             String strCASUserLogin = cASUserKeyService.getKey( principal.getName(  ),
300                     principal.getAttributes(  ).get( getAttributeUsernameKey(  ) ) );
301 
302             if ( strCASUserLogin != null )
303             {
304                 CASUser/plugins/mylutece/modules/cas/authentication/CASUser.html#CASUser">CASUser user = new CASUser( strCASUserLogin, this );
305                 List<String> listRoles = new ArrayList<>(  );
306 
307                 if ( StringUtils.isNotBlank( getDefaultRoleName(  ) ) )
308                 {
309                     listRoles.add( getDefaultRoleName(  ) );
310                 }
311 
312                 addUserRoles( principal, listRoles );
313                 user.setRoles( listRoles );
314 
315                 addUserAttributes( principal, user );
316 
317                 if ( !isAuthorized( user ) )
318                 {
319                 	AppLogService.debug( "Principal found, but user not Authorized" + principal.getName(  ) );
320                     throw new CASUserNotAuthorizedException();
321                 	
322                 }
323                 return user;
324             }
325             else
326             {
327                 AppLogService.error( "Principal found, but not username attribute can be found for " +
328                     principal.getName(  ) );
329                 throw new CASUserKeyEmptyException();
330             }
331         }
332 
333         return null;
334     }
335     
336 
337     /**
338      * Adds user role, according to {@link #ATTRIBUTE_ROLES} keys
339      *
340      * @param principal
341      *            principal
342      * @param roles
343      *            the roles list
344      */
345     private void addUserRoles( AttributePrincipal principal, List<String> roles )
346     {
347         for ( String strAttributeKey : ATTRIBUTE_ROLES )
348         {
349             Object attributeValue = principal.getAttributes(  ).get( strAttributeKey );
350 
351             if ( attributeValue instanceof String )
352             {
353                 roles.add( (String) attributeValue );
354                 addRolesAssociated( (String) attributeValue, roles );
355             }
356             else if ( attributeValue instanceof List )
357             {
358                 for ( Object oValue : (List) attributeValue )
359                 {
360                     if ( oValue instanceof String )
361                     {
362                         roles.add( (String) oValue );
363                         addRolesAssociated( (String) oValue, roles );
364                     }
365                 }
366             }
367         }
368     }
369 
370     /**
371      * Add all principal attributes to the user
372      *
373      * @param principal
374      *            the principal
375      * @param user
376      *            the user
377      */
378     private void addUserAttributes( AttributePrincipal principal, CASUser user )
379     {
380         String strValue;
381 
382         for ( Entry<String, Object> entry : ( (Map<String, Object>) principal.getAttributes(  ) ).entrySet(  ) )
383         {
384             strValue = null;
385 
386             if ( entry.getValue(  ) instanceof String )
387             {
388                 strValue = (String) entry.getValue(  );
389             }
390             else if ( entry.getValue(  ) instanceof List )
391             {
392                 strValue = getValueAttributeMultivalued( (List) entry.getValue(  ) );
393             }
394 
395             if ( strValue != null )
396             {
397                 if ( ATTRIBUTE_USER_MAPPING.containsKey( entry.getKey(  ) ) )
398                 {
399                     user.setUserInfo( ATTRIBUTE_USER_MAPPING.get( entry.getKey(  ) ), strValue );
400                 }
401                 else
402                 {
403                     user.setUserInfo( entry.getKey(  ), strValue );
404                 }
405             }
406         }
407     }
408 
409     /**
410      * This methods logout the user
411      *
412      * @param user
413      *            The user
414      */
415     public void logout( LuteceUser user )
416     {
417     }
418 
419     public String[] getRolesByUser( LuteceUser user )
420     {
421         return user.getRoles(  );
422     }
423 
424     /**
425      * This method returns an anonymous Lutece user
426      *
427      * @return An anonymous Lutece user
428      */
429     public LuteceUser getAnonymousUser(  )
430     {
431         return new CASUser( LuteceUser.ANONYMOUS_USERNAME, this );
432     }
433 
434     /**
435      * Checks that the current user is associated to a given role
436      *
437      * @param user
438      *            The user
439      * @param request
440      *            The HTTP request
441      * @param strRole
442      *            The role name
443      * @return Returns true if the user is associated to the role, otherwise
444      *         false
445      */
446     public boolean isUserInRole( LuteceUser user, HttpServletRequest request, String strRole )
447     {
448         if ( ( user == null ) || ( strRole == null ) )
449         {
450             return false;
451         }
452 
453         String[] roles = user.getRoles(  );
454 
455         if ( roles != null )
456         {
457             for ( int i = 0; i < roles.length; i++ )
458             {
459                 if ( strRole.equals( roles[i] ) )
460                 {
461                     return true;
462                 }
463             }
464         }
465 
466         return false;
467     }
468 
469     /**
470      * Returns true
471      *
472      * @return true
473      */
474     public boolean isExternalAuthentication(  )
475     {
476         return true;
477     }
478 
479     /**
480      *
481      * {@inheritDoc}
482      */
483     public String getName(  )
484     {
485         return CASPlugin.PLUGIN_NAME;
486     }
487 
488     /**
489      *
490      * {@inheritDoc}
491      */
492     public String getPluginName(  )
493     {
494         return CASPlugin.PLUGIN_NAME;
495     }
496 
497     public ICASUserKeyService getCASUserKeyService(  )
498     {
499         return cASUserKeyService;
500     }
501 
502     public void setCASUserKeyService( ICASUserKeyService cASUserKeyService )
503     {
504         this.cASUserKeyService = cASUserKeyService;
505     }
506 
507     /**
508      * Get the default role name property
509      * @return The default role name property
510      */
511     private String getDefaultRoleName(  )
512     {
513         if ( _strPropertyDefaultRoleName == null )
514         {
515             _strPropertyDefaultRoleName = AppPropertiesService.getProperty( "mylutece-cas.role.name" );
516         }
517 
518         return _strPropertyDefaultRoleName;
519     }
520 
521     /**
522      * Get the user name key attribute
523      * @return The user name key attribute
524      */
525     private String getAttributeUsernameKey(  )
526     {
527         if ( _strAttributeKeyUsername == null )
528         {
529             _strAttributeKeyUsername = AppPropertiesService.getProperty( "mylutece-cas.attributeKeyUsername" );
530         }
531 
532         return _strAttributeKeyUsername;
533     }
534 
535     /**
536      * Get the value of an attribute multivalued
537      * @param value the attribute value
538      * @return the value of an attribute multivalued
539      */
540     private String getValueAttributeMultivalued( List lValues )
541     {
542         StringBuffer strBuffer = new StringBuffer(  );
543         int ncpt = 1;
544 
545         for ( Object oValue : lValues )
546         {
547             if ( oValue instanceof String )
548             {
549                 strBuffer.append( (String) oValue );
550 
551                 if ( ncpt < lValues.size(  ) )
552                 {
553                     strBuffer.append( SEPARATOR );
554                 }
555 
556                 ncpt++;
557             }
558         }
559 
560         return strBuffer.toString(  );
561     }
562 
563     /**
564      * Add in the list of roles the roles associated to the given role passed in parameter
565      * @param strRole the role
566      * @param roles the roles list
567      */
568     private void addRolesAssociated( String strRole, List<String> roles )
569     {
570         if ( ROLES_ASSOCIATIONS.containsKey( strRole ) )
571         {
572             roles.addAll( ROLES_ASSOCIATIONS.get( strRole ) );
573         }
574     }
575 
576     /**
577      * return true if the user  is Authorized to be authenticate depending the mandatory attributes
578      * @param user LuteceUser
579      * @return true if the user is Authorized to be authenticate depending the mandatory attributes
580      */
581     private boolean isAuthorized( LuteceUser user )
582     {
583         if ( !USER_MANDATORY_ATTRIBUTES.isEmpty(  ) )
584         {
585             for ( Entry<String, String> entry : ( USER_MANDATORY_ATTRIBUTES ).entrySet(  ) )
586             {
587                 if ( ( StringUtils.isEmpty( user.getUserInfo( entry.getKey(  ) ) ) ) ||
588                         ( !StringUtils.isEmpty( entry.getValue(  ) ) &&
589                         !entry.getValue(  ).equals( user.getUserInfo( entry.getKey(  ) ) ) ) )
590                 {
591                     return false;
592                 }
593             }
594         }
595 
596         return true;
597     }
598 }