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.modules.database.authentication.service;
35  
36  import java.util.ArrayList;
37  import java.util.Collection;
38  import java.util.HashMap;
39  import java.util.List;
40  import java.util.Locale;
41  import java.util.Map;
42  
43  import org.apache.commons.collections.CollectionUtils;
44  import org.apache.commons.lang3.StringUtils;
45  
46  import fr.paris.lutece.plugins.mylutece.business.attribute.AttributeHome;
47  import fr.paris.lutece.plugins.mylutece.business.attribute.IAttribute;
48  import fr.paris.lutece.plugins.mylutece.business.attribute.MyLuteceUserField;
49  import fr.paris.lutece.plugins.mylutece.business.attribute.MyLuteceUserFieldHome;
50  import fr.paris.lutece.plugins.mylutece.modules.database.authentication.business.DatabaseHome;
51  import fr.paris.lutece.plugins.mylutece.modules.database.authentication.business.DatabaseUser;
52  import fr.paris.lutece.plugins.mylutece.modules.database.authentication.business.DatabaseUserHome;
53  import fr.paris.lutece.plugins.mylutece.modules.database.authentication.service.parameter.DatabaseUserParameterService;
54  import fr.paris.lutece.plugins.mylutece.service.MyLutecePlugin;
55  import fr.paris.lutece.plugins.mylutece.service.attribute.MyLuteceUserFieldListenerService;
56  import fr.paris.lutece.plugins.mylutece.service.attribute.MyLuteceUserFieldService;
57  import fr.paris.lutece.plugins.mylutece.util.SecurityUtils;
58  import fr.paris.lutece.portal.service.csv.CSVMessageDescriptor;
59  import fr.paris.lutece.portal.service.csv.CSVMessageLevel;
60  import fr.paris.lutece.portal.service.csv.CSVReaderService;
61  import fr.paris.lutece.portal.service.i18n.I18nService;
62  import fr.paris.lutece.portal.service.mail.MailService;
63  import fr.paris.lutece.portal.service.plugin.Plugin;
64  import fr.paris.lutece.portal.service.plugin.PluginService;
65  import fr.paris.lutece.portal.service.spring.SpringContextService;
66  import fr.paris.lutece.portal.service.template.AppTemplateService;
67  import fr.paris.lutece.portal.service.util.AppLogService;
68  import fr.paris.lutece.portal.service.util.AppPathService;
69  import fr.paris.lutece.portal.service.util.AppPropertiesService;
70  import fr.paris.lutece.util.html.HtmlTemplate;
71  
72  /**
73   * Import database users from a CSV file
74   */
75  public class ImportDatabaseUserService extends CSVReaderService
76  {
77      private static final String MESSAGE_NO_STATUS = "module.mylutece.database.import_users_from_file.importNoStatus";
78      private static final String MESSAGE_ACCESS_CODE_ALREADY_USED = "module.mylutece.database.message.user_exist";
79      private static final String MESSAGE_EMAIL_ALREADY_USED = "module.mylutece.database.message.user_exist";
80      private static final String MESSAGE_USERS_IMPORTED = "module.mylutece.database.import_users_from_file.usersImported";
81      private static final String MESSAGE_ERROR_MIN_NUMBER_COLUMNS = "module.mylutece.database.import_users_from_file.messageErrorMinColumnNumber";
82      private static final String MESSAGE_ACCOUNT_IMPORTED_MAIL_SUBJECT = "module.mylutece.database.import_users_from_file.email.mailSubject";
83      private static final String MESSAGE_ERROR_IMPORTING_ATTRIBUTES = "module.mylutece.database.import_users_from_file.errorImportingAttributes";
84      private static final String PROPERTY_NO_REPLY_EMAIL = "mail.noreply.email";
85      private static final String PROPERTY_IMPORT_EXPORT_USER_SEPARATOR = "lutece.importExportUser.defaultSeparator";
86      private static final String PROPERTY_SITE_NAME = "lutece.name";
87      private static final String TEMPLATE_MAIL_USER_IMPORTED = "admin/plugins/mylutece/modules/database/mail_user_imported.html";
88      private static final String MARK_SITE_NAME = "site_name";
89      private static final String MARK_USER = "user";
90      private static final String MARK_SITE_LINK = "site_link";
91      private static final String MARK_PASSWORD = "password";
92      private static final String CONSTANT_DEFAULT_IMPORT_EXPORT_USER_SEPARATOR = ":";
93      private static final String CONSTANT_ROLE = "role";
94      private static final String CONSTANT_GROUP = "group";
95      private static final int CONSTANT_MINIMUM_COLUMNS_PER_LINE = 7;
96      private Character _strAttributesSeparator;
97      private boolean _bUpdateExistingUsers;
98      private DatabaseUserParameterService _userParamService = DatabaseUserParameterService.getService( );
99  
100     /**
101      * {@inheritDoc}
102      */
103     @Override
104     protected List<CSVMessageDescriptor> readLineOfCSVFile( String [ ] strLineDataArray, int nLineNumber, Locale locale, String strBaseUrl )
105     {
106         Plugin databasePlugin = PluginService.getPlugin( DatabasePlugin.PLUGIN_NAME );
107         Plugin mylutecePlugin = PluginService.getPlugin( MyLutecePlugin.PLUGIN_NAME );
108         List<CSVMessageDescriptor> listMessages = new ArrayList<>( );
109         int nIndex = 0;
110 
111         String strAccessCode = strLineDataArray [nIndex++];
112         String strLastName = strLineDataArray [nIndex++];
113         String strFirstName = strLineDataArray [nIndex++];
114         String strEmail = strLineDataArray [nIndex++];
115 
116         boolean bUpdateUser = getUpdateExistingUsers( );
117         int nUserId = 0;
118 
119         if ( bUpdateUser )
120         {
121             int nAccessCodeUserId = DatabaseUserHome.findDatabaseUserIdFromLogin( strAccessCode, databasePlugin );
122 
123             if ( nAccessCodeUserId > 0 )
124             {
125                 nUserId = nAccessCodeUserId;
126             }
127 
128             bUpdateUser = nUserId > 0;
129         }
130 
131         String strStatus = strLineDataArray [nIndex++];
132         int nStatus = 0;
133 
134         if ( StringUtils.isNotEmpty( strStatus ) && StringUtils.isNumeric( strStatus ) )
135         {
136             nStatus = Integer.parseInt( strStatus );
137         }
138         else
139         {
140             Object [ ] args = {
141                     strLastName, strFirstName, nStatus
142             };
143             String strMessage = I18nService.getLocalizedString( MESSAGE_NO_STATUS, args, locale );
144             CSVMessageDescriptor message = new CSVMessageDescriptor( CSVMessageLevel.INFO, nLineNumber, strMessage );
145             listMessages.add( message );
146         }
147 
148         // We ignore the password max valid date attribute because we changed the password.
149         nIndex++;
150         // We ignore the account max valid date attribute
151         nIndex++;
152 
153         DatabaseUserlugins/mylutece/modules/database/authentication/business/DatabaseUser.html#DatabaseUser">DatabaseUser user = new DatabaseUser( );
154 
155         user.setLogin( strAccessCode );
156         user.setLastName( strLastName );
157         user.setFirstName( strFirstName );
158         user.setEmail( strEmail );
159         user.setStatus( nStatus );
160 
161         if ( bUpdateUser )
162         {
163             user.setUserId( nUserId );
164             // We update the user
165             DatabaseService.getService( ).doUpdateUser( user, databasePlugin );
166         }
167         else
168         {
169             // We create the user
170             String strPassword = SecurityUtils.makePassword( _userParamService, databasePlugin );
171             DatabaseService.getService( ).doCreateUser( user, strPassword, databasePlugin );
172             notifyUserAccountCreated( user, strPassword, locale, AppPathService.getProdUrl( strBaseUrl ) );
173         }
174 
175         // We remove old roles, groups and attributes of the user
176         DatabaseHome.removeRolesForUser( user.getUserId( ), databasePlugin );
177         DatabaseHome.removeGroupsForUser( user.getUserId( ), databasePlugin );
178         MyLuteceUserFieldService.doRemoveUserFields( user.getUserId( ), locale );
179 
180         // We get every attributes, roles and groups of the user
181         Map<Integer, List<String>> mapAttributesValues = new HashMap<>( );
182         List<String> listRoles = new ArrayList<>( );
183         List<String> listGroups = new ArrayList<>( );
184 
185         while ( nIndex < strLineDataArray.length )
186         {
187             String strValue = strLineDataArray [nIndex];
188 
189             if ( StringUtils.isNotBlank( strValue ) && ( strValue.indexOf( getAttributesSeparator( ) ) > 0 ) )
190             {
191                 int nSeparatorIndex = strValue.indexOf( getAttributesSeparator( ) );
192                 String strLineId = strValue.substring( 0, nSeparatorIndex );
193 
194                 if ( StringUtils.isNotBlank( strLineId ) )
195                 {
196                     if ( StringUtils.equalsIgnoreCase( strLineId, CONSTANT_ROLE ) )
197                     {
198                         listRoles.add( strValue.substring( nSeparatorIndex + 1 ) );
199                     }
200                     else
201                         if ( StringUtils.equalsIgnoreCase( strLineId, CONSTANT_GROUP ) )
202                         {
203                             listGroups.add( strValue.substring( nSeparatorIndex + 1 ) );
204                         }
205                         else
206                         {
207                             int nAttributeId = Integer.parseInt( strLineId );
208 
209                             String strAttributeValue = strValue.substring( nSeparatorIndex + 1 );
210                             List<String> listValues = mapAttributesValues.get( nAttributeId );
211 
212                             if ( listValues == null )
213                             {
214                                 listValues = new ArrayList<>( );
215                             }
216 
217                             listValues.add( strAttributeValue );
218                             mapAttributesValues.put( nAttributeId, listValues );
219                         }
220                 }
221             }
222 
223             nIndex++;
224         }
225 
226         // We create roles
227         for ( String strRole : listRoles )
228         {
229             DatabaseHome.addRoleForUser( user.getUserId( ), strRole, databasePlugin );
230         }
231 
232         // We create groups
233         for ( String strGoup : listGroups )
234         {
235             DatabaseHome.addGroupForUser( user.getUserId( ), strGoup, databasePlugin );
236         }
237 
238         // We save the attributes found
239         List<IAttribute> listAttributes = AttributeHome.findAll( locale, mylutecePlugin );
240 
241         for ( IAttribute attribute : listAttributes )
242         {
243             List<String> listValues = mapAttributesValues.get( attribute.getIdAttribute( ) );
244 
245             if ( CollectionUtils.isNotEmpty( listValues ) )
246             {
247                 int nIdField = 0;
248                 boolean bMyLuteceAttribute = ( attribute.getPlugin( ) == null )
249                         || StringUtils.equals( attribute.getPlugin( ).getName( ), MyLutecePlugin.PLUGIN_NAME );
250 
251                 for ( String strValue : listValues )
252                 {
253                     int nSeparatorIndex = strValue.indexOf( getAttributesSeparator( ) );
254 
255                     if ( nSeparatorIndex >= 0 )
256                     {
257                         nIdField = 0;
258 
259                         try
260                         {
261                             nIdField = Integer.parseInt( strValue.substring( 0, nSeparatorIndex ) );
262                         }
263                         catch( NumberFormatException e )
264                         {
265                             nIdField = 0;
266                         }
267 
268                         strValue = strValue.substring( nSeparatorIndex + 1 );
269                     }
270                     else
271                     {
272                         nIdField = 0;
273                     }
274 
275                     String [ ] strValues = {
276                             strValue
277                     };
278 
279                     try
280                     {
281                         List<MyLuteceUserField> listUserFields = attribute.getUserFieldsData( strValues, user.getUserId( ) );
282 
283                         for ( MyLuteceUserField userField : listUserFields )
284                         {
285                             if ( userField != null )
286                             {
287                                 userField.getAttributeField( ).setIdField( nIdField );
288                                 MyLuteceUserFieldHome.create( userField, mylutecePlugin );
289                             }
290                         }
291 
292                         if ( !bMyLuteceAttribute )
293                         {
294                             for ( MyLuteceUserFieldListenerService myLuteceUserFieldListenerService : SpringContextService
295                                     .getBeansOfType( MyLuteceUserFieldListenerService.class ) )
296                             {
297                                 myLuteceUserFieldListenerService.doCreateUserFields( user.getUserId( ), listUserFields, locale );
298                             }
299                         }
300                     }
301                     catch( Exception e )
302                     {
303                         AppLogService.error( e.getMessage( ), e );
304 
305                         String strErrorMessage = I18nService.getLocalizedString( MESSAGE_ERROR_IMPORTING_ATTRIBUTES, locale );
306                         CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, nLineNumber, strErrorMessage );
307                         listMessages.add( error );
308                     }
309                 }
310             }
311         }
312 
313         return listMessages;
314     }
315 
316     /**
317      * {@inheritDoc}
318      */
319     @Override
320     protected List<CSVMessageDescriptor> checkLineOfCSVFile( String [ ] strLineDataArray, int nLineNumber, Locale locale )
321     {
322         int nMinColumnNumber = CONSTANT_MINIMUM_COLUMNS_PER_LINE;
323         Plugin databasePlugin = PluginService.getPlugin( DatabasePlugin.PLUGIN_NAME );
324         List<CSVMessageDescriptor> listMessages = new ArrayList<>( );
325 
326         if ( ( strLineDataArray == null ) || ( strLineDataArray.length < nMinColumnNumber ) )
327         {
328             int nNbCol;
329 
330             if ( strLineDataArray == null )
331             {
332                 nNbCol = 0;
333             }
334             else
335             {
336                 nNbCol = strLineDataArray.length;
337             }
338 
339             Object [ ] args = {
340                     nNbCol, nMinColumnNumber
341             };
342             String strErrorMessage = I18nService.getLocalizedString( MESSAGE_ERROR_MIN_NUMBER_COLUMNS, args, locale );
343             CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, nLineNumber, strErrorMessage );
344             listMessages.add( error );
345 
346             return listMessages;
347         }
348 
349         if ( !getUpdateExistingUsers( ) )
350         {
351             String strAccessCode = strLineDataArray [0];
352             String strEmail = strLineDataArray [3];
353 
354             if ( DatabaseUserHome.findDatabaseUserIdFromLogin( strAccessCode, databasePlugin ) > 0 )
355             {
356                 String strMessage = I18nService.getLocalizedString( MESSAGE_ACCESS_CODE_ALREADY_USED, locale );
357                 CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, nLineNumber, strMessage );
358                 listMessages.add( error );
359             }
360             else
361             {
362                 Collection<DatabaseUser> listUsers = DatabaseUserHome.findDatabaseUsersListForEmail( strEmail, databasePlugin );
363 
364                 if ( CollectionUtils.isNotEmpty( listUsers ) )
365                 {
366                     String strMessage = I18nService.getLocalizedString( MESSAGE_EMAIL_ALREADY_USED, locale );
367                     CSVMessageDescriptor error = new CSVMessageDescriptor( CSVMessageLevel.ERROR, nLineNumber, strMessage );
368                     listMessages.add( error );
369                 }
370             }
371         }
372 
373         return listMessages;
374     }
375 
376     /**
377      * {@inheritDoc}
378      */
379     @Override
380     protected List<CSVMessageDescriptor> getEndOfProcessMessages( int nNbLineParses, int nNbLinesWithoutErrors, Locale locale )
381     {
382         List<CSVMessageDescriptor> listMessages = new ArrayList<>( );
383         Object [ ] args = {
384                 nNbLineParses, nNbLinesWithoutErrors
385         };
386         String strMessageContent = I18nService.getLocalizedString( MESSAGE_USERS_IMPORTED, args, locale );
387         CSVMessageDescriptor message = new CSVMessageDescriptor( CSVMessageLevel.INFO, 0, strMessageContent );
388         listMessages.add( message );
389 
390         return listMessages;
391     }
392 
393     /**
394      * Notify a user of the creation of his account and give him his credentials
395      * 
396      * @param user
397      *            the user to notify
398      * @param strPassword
399      *            The password of the user
400      * @param locale
401      *            The locale
402      * @param strProdUrl
403      *            The prod URL
404      */
405     private void notifyUserAccountCreated( DatabaseUser user, String strPassword, Locale locale, String strProdUrl )
406     {
407         String strSenderEmail = AppPropertiesService.getProperty( PROPERTY_NO_REPLY_EMAIL );
408         String strSiteName = AppPropertiesService.getProperty( PROPERTY_SITE_NAME );
409 
410         String strEmailSubject = I18nService.getLocalizedString( MESSAGE_ACCOUNT_IMPORTED_MAIL_SUBJECT, new String [ ] {
411                 strSiteName
412         }, locale );
413         String strBaseURL = strProdUrl;
414         Map<String, Object> model = new HashMap<>( );
415         model.put( MARK_USER, user );
416         model.put( MARK_SITE_NAME, strSiteName );
417         model.put( MARK_SITE_LINK, MailService.getSiteLink( strBaseURL, true ) );
418         model.put( MARK_PASSWORD, strPassword );
419 
420         HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_MAIL_USER_IMPORTED, locale, model );
421 
422         MailService.sendMailHtml( user.getEmail( ), strSenderEmail, strSenderEmail, strEmailSubject, template.getHtml( ) );
423     }
424 
425     /**
426      * Get the separator used for attributes of admin users.
427      * 
428      * @return The separator
429      */
430     public Character getAttributesSeparator( )
431     {
432         if ( _strAttributesSeparator == null )
433         {
434             _strAttributesSeparator = AppPropertiesService.getProperty( PROPERTY_IMPORT_EXPORT_USER_SEPARATOR, CONSTANT_DEFAULT_IMPORT_EXPORT_USER_SEPARATOR )
435                     .charAt( 0 );
436         }
437 
438         return _strAttributesSeparator;
439     }
440 
441     /**
442      * Get the update users flag
443      * 
444      * @return True if existing users should be updated, false if they should be ignored.
445      */
446     public boolean getUpdateExistingUsers( )
447     {
448         return _bUpdateExistingUsers;
449     }
450 
451     /**
452      * Set the update users flag
453      * 
454      * @param bUpdateExistingUsers
455      *            True if existing users should be updated, false if they should be ignored.
456      */
457     public void setUpdateExistingUsers( boolean bUpdateExistingUsers )
458     {
459         this._bUpdateExistingUsers = bUpdateExistingUsers;
460     }
461 }