001/*
002 *  Copyright 2012 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.web.usermanagement;
017
018import java.io.IOException;
019import java.sql.Connection;
020import java.sql.SQLException;
021import java.sql.Timestamp;
022import java.time.LocalDate;
023import java.time.ZoneId;
024import java.time.ZonedDateTime;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Date;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.Map.Entry;
032import java.util.Optional;
033import java.util.Set;
034import java.util.UUID;
035import java.util.stream.Collectors;
036
037import org.apache.avalon.framework.configuration.Configuration;
038import org.apache.avalon.framework.configuration.ConfigurationException;
039import org.apache.avalon.framework.service.ServiceException;
040import org.apache.avalon.framework.service.ServiceManager;
041import org.apache.commons.lang.StringUtils;
042import org.apache.commons.lang3.RandomStringUtils;
043import org.apache.ibatis.session.RowBounds;
044import org.apache.ibatis.session.SqlSession;
045
046import org.ametys.cms.repository.Content;
047import org.ametys.cms.transformation.xslt.ResolveURIComponent;
048import org.ametys.core.datasource.AbstractMyBatisDAO;
049import org.ametys.core.datasource.ConnectionHelper;
050import org.ametys.core.right.RightManager;
051import org.ametys.core.right.RightManager.RightResult;
052import org.ametys.core.trace.ForensicLogger;
053import org.ametys.core.ui.Callable;
054import org.ametys.core.user.CurrentUserProvider;
055import org.ametys.core.user.InvalidModificationException;
056import org.ametys.core.user.User;
057import org.ametys.core.user.User.UserCreationOrigin;
058import org.ametys.core.user.UserIdentity;
059import org.ametys.core.user.UserManager;
060import org.ametys.core.user.directory.ModifiableUserDirectory;
061import org.ametys.core.user.directory.NotUniqueUserException;
062import org.ametys.core.user.directory.UserDirectory;
063import org.ametys.core.user.directory.UserDirectoryFactory;
064import org.ametys.core.user.directory.UserDirectoryModel;
065import org.ametys.core.user.population.PopulationContextHelper;
066import org.ametys.core.user.population.UserPopulation;
067import org.ametys.core.user.population.UserPopulationDAO;
068import org.ametys.core.util.I18nUtils;
069import org.ametys.core.util.URIUtils;
070import org.ametys.core.util.mail.SendMailHelper;
071import org.ametys.plugins.repository.AmetysObjectIterable;
072import org.ametys.plugins.repository.AmetysObjectResolver;
073import org.ametys.plugins.repository.AmetysRepositoryException;
074import org.ametys.plugins.repository.query.expression.Expression;
075import org.ametys.plugins.repository.query.expression.Expression.Operator;
076import org.ametys.runtime.i18n.I18nizableText;
077import org.ametys.runtime.i18n.I18nizableTextParameter;
078import org.ametys.runtime.parameter.Errors;
079import org.ametys.web.renderingcontext.RenderingContext;
080import org.ametys.web.renderingcontext.RenderingContextHandler;
081import org.ametys.web.repository.page.Page;
082import org.ametys.web.repository.page.PageQueryHelper;
083import org.ametys.web.repository.page.Zone;
084import org.ametys.web.repository.page.ZoneItem;
085import org.ametys.web.repository.page.ZoneItem.ZoneType;
086import org.ametys.web.repository.site.Site;
087import org.ametys.web.repository.site.SiteManager;
088import org.ametys.web.site.SiteConfigurationExtensionPoint;
089import org.ametys.web.tags.TagExpression;
090import org.ametys.web.usermanagement.UserManagementException.StatusError;
091import org.ametys.web.usermanagement.UserSignupManager.TempUser.TempUserOrigin;
092
093import com.google.common.collect.Multimap;
094
095import jakarta.mail.MessagingException;
096
097/**
098 * Manages registration and password recovery of users.
099 */
100public class UserSignupManager extends AbstractMyBatisDAO
101{
102
103    /** The component role. */
104    public static final String ROLE = UserSignupManager.class.getName();
105    
106    /** The site parameter name for signup type */
107    public static final String SITE_PARAM_SIGNUP_TYPE = "signup-type";
108    
109    /** The Signup Service parameter : userDirectory */
110    public static final String SIGNUP_SERVICE_PARAMETER_USERDIRECTORY = "userdirectory";
111    
112    /** Tag for signup pages */
113    public static final String SIGNUP_PAGE_TAG_NAME = "USER_SIGNUP";
114    
115    /** Id of service for signup pages */
116    public static final String SIGNUP_PAGE_SERVICE_ID = "org.ametys.web.service.UserSignup";
117    
118    /** Tag for password pages */
119    public static final String CHANGE_PASSWORD_PAGE_TAG_NAME = "USER_PASSWORD_CHANGE";
120    
121    /** Id of service for signup pages */
122    public static final String CHANGE_PASSWORD_PAGE_SERVICE_ID = "org.ametys.web.service.UserPassword";
123
124    private static final String __EVENT_ACCOUNT_TEMP_SIGNUP = "account.temporary.signup";
125    private static final String __EVENT_ACCOUNT_CREATED = "account.created";
126    private static final String __EVENT_ACCOUNT_PASSWORD_RESET = "account.password.reset";
127    private static final String __EVENT_ACCOUNT_PASSWORD_CHANGE = "account.password.change";
128    private static final String __EVENT_ACCOUNT_PASSWORD_CHANGE_FAILED = "account.password.change.failed";
129
130    /** The user manager. */
131    protected UserManager _userManager;
132    
133    /** The DAO for user populations */
134    protected UserPopulationDAO _userPopulationDAO;
135
136    /** The site manager. */
137    protected SiteManager _siteManager;
138
139    /** The site configuration extension point. */
140    protected SiteConfigurationExtensionPoint _siteConf;
141
142    /** The ametys object resolver. */
143    protected AmetysObjectResolver _resolver;
144
145    /** The i18n utils. */
146    protected I18nUtils _i18nUtils;
147
148    /** The user sign up configuration */
149    protected UserSignUpConfiguration _userSignUpConfiguration;
150    
151    /** The temporary users table. */
152    protected String _tempUsersTable;
153
154    /** The password change table. */
155    protected String _pwdChangeTable;
156
157    /** The population context helper. */
158    protected PopulationContextHelper _populationContextHelper;
159
160    /** The rendering context handler */
161    protected RenderingContextHandler _renderingContextHandler;
162
163    /** factory for user directory models */
164    protected UserDirectoryFactory _userDirectoryFactory;
165
166    /** The current user provider */
167    protected CurrentUserProvider _currentUserProvider;
168
169    /** The right manager */
170    protected RightManager _rightManager;
171
172    /**
173     * Enumeration for the different type of signup
174     *
175     */
176    public enum SignupType 
177    {
178        /** Signup is not authorized */
179        UNAUTHORIZED,
180        
181        /** Signup allowed on invitation only */
182        INVITATION_ONLY,
183        
184        /** Public signup allowed */
185        PUBLIC
186    }
187
188    @Override
189    public void service(ServiceManager serviceManager) throws ServiceException
190    {
191        super.service(serviceManager);
192        
193        _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE);
194        _userPopulationDAO = (UserPopulationDAO) serviceManager.lookup(UserPopulationDAO.ROLE);
195        _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE);
196        _siteConf = (SiteConfigurationExtensionPoint) serviceManager.lookup(SiteConfigurationExtensionPoint.ROLE);
197        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
198        _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE);
199        _userSignUpConfiguration = (UserSignUpConfiguration) serviceManager.lookup(UserSignUpConfiguration.ROLE);
200        _populationContextHelper = (PopulationContextHelper) serviceManager.lookup(PopulationContextHelper.ROLE);
201        _renderingContextHandler = (RenderingContextHandler) serviceManager.lookup(RenderingContextHandler.ROLE);
202        _userDirectoryFactory = (UserDirectoryFactory) serviceManager.lookup(UserDirectoryFactory.ROLE);
203        _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
204        _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE);
205    }
206    
207
208    @Override
209    public void configure(Configuration configuration) throws ConfigurationException
210    {
211        super.configure(configuration);
212        
213        // Configure pool and tables.
214        _tempUsersTable = configuration.getChild("temp-users-table").getValue();
215        _pwdChangeTable = configuration.getChild("pwd-change-table").getValue();
216    }
217
218    /**
219     * Get the type of signup authorized for this site
220     * @param siteName the site name
221     * @return the of signup
222     */
223    public SignupType getSignupType(String siteName)
224    {
225        Site site = _siteManager.getSite(siteName);
226        String value = site.getValue(SITE_PARAM_SIGNUP_TYPE, false, SignupType.UNAUTHORIZED.name());
227        return SignupType.valueOf(value.toUpperCase());
228    }
229    
230    /**
231     * Test if public signup is allowed in a site.
232     * @param siteName the site to test.
233     * @return true if public signup is allowed, false otherwise.
234     */
235    public boolean isPublicSignupAllowed(String siteName)
236    {
237        return getSignupType(siteName) == SignupType.PUBLIC;
238    }
239    
240    /**
241     * Test if signup is allowed in a site, public or on invitation
242     * @param siteName the site to test.
243     * @return true if signup is allowed, false otherwise.
244     */
245    public boolean isSignupAllowed(String siteName)
246    {
247        SignupType signupType = getSignupType(siteName);
248        return signupType == SignupType.PUBLIC || signupType == SignupType.INVITATION_ONLY;
249    }
250
251    /**
252     * Get the sign-up page in a given site and sitemap.<br>
253     * If more than one page are tagged "sign-up", return the first.
254     * @param siteName the site name.
255     * @param language the sitemap name.
256     * @return the sign-up page or null if not found.
257     */
258    public Page getSignupPage(String siteName, String language)
259    {
260        return getSignupPage(siteName, language, null, null);
261    }
262    
263    /**
264     * Get the sign-up page in a given site and sitemap, configured for given population and user directory.<br>
265     * If more than one page are tagged "sign-up", return the first.
266     * @param siteName the site name.
267     * @param language the sitemap name.
268     * @param populationId The population id. Can be null to not check population.
269     * @param userDirectoryId The user directory id. Can be null to not check user directory.
270     * @return the sign-up page or null if not found.
271     */
272    public Page getSignupPage(String siteName, String language, String populationId, String userDirectoryId)
273    {
274        List<Page> signupPages = getSignupPages(siteName, language, populationId, userDirectoryId);
275        return signupPages.isEmpty() ? null : signupPages.get(0);
276    }
277    
278    /**
279     * Check if page is a signup page
280     * @param page the page
281     * @return true if the page contains the signup service
282     */
283    public boolean isSignupPage(Page page)
284    {
285        return _checkPageService(page, SIGNUP_PAGE_SERVICE_ID);
286    }
287    
288    /**
289     * Get all the pages tagged "sign-up" with signup service
290     * @param siteName the site name.
291     * @param language the sitemap name.
292     * @return an iterable on all the pages tagged "sign-up".
293     */
294    public List<Page> getSignupPages(String siteName, String language)
295    {
296        return getSignupPages(siteName, language, null, null);
297    }
298
299    /**
300     * Get all the pages tagged "sign-up" with signup service configured for given population and user directory
301     * @param siteName the site name.
302     * @param language the sitemap name.
303     * @param populationId The population id. Can be null to not check population.
304     * @param userDirectoryId The user directory id. Can be null to not check user directory.
305     * @return an iterable on all the pages tagged "sign-up".
306     */
307    public List<Page> getSignupPages(String siteName, String language, String populationId, String userDirectoryId)
308    {
309        Expression expression = new TagExpression(Operator.EQ, SIGNUP_PAGE_TAG_NAME);
310        String query = PageQueryHelper.getPageXPathQuery(siteName, language, null, expression, null);
311
312        AmetysObjectIterable<Page> pages = _resolver.query(query);
313        
314        return pages.stream()
315            .filter(p -> _checkSignupPage(p, populationId, userDirectoryId))
316            .collect(Collectors.toList());
317    }
318    
319    private boolean _checkPageService(Page page, String serviceId)
320    {
321        for (Zone zone : page.getZones())
322        {
323            try (AmetysObjectIterable<? extends ZoneItem> zoneItems = zone.getZoneItems())
324            {
325                for (ZoneItem zoneItem : zoneItems)
326                {
327                    if (zoneItem.getType() == ZoneType.SERVICE && serviceId.equals(zoneItem.getServiceId()))
328                    {
329                        return true;
330                    }
331                }
332            }
333        }
334        
335        return false;
336    }
337    
338    private boolean _checkSignupPage(Page page, String populationId, String userDirectoryId)
339    {
340        for (Zone zone : page.getZones())
341        {
342            try (AmetysObjectIterable<? extends ZoneItem> zoneItems = zone.getZoneItems())
343            {
344                for (ZoneItem zoneItem : zoneItems)
345                {
346                    if (zoneItem.getType() == ZoneType.SERVICE && SIGNUP_PAGE_SERVICE_ID.equals(zoneItem.getServiceId()))
347                    {
348                        if (StringUtils.isEmpty(populationId) && StringUtils.isEmpty(userDirectoryId))
349                        {
350                            // No filter on user directory
351                            return true;
352                        }
353                        else
354                        {
355                            UserDirectory userDirectory = getUserDirectory(zoneItem);
356                            if (userDirectory != null && userDirectory.getId().equals(userDirectoryId) && userDirectory.getPopulationId().equals(populationId))
357                            {
358                                return true;
359                            }
360                        }
361                    }
362                }
363            }
364        }
365        
366        return false;
367    }
368
369    /**
370     * Get the password change page in a given site and sitemap.
371     * If more than one page are tagged "password change", return the first.
372     * @param siteName the site name.
373     * @param language the sitemap name.
374     * @return the password change page or null if not found.
375     */
376    public Page getPwdChangePage(String siteName, String language)
377    {
378        List<Page> pwdChangePages = getPwdChangePages(siteName, language);
379        return pwdChangePages.isEmpty() ? null : pwdChangePages.get(0);
380    }
381    
382    /**
383     * Get the GTU page
384     * Returns null if not found or empty
385     * @param zoneItem the zone item
386     * @return the GTU page or null.
387     */
388    public Page getGTUPage(ZoneItem zoneItem)
389    {
390        Page page = null;
391
392        try
393        {
394            String tosMode = zoneItem.getServiceParameters().getValue("terms-of-service-mode");
395            if ("PAGE".equals(tosMode))
396            {
397                String tosPageId = zoneItem.getServiceParameters().getValue("terms-of-service-page");
398                if (StringUtils.isNotEmpty(tosPageId))
399                {
400                    page = _resolver.resolveById(tosPageId);
401                }
402            }
403        }
404        catch (AmetysRepositoryException e)
405        {
406            // Nothing
407        }
408
409        return page;
410    }
411    
412    /**
413     * Get the user directory
414     * @param zoneItem the zone item with signup service
415     * @return The user directory or null if not found
416     */
417    public UserDirectory getUserDirectory(ZoneItem zoneItem)
418    {
419        try
420        {
421            String userDirectoryParameterValue = zoneItem.getServiceParameters().getValue(SIGNUP_SERVICE_PARAMETER_USERDIRECTORY);
422            String[] populationAndDirectory = userDirectoryParameterValue.split("#", 2);
423            String populationId = populationAndDirectory[0];
424            String userDirectoryId = populationAndDirectory[1];
425            
426            UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(populationId);
427            if (userPopulation == null)
428            {
429                getLogger().error("The user population with id '{}' configured into service parameters of '{}' does not exist.", populationId, zoneItem.getId()); 
430                return null;
431            }
432            return userPopulation.getUserDirectory(userDirectoryId);
433        }
434        catch (AmetysRepositoryException e)
435        {
436            getLogger().error("Failed to get user directory from service parameters of '{}'", zoneItem.getId(), e); 
437            return null;
438        }
439    }
440    
441    
442    /**
443     * Get the GTU content
444     * Returns null if not found or empty
445     * @param zoneItem the zone item
446     * @return the GTU content or null.
447     */
448    public Content getGTUContent(ZoneItem zoneItem)
449    {
450        Content content = null;
451
452        try
453        {
454            String tosMode = zoneItem.getServiceParameters().getValue("terms-of-service-mode");
455            if ("CONTENT".equals(tosMode))
456            {
457                String tosContentId = zoneItem.getServiceParameters().getValue("terms-of-service-content");
458                if (StringUtils.isNotEmpty(tosContentId))
459                {
460                    content = _resolver.resolveById(tosContentId);
461                }
462            }
463            
464        }
465        catch (AmetysRepositoryException e)
466        {
467            // Nothing
468        }
469
470        return content;
471    }
472    
473    /**
474     * Get the GTU page
475     * Returns null if not found or empty
476     * @param zoneItem the zone item
477     * @return the success page or null.
478     */
479    public Page getSuccessPage(ZoneItem zoneItem)
480    {
481        Page page = null;
482
483        try
484        {
485            String successMode = zoneItem.getServiceParameters().getValue("success-mode");
486            if ("PAGE".equals(successMode))
487            {
488                String successPageId = zoneItem.getServiceParameters().getValue("success-page");
489                if (StringUtils.isNotEmpty(successPageId))
490                {
491                    page = _resolver.resolveById(successPageId);
492                }
493            }
494        }
495        catch (AmetysRepositoryException e)
496        {
497            // Nothing
498        }
499
500        return page;
501    }
502    
503    /**
504     * Get the success content
505     * Returns null if not found or empty
506     * @param zoneItem the zone item
507     * @return the success content or null.
508     */
509    public Content getSuccessContent(ZoneItem zoneItem)
510    {
511        Content content = null;
512
513        try
514        {
515            String successMode = zoneItem.getServiceParameters().getValue("success-mode");
516            if ("CONTENT".equals(successMode))
517            {
518                String successContentId = zoneItem.getServiceParameters().getValue("success-content");
519                if (StringUtils.isNotEmpty(successContentId))
520                {
521                    content = _resolver.resolveById(successContentId);
522                }
523            }
524        }
525        catch (AmetysRepositoryException e)
526        {
527            // Nothing
528        }
529
530        return content;
531    }
532
533    /**
534     * Get all the pages tagged "password change".
535     * @param siteName the site name.
536     * @param language the sitemap name.
537     * @return an iterable on all the pages tagged "password change".
538     */
539    public List<Page> getPwdChangePages(String siteName, String language)
540    {
541        Expression expression = new TagExpression(Operator.EQ, CHANGE_PASSWORD_PAGE_TAG_NAME);
542        String query = PageQueryHelper.getPageXPathQuery(siteName, language, null, expression, null);
543
544        AmetysObjectIterable<Page> pages = _resolver.query(query);
545        
546        return pages.stream()
547            .filter(p -> _checkPageService(p, CHANGE_PASSWORD_PAGE_SERVICE_ID))
548            .collect(Collectors.toList());
549    }
550    
551    /**
552     * Check if page is a change password page
553     * @param page the page
554     * @return true if the page contains the change password service
555     */
556    public boolean isPwdChangePage(Page page)
557    {
558        return _checkPageService(page, CHANGE_PASSWORD_PAGE_SERVICE_ID);
559    }
560    
561    /**
562     * Get the user from email if he/she exists in the populations 
563     * @param email the e-mail to test.
564     * @param siteName The site name
565     * @return the user if the user exists, empty otherwise.
566     * @throws UserManagementException if an error occurs.
567     * @throws NotUniqueUserException if several user respond to the email
568     */
569    public Optional<User> getUserIfHeExists(String email, String siteName) throws UserManagementException, NotUniqueUserException
570    {
571        Set<String> populationIds = _populationContextHelper.getUserPopulationsOnContexts(Arrays.asList("/sites/" + siteName, "/sites-fo/" + siteName), false, false);
572        for (String population : populationIds)
573        {
574            User user = _userManager.getUser(population, email);
575            if (user != null)
576            {
577                return Optional.of(user);
578            }
579            else
580            {
581                User userByEmail = _userManager.getUserByEmail(population, email);
582                if (userByEmail != null)
583                {
584                    return Optional.of(userByEmail);
585                }
586            }
587        }
588        
589        return Optional.empty();
590    }
591    
592    /**
593     * Tests if the user already exists in the populations 
594     * @param email the e-mail to test.
595     * @param siteName The site name
596     * @return true if the user exists, false otherwise.
597     * @throws UserManagementException if an error occurs.
598     */
599    public boolean userExists(String email, String siteName) throws UserManagementException
600    {
601        try
602        {
603            return getUserIfHeExists(email, siteName).isPresent();
604        }
605        catch (NotUniqueUserException e)
606        {
607            return true;
608        }
609    }
610
611    /**
612     * Validate the user subscription data.
613     * @param siteName the site name.
614     * @param email the user e-mail.
615     * @param additionalValues the additional user values.
616     * @return a Map of the Errors by field.
617     * @throws UserManagementException if an error occurs.
618     */
619    public Map<String, Errors> validate(String siteName, String email, Map<String, String> additionalValues) throws UserManagementException
620    {
621        Map<String, String> userInfos = new HashMap<>();
622        
623        userInfos.putAll(additionalValues);
624        
625        // Standard info for user (provide a dummy password, as we do not know it yet).
626        userInfos.put("login", email);
627        userInfos.put("email", email);
628        userInfos.put("password", "password");
629
630        Map<String, Errors> usersManagerErrors = new HashMap<>(); //foUsersManager.validate(userInfos);
631        Map<String, Errors> errors = new HashMap<>(usersManagerErrors);
632
633        // If there are errors on the login, do not return it except if
634        if (errors.containsKey("login"))
635        {
636            if (!errors.containsKey("email"))
637            {
638                errors.put("email", errors.get("login"));
639            }
640            errors.remove("login");
641        }
642
643        return errors;
644    }
645
646    /**
647     * Validate the user password.
648     * @param siteName the site name.
649     * @param password the password to validate.
650     * @param login the login of the user
651     * @param population The id of the population
652     * @return a Map of the Errors by field.
653     * @throws UserManagementException if an error occurs.
654     */
655    public Map<String, Errors> validatePassword(String siteName, String password, String login, String population) throws UserManagementException
656    {
657        Map<String, String> userInfos = new HashMap<>();
658
659        userInfos.put("password", password);
660
661        UserDirectory userDirectory = _userManager.getUserDirectory(population, login);
662        if (!(userDirectory instanceof ModifiableUserDirectory))
663        {
664            throw new UserManagementException("The user subscription feature can't be used, as the UserDirectory is not modifiable.", StatusError.UNMODIFIABLE_USER_DIRECTORY);
665        }
666        Map<String, Errors> usersManagerErrors = ((ModifiableUserDirectory) userDirectory).validate(userInfos);
667        Map<String, Errors> errors = new HashMap<>();
668
669        // Keep only errors related to the password field.
670        if (usersManagerErrors.containsKey("password"))
671        {
672            errors.put("password", usersManagerErrors.get("password"));
673        }
674
675        return errors;
676    }
677    
678    /**
679     * Temporary sign the user in and returns the token
680     * @param siteName The site's name
681     * @param language The page's language
682     * @param email The email of the user
683     * @param population The population ID
684     * @param userDirectoryId The userDirectory ID
685     * @return The sign-up token
686     * @throws UserManagementException If an error occurs
687     */
688    public String getOrCreateToken(String siteName, String language, String email, String population, String userDirectoryId) throws UserManagementException 
689    {
690        String token = getToken(siteName, email, population, userDirectoryId);
691        
692        if (token == null)
693        {
694            temporarySignup(siteName, language, email, population, userDirectoryId, false);
695            token = getToken(siteName, email, population, userDirectoryId);
696        }
697        
698        return token;
699    }
700    
701    /**
702     * Validate and store a sign-up request and send a confirmation e-mail.
703     * @param siteName the site name.
704     * @param language the sitemap name.
705     * @param email the user e-mail address.
706     * @param population the population
707     * @param userDirectoryId the id of the user directory of the population
708     * @throws UserManagementException if an error occurs.
709     */
710    public void temporarySignup(String siteName, String language, String email, String population, String userDirectoryId) throws UserManagementException
711    {
712        temporarySignup(siteName, language, email, population, userDirectoryId, true);
713    }
714    
715    /**
716     * Validate and store a sign-up request and send a confirmation e-mail.
717     * @param siteName the site name.
718     * @param language the sitemap name.
719     * @param email the user e-mail address.
720     * @param population the population
721     * @param userDirectoryId the id of the user directory of the population
722     * @param sendMail Set to false to not send mail at end of process
723     * @throws UserManagementException if an error occurs.
724     */
725    public void temporarySignup(String siteName, String language, String email, String population, String userDirectoryId, boolean sendMail) throws UserManagementException
726    {
727        // Verify that public sign-up is allowed and throw an exception otherwise.
728        checkPublicSignup(siteName);
729
730        if (getLogger().isDebugEnabled())
731        {
732            getLogger().debug("A user is requesting a sign-up: " + email);
733        }
734
735        _temporarySignup(siteName, language, email, population, userDirectoryId, null, null, TempUserOrigin.USER_REQUEST, sendMail, false);
736    }
737    
738    /**
739     * Validate and store a invitation to sign-up request and send a confirmation e-mail.
740     * @param siteName the site name.
741     * @param language the sitemap name.
742     * @param email the user e-mail address.
743     * @param population the population
744     * @param userDirectoryId the id of the user directory of the population
745     * @param lastname The guest last name. Can be null.
746     * @param firstname The guest first name. Can be null.
747     * @throws UserManagementException if an error occurs.
748     */
749    public void inviteToSignup(String siteName, String language, String email, String population, String userDirectoryId, String lastname, String firstname) throws UserManagementException
750    {
751        inviteToSignup(siteName, language, email, population, userDirectoryId, lastname, firstname, true, true, true);
752    }
753    
754    /**
755     * Validate and store a invitation to sign-up request and send a confirmation e-mail.
756     * @param siteName the site name.
757     * @param language the sitemap name.
758     * @param email the user e-mail address.
759     * @param population the population
760     * @param userDirectoryId the id of the user directory of the population
761     * @param lastname The guest last name. Can be null.
762     * @param firstname The guest first name. Can be null.
763     * @param sendMail Set to false to not send mail at end of process
764     * @param resendInvitationIfExists Set to false to not resend invitation if the email already exists as a user waiting signup
765     * @param checkRights set to false to ignore rights
766     * @throws UserManagementException if an error occurs.
767     */
768    public void inviteToSignup(String siteName, String language, String email, String population, String userDirectoryId, String lastname, String firstname, boolean sendMail, boolean resendInvitationIfExists, boolean checkRights) throws UserManagementException
769    {
770        // Verify that sign-up is allowed (public or on invitation only) and throw an exception otherwise.
771        checkSignupAllowed(siteName);
772        
773        if (checkRights)
774        {
775            // Verify that user is allowed to send invitation for this population and throw an exception otherwise.
776            checkUserAllowedForInvitation(siteName, population);
777        }
778        
779        if (getLogger().isDebugEnabled())
780        {
781            getLogger().debug("A user is invited to sign-up: " + email);
782        }
783        
784        _temporarySignup(siteName, language, email, population, userDirectoryId, lastname, firstname, TempUserOrigin.INVITATION, sendMail, resendInvitationIfExists);
785    }
786    
787    private void _temporarySignup(String siteName, String language, String email, String population, String userDirectoryId, String lastname, String firstname, TempUserOrigin origin, boolean sendMail, boolean resendInvitationIfExists) throws UserManagementException
788    {
789        if (userExists(email, siteName))
790        {
791            throw new UserManagementException("User with email " + email  + " already exists", StatusError.USER_ALREADY_EXISTS);
792        }
793        
794        // Generate unique token.
795        String token = UUID.randomUUID().toString().replace("-", "");
796
797        try
798        {
799            addTemporaryUser(siteName, email, token, population, userDirectoryId, lastname, firstname, origin);
800            
801            // Send validation e-mail with token.
802            if (sendMail)
803            {
804                TempUser tempUser = getTempUser(siteName, email, population, userDirectoryId);
805                sendSignupConfirmMail(tempUser, language);
806            }
807            
808            ForensicLogger.info(__EVENT_ACCOUNT_TEMP_SIGNUP, Map.of("site", siteName, "email", email, "population", population, "userDirectory", userDirectoryId), null);
809        }
810        catch (UserManagementException e)
811        {
812            if (e.getStatusError() == StatusError.TEMP_USER_ALREADY_EXISTS && resendInvitationIfExists)
813            {
814                // Ignore error and resend the invitation if the temporary user was already registered
815                resendInvitation(siteName, email, population, userDirectoryId);
816                return;
817            }
818            else
819            {
820                // Remove the temporary user that was potentially created (as the temporary signup could not complete successfully)
821                removeTempUser(siteName, email, token, population, userDirectoryId);
822                throw e;
823            }
824        }
825        
826    }
827    
828    /**
829     * Get a token for temp user
830     * @param siteName the site name.
831     * @param email the user e-mail address.
832     * @param population The id of the population
833     * @param userDirectoryId The id of the user directory of the population
834     * @return the user token or null if not found
835     * @throws UserManagementException if an error occurs.
836     */
837    public String getToken(String siteName, String email, String population, String userDirectoryId) throws UserManagementException
838    {
839        TempUser tempUser = getTempUser(siteName, email, population, userDirectoryId);
840        if (tempUser != null)
841        {
842            return tempUser.getToken();
843        }
844        
845        return null;
846    }
847    
848    /**
849     * Update susbcription date and resend an invitation
850     * @param siteName the site name
851     * @param email the email
852     * @param population the population id
853     * @param userDirectoryId the user directory id
854     * @throws UserManagementException if an error occurred
855     */
856    public void resendInvitation(String siteName, String email, String population, String userDirectoryId) throws UserManagementException
857    {
858        // Verify that sign-up is allowed and throw an exception otherwise.
859        checkSignupAllowed(siteName);
860        
861        // Verify that user is allowed  and throw an exception otherwise.
862        checkUserAllowedForInvitation(siteName, population);
863        
864        TempUser tempUser = getTempUser(siteName, email, population, userDirectoryId);
865        if (tempUser.getOrigin() != TempUserOrigin.INVITATION)
866        {
867            throw new UserManagementException("Can not resend invitation. The subscription request is a user request", StatusError.UNMODIFIABLE_SIGNUP_REQUEST);
868        }
869        
870        // Reset signup without generating new token
871        resetTempSignup(siteName, null, email, null, population, userDirectoryId);
872    }
873
874    /**
875     * Reset a sign-up request: generate a new token and re-send the confirmation e-mail.
876     * @param siteName the site name.
877     * @param language the sitemap name.
878     * @param email the user e-mail address.
879     * @param population The id of the population
880     * @param userDirectoryId The id of the user directory of the population
881     * @throws UserManagementException if an error occurs.
882     */
883    public void resetTempSignup(String siteName, String language, String email, String population, String userDirectoryId) throws UserManagementException
884    {
885        // Verify that public sign-up is allowed and throw an exception otherwise.
886        checkPublicSignup(siteName);
887        
888        // Generate a new token.
889        String newToken = UUID.randomUUID().toString().replace("-", "");
890        
891        if (getLogger().isDebugEnabled())
892        {
893            getLogger().debug("Resetting temporary signup for email: " + email + " in site " + siteName);
894        }
895        
896        resetTempSignup(siteName, language, email, newToken, population, userDirectoryId);
897    }
898    
899    /**
900     * Reset a sign-up request: generate a new token and re-send the confirmation e-mail.
901     * @param siteName the site name.
902     * @param language the sitemap name.
903     * @param email the user e-mail address.
904     * @param token the token. Can be null to not re-generate token.
905     * @param population The id of the population
906     * @param userDirectoryId The id of the user directory of the population
907     * @throws UserManagementException if an error occurs.
908     */
909    protected void resetTempSignup(String siteName, String language, String email, String token, String population, String userDirectoryId) throws UserManagementException
910    {
911        // Test if the subscription request really exists.
912        TempUser tempUser = getTempUser(siteName, email, population, userDirectoryId);
913
914        if (tempUser == null)
915        {
916            throw new UserManagementException("Found no subscription request with email '" + email + "' for site '" + siteName + "'", StatusError.UNKNOWN_EMAIL);
917        }
918        
919        String newToken = token;
920        if (newToken == null)
921        {
922            // No generate new token
923            newToken = tempUser.getToken();
924        }
925        
926        updateTempToken(siteName, email, newToken, population, userDirectoryId);
927        
928        tempUser = getTempUser(siteName, email, population, userDirectoryId);
929
930        sendSignupConfirmMail(tempUser, language);
931    }
932
933
934    /**
935     * Create the user in the FO UsersManager from his temporary request.
936     * @param siteName the site name.
937     * @param language the current language
938     * @param firstname the user firstname
939     * @param lastname the user lastname
940     * @param email the user e-mail address.
941     * @param token the request token.
942     * @param password the user password.
943     * @param population The id of the population
944     * @param userDirectoryId The id of the user directory of the population
945     * @param errors the errors
946     * @throws UserManagementException if an error occurs.
947     */
948    public void signup(String siteName, String language, String firstname, String lastname, String email, String token, String password, String population, String userDirectoryId, Multimap<String, I18nizableText> errors) throws UserManagementException
949    {
950        // Verify that public sign-up is allowed and throw an exception otherwise.
951        checkSignupAllowed(siteName);
952
953        Map<String, String> userInfos = new HashMap<>();
954
955        TempUser tempUser = getTempUser(siteName, email, token, population, userDirectoryId);
956
957        if (tempUser == null)
958        {
959            throw new UserManagementException("Provided token is unknown", StatusError.TOKEN_UNKNOWN);
960        }
961        
962        // Generate login
963        String login = RandomStringUtils.randomNumeric(10);
964
965        // Standard info for user.
966        userInfos.put("login", login);
967        userInfos.put("email", email);
968        userInfos.put("firstname", firstname);
969        userInfos.put("lastname", lastname);
970        userInfos.put("password", password);
971
972        try
973        {
974            validationBeforeSignup(errors);
975            
976            if (errors.isEmpty())
977            {
978                // Add the user.
979                ModifiableUserDirectory userDirectory = (ModifiableUserDirectory) _userPopulationDAO.getUserPopulation(population).getUserDirectory(userDirectoryId);
980                userDirectory.add(userInfos, UserCreationOrigin.USER_SIGNUP);
981
982                // Remove the temporary user.
983                removeTempUser(siteName, email, token, population, userDirectoryId);
984                
985                User createdUser = userDirectory.getUser(login);
986                
987                // Do additional operations after signup
988                additionalSignupOperations(createdUser, errors);
989                
990                if (errors.isEmpty())
991                {
992                    sendSignupValidatedMail(siteName, language, createdUser);
993                    
994                    ForensicLogger.info(__EVENT_ACCOUNT_CREATED, Map.of("site", siteName, "user", createdUser, "population", population, "userDirectory", userDirectoryId), createdUser.getIdentity());
995                }
996            }
997            else
998            {
999                throw new UserManagementException("Unable to signup user");
1000            }
1001            
1002        }
1003        catch (InvalidModificationException e)
1004        {
1005            throw new UserManagementException("An error occurred signing up the user.", StatusError.INVALID_MODIFICATION, e);
1006        }
1007    }
1008    
1009    /**
1010     * Do some validation before signup
1011     * @param errors the map of errors to fill in cause of errors during validation
1012     */
1013    public void validationBeforeSignup(Multimap<String, I18nizableText> errors)
1014    {
1015        // Nothing
1016    }
1017    
1018    /**
1019     * Process additional operations after creation of user
1020     * @param createdUser the created user
1021     * @param errors the map of errors to fill in case of errors during additional operations
1022     * @throws UserManagementException if an error occurs.
1023     */
1024    public void additionalSignupOperations(User createdUser, Multimap<String, I18nizableText> errors) throws UserManagementException
1025    {
1026        // Nothing
1027    }
1028    
1029
1030    /**
1031     * Create a reset password request and send a confirmation e-mail.
1032     * @param siteName the site name.
1033     * @param language the sitemap name.
1034     * @param loginOrEmail the user login or email.
1035     * @param populationId the population
1036     * @throws UserManagementException if an error occurs.
1037     */
1038    public void resetPassword(String siteName, String language, String loginOrEmail, String populationId) throws UserManagementException
1039    {
1040        // Check if the population exists
1041        UserPopulation population = _userPopulationDAO.getUserPopulation(populationId);
1042        if (population == null)
1043        {
1044            throw new UserManagementException("Unknown population with id '" + populationId + "'", StatusError.POPULATION_UNKNOWN);
1045        }
1046        
1047        // Check if the user exists and get it
1048        User user = _userManager.getUser(populationId, loginOrEmail);
1049        if (user == null)
1050        {
1051            try
1052            {
1053                user = _userManager.getUserByEmail(populationId, loginOrEmail);
1054                if (user == null)
1055                {
1056                    // No user with this e-mail or login.
1057                    throw new UserManagementException("Unknown user with login or email '" + loginOrEmail + "' for population '" + populationId + "'", StatusError.USER_UNKNOWN);
1058                }
1059            }
1060            catch (NotUniqueUserException e)
1061            {
1062                throw new UserManagementException("Many users match for email '" + loginOrEmail + "' and population '" + populationId + "'", StatusError.NOT_UNIQUE_USER);
1063            }
1064        }
1065        
1066        // Check if the user directory is modifiable
1067        if (!(user.getUserDirectory() instanceof ModifiableUserDirectory))
1068        {
1069            throw new UserManagementException("User directory is not modifiable", StatusError.UNMODIFIABLE_USER_DIRECTORY);
1070        }
1071        
1072        if (StringUtils.isEmpty(user.getEmail()))
1073        {
1074            throw new UserManagementException("User has no email", StatusError.EMPTY_EMAIL);
1075        }
1076
1077        // Generate a new token.
1078        String token = UUID.randomUUID().toString().replace("-", "");
1079
1080        // Insert the token in the database.
1081        addPasswordToken(siteName, user.getIdentity().getLogin(), token, populationId);
1082
1083        // Send the e-mail.
1084        sendResetPasswordMail(siteName, language, user, token);
1085        
1086        // Trace event
1087        ForensicLogger.info(__EVENT_ACCOUNT_PASSWORD_RESET, Map.of("site", siteName, "user", user), user.getIdentity());
1088    }
1089
1090    /**
1091     * Change the user password.
1092     * @param siteName the site name.
1093     * @param login the user login.
1094     * @param token the password change request token.
1095     * @param newPassword the new password.
1096     * @param population the population
1097     * @throws UserManagementException if an error occurs.
1098     */
1099    public void changeUserPassword(String siteName, String login, String token, String newPassword, String population) throws UserManagementException
1100    {
1101        checkPasswordToken(siteName, login, token, population);
1102
1103        Map<String, String> userInfos = new HashMap<>();
1104
1105        userInfos.put("login", login);
1106        userInfos.put("password", newPassword);
1107
1108        try
1109        {
1110            UserDirectory userDirectory = _userManager.getUserDirectory(population, login);
1111            if (!(userDirectory instanceof ModifiableUserDirectory))
1112            {
1113                _logPasswordChangeFailed(siteName, login, population, "unmodifiable-modification");
1114                throw new UserManagementException("The user's password can't be changed, as the UserDirectory is not modifiable.", StatusError.UNMODIFIABLE_USER_DIRECTORY);
1115            }
1116            ((ModifiableUserDirectory) userDirectory).update(userInfos);
1117
1118            removePasswordToken(siteName, login, token, population);
1119            
1120            ForensicLogger.info(__EVENT_ACCOUNT_PASSWORD_CHANGE, Map.of("site", siteName, "login", login, "population", population), new UserIdentity(login, population));
1121        }
1122        catch (InvalidModificationException e)
1123        {
1124            _logPasswordChangeFailed(siteName, login, population, "invalid-modification");
1125            throw new UserManagementException("Invalid user inputs to change password", StatusError.INVALID_MODIFICATION, e);
1126        }
1127    }
1128    
1129    private void _logPasswordChangeFailed(String siteName, String login, String population, String errorCause)
1130    {
1131        Map<String, Object> eventArgs = Map.of("site", siteName, "login", login, "population", population, "error", errorCause);
1132        ForensicLogger.warn(__EVENT_ACCOUNT_PASSWORD_CHANGE_FAILED, eventArgs, new UserIdentity(login, population));
1133    }
1134    
1135    /**
1136     * Check the sign-up request token.
1137     * @param siteName the site name.
1138     * @param email the user e-mail.
1139     * @param token the sign-up request token.
1140     * @param population The id of the population
1141     * @param userDirectoryId The id of the user directory of the population
1142     * @throws UserManagementException if an error occurs.
1143     */
1144    public void checkToken(String siteName, String email, String token, String population, String userDirectoryId) throws UserManagementException
1145    {
1146        removeExpiredTokens();
1147
1148        try (SqlSession sqlSession = getSession())
1149        {
1150            String stmtId = "UserSignupManager.getSubscriptionDate";
1151            
1152            Map<String, Object> params = new HashMap<>();
1153            params.put("tempUsersTable", _tempUsersTable);
1154            
1155            params.put("site", siteName);
1156            params.put("email", email);
1157            params.put("token", token);
1158            params.put("population", population);
1159            params.put("userDirectory", userDirectoryId);
1160            
1161            Date rawSubscriptionDate = sqlSession.selectOne(stmtId, params);
1162
1163            // Date verification.
1164            if (rawSubscriptionDate == null)
1165            {
1166                throw new UserManagementException("Provided token is unknown", StatusError.TOKEN_UNKNOWN);
1167            }
1168            
1169            // The validity limit
1170            ZonedDateTime limit = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).minusDays(_userSignUpConfiguration.getTokenValidity());
1171            ZonedDateTime subscriptionDate = rawSubscriptionDate.toInstant().atZone(ZoneId.systemDefault());
1172
1173            if (subscriptionDate.isBefore(limit))
1174            {
1175                throw new UserManagementException("Provided token is expired", StatusError.TOKEN_EXPIRED);
1176            }
1177        }
1178        catch (Exception e)
1179        {
1180            if (e instanceof UserManagementException)
1181            {
1182                throw e;
1183            }
1184            else
1185            {
1186                throw new UserManagementException("Database error while testing the token for user [" + email + "]", StatusError.DATABASE_ERROR, e);
1187            }
1188        }
1189    }
1190
1191    /**
1192     * Check the password change request token.
1193     * @param siteName the site name.
1194     * @param login the user login.
1195     * @param token the password change request token.
1196     * @param population the population
1197     * @throws UserManagementException if an error occurs.
1198     */
1199    public void checkPasswordToken(String siteName, String login, String token, String population) throws UserManagementException
1200    {
1201        removeExpiredPasswordTokens();
1202
1203        try (SqlSession sqlSession = getSession())
1204        {
1205            String stmtId = "UserSignupManager.getRequestDate";
1206            
1207            Map<String, Object> params = new HashMap<>();
1208            params.put("pwdChangeTable", _pwdChangeTable);
1209            
1210            params.put("site", siteName);
1211            params.put("login", login);
1212            params.put("token", token);
1213            params.put("population", population);
1214            
1215            Date rawRequestDate = sqlSession.selectOne(stmtId, params);
1216
1217            // Date verification.
1218            if (rawRequestDate == null)
1219            {
1220                throw new UserManagementException("Provided token for login '" + login + "' and site '" + siteName + "' is unknown", StatusError.TOKEN_UNKNOWN);
1221            }
1222
1223            // Check the validity.
1224            ZonedDateTime limit = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).minusDays(_userSignUpConfiguration.getTokenValidity());
1225            ZonedDateTime requestDate = rawRequestDate.toInstant().atZone(ZoneId.systemDefault());
1226
1227            if (requestDate.isBefore(limit))
1228            {
1229                throw new UserManagementException("Provided token for login '" + login + "' and site '" + siteName + "' is expired", StatusError.TOKEN_EXPIRED);
1230            }
1231        }
1232        catch (Exception e)
1233        {
1234            if (e instanceof UserManagementException)
1235            {
1236                throw e;
1237            }
1238            else
1239            {
1240                throw new UserManagementException("Database error while testing the password token for user " + login, StatusError.DATABASE_ERROR, e);
1241            }
1242        }
1243    }
1244    
1245    /**
1246     * Remove the expired sign-up request tokens (user request only)
1247     * @throws UserManagementException if an error occurs.
1248     */
1249    public void removeExpiredTokens() throws UserManagementException
1250    {
1251        removeExpiredTokens(null);
1252    }
1253
1254    /**
1255     * Remove the expired sign-up request tokens (user request only)
1256     * @param siteName the site name. Can be null.
1257     * @return the number of deleted tokens
1258     * @throws UserManagementException if an error occurs.
1259     */
1260    public int removeExpiredTokens(String siteName) throws UserManagementException
1261    {
1262        try (SqlSession sqlSession = getSession())
1263        {
1264            String stmtId = "UserSignupManager.deleteExpiredTokens";
1265            
1266            Map<String, Object> params = new HashMap<>();
1267            params.put("tempUsersTable", _tempUsersTable);
1268            
1269            ZonedDateTime limit = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).minusDays(_userSignUpConfiguration.getTokenValidity());
1270            Timestamp limitTimestamp = Timestamp.from(limit.toInstant());
1271            params.put("threshold", limitTimestamp);
1272            if (StringUtils.isNotBlank(siteName))
1273            {
1274                params.put("site", siteName);
1275            }
1276            
1277            int deletedTokens = sqlSession.delete(stmtId, params);
1278            sqlSession.commit();
1279            return deletedTokens;
1280        }
1281        catch (Exception e)
1282        {
1283            throw new UserManagementException("Database error while removing the expired tokens.", StatusError.DATABASE_ERROR, e);
1284        }
1285    }
1286
1287    /**
1288     * Remove the expired change password request tokens.
1289     * @throws UserManagementException if an error occurs.
1290     */
1291    public void removeExpiredPasswordTokens() throws UserManagementException
1292    {
1293        try (SqlSession sqlSession = getSession())
1294        {
1295            String stmtId = "UserSignupManager.deleteExpiredPasswordTokens";
1296            
1297            Map<String, Object> params = new HashMap<>();
1298            params.put("pwdChangeTable", _pwdChangeTable);
1299            
1300            ZonedDateTime limit = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).minusDays(_userSignUpConfiguration.getTokenValidity());
1301            Timestamp limitTimestamp = Timestamp.from(limit.toInstant());
1302            params.put("threshold", limitTimestamp);
1303            
1304            sqlSession.delete(stmtId, params);
1305            sqlSession.commit();
1306        }
1307        catch (Exception e)
1308        {
1309            throw new UserManagementException("Database error while removing the expired tokens.", StatusError.DATABASE_ERROR, e);
1310        }
1311    }
1312
1313    /**
1314     * Verify that public sign-up is allowed. If not, throw an exception.
1315     * @param siteName the site name.
1316     * @throws UserManagementException if public sign-up is not enabled.
1317     */
1318    protected void checkPublicSignup(String siteName) throws UserManagementException
1319    {
1320        if (!isPublicSignupAllowed(siteName))
1321        {
1322            throw new UserManagementException("Public signup is disabled for this site.", StatusError.PUBLIC_SIGNUP_NOT_ALLOWED);
1323        }
1324    }
1325    
1326    /**
1327     * Verify that public sign-up is allowed. If not, throw an exception.
1328     * @param siteName the site name.
1329     * @throws UserManagementException if public sign-up is not enabled.
1330     */
1331    protected void checkSignupAllowed(String siteName) throws UserManagementException
1332    {
1333        if (!isSignupAllowed(siteName))
1334        {
1335            throw new UserManagementException("Signup is disabled for this site.", StatusError.SIGNUP_NOT_ALLOWED);
1336        }
1337    }
1338
1339    /**
1340     * Create a user sign-up request ("temporary" user) in the database.
1341     * @param siteName the site name.
1342     * @param email the user e-mail.
1343     * @param token the generated token.
1344     * @param population the population
1345     * @param userDirectoryId the id of the user directory of the population
1346     * @param lastname The guest last name. Can be null.
1347     * @param firstname The guest first name. Can be null.
1348     * @param origin true if this signup request origin
1349     * @throws UserManagementException if an error occurs.
1350     */
1351    protected void addTemporaryUser(String siteName, String email, String token, String population, String userDirectoryId, String lastname, String firstname, TempUserOrigin origin) throws UserManagementException
1352    {
1353        try (SqlSession sqlSession = getSession())
1354        {
1355            // Does this email already exists
1356            String stmtId = "UserSignupManager.tempEmailExists";
1357            
1358            Map<String, Object> params = new HashMap<>();
1359            params.put("tempUsersTable", _tempUsersTable);
1360            
1361            params.put("site", siteName);
1362            params.put("email", email);
1363            
1364            List<String> emails = sqlSession.selectList(stmtId, params);
1365            
1366            if (!emails.isEmpty())
1367            {
1368                throw new UserManagementException("Temporary user with email '" + email + "' and site name '" + siteName + "' already exists", StatusError.TEMP_USER_ALREADY_EXISTS);
1369            }
1370            
1371            // Add temporary user
1372            stmtId = "UserSignupManager.addTempUser";
1373            params = new HashMap<>();
1374            params.put("tempUsersTable", _tempUsersTable);
1375            
1376            Timestamp now = new Timestamp(System.currentTimeMillis());
1377            
1378            params.put("site", siteName);
1379            params.put("email", email);
1380            params.put("population", population);
1381            params.put("userDirectory", userDirectoryId);
1382            params.put("subscription_date", now);
1383            params.put("token", token);
1384            params.put("lastname", lastname);
1385            params.put("firstname", firstname);
1386            params.put("origin", origin.name());
1387            
1388            sqlSession.insert(stmtId, params);
1389            sqlSession.commit();
1390        }
1391        catch (Exception e)
1392        {
1393            if (e instanceof UserManagementException)
1394            {
1395                throw e;
1396            }
1397            else
1398            {
1399                throw new UserManagementException("Database error while signing up a new user [" + email + "]", StatusError.DATABASE_ERROR, e);
1400            }
1401        }
1402    }
1403
1404    /**
1405     * Send a sign-up confirmation link by e-mail.
1406     * @param tempUser the temp user
1407     * @param language the e-mail language.
1408     * @throws UserManagementException if an error occurs.
1409     */
1410    protected void sendSignupConfirmMail(TempUser tempUser, String language) throws UserManagementException
1411    {
1412        String email = tempUser.getEmail();
1413        String siteName = tempUser.getSite();
1414        String populationId = tempUser.getPopulation();
1415        String userDirectoryId = tempUser.getUserDirectoryId();
1416        String token = tempUser.getToken();
1417        String lastname = tempUser.getLastname();
1418        String firstname = tempUser.getFirstname();
1419        String fullname = (StringUtils.isEmpty(firstname) ? "" : " " + firstname) + (StringUtils.isEmpty(lastname) ? "" : " " + lastname);
1420        boolean fromInvitation = tempUser.getOrigin() == TempUserOrigin.INVITATION;
1421        
1422        if (getLogger().isDebugEnabled())
1423        {
1424            getLogger().debug("Sending signup confirmation e-mail to " + email);
1425        }
1426
1427        Site site = _siteManager.getSite(siteName);
1428        String from = site.getValue("site-mail-from");
1429        
1430        if (!SendMailHelper.EMAIL_VALIDATION.matcher(StringUtils.trimToEmpty(email)).matches())
1431        {
1432            throw new UserManagementException("Cannot send signup email. The email is invalid [" + email + "]", StatusError.INVALID_EMAIL);
1433        }
1434
1435        // Prepare mail.
1436        Map<String, I18nizableTextParameter> i18nParams = new HashMap<>();
1437        i18nParams.put("siteName", new I18nizableText(siteName));
1438        i18nParams.put("email", new I18nizableText(email));
1439        i18nParams.put("fullName", new I18nizableText(fullname));
1440        i18nParams.put("token", new I18nizableText(tempUser.getToken()));
1441        i18nParams.put("tokenValidity", new I18nizableText(String.valueOf(_userSignUpConfiguration.getTokenValidity())));
1442        
1443        Page signupPage = getSignupPage(siteName, language, populationId, userDirectoryId);
1444        if (signupPage != null)
1445        {
1446            RenderingContext currentContext = _renderingContextHandler.getRenderingContext();
1447            
1448            try
1449            {
1450                // Force front context to resolver signup page uri
1451                _renderingContextHandler.setRenderingContext(RenderingContext.FRONT);
1452                
1453                String encodedEmail = URIUtils.encodeParameter(email);
1454                
1455                String confirmUri = ResolveURIComponent.resolve("page", signupPage.getId(), false, true);
1456                confirmUri = confirmUri + "?email=" + encodedEmail + "&token=" + token;
1457                
1458                if (StringUtils.isNotEmpty(lastname))
1459                {
1460                    confirmUri += "&lastname=" + lastname;
1461                }
1462                if (StringUtils.isNotEmpty(firstname))
1463                {
1464                    confirmUri += "&firstname=" + firstname;
1465                }
1466
1467                i18nParams.put("confirmUri", new I18nizableText(confirmUri));
1468            }
1469            finally
1470            {
1471                _renderingContextHandler.setRenderingContext(currentContext);
1472            }
1473        }
1474        else
1475        {
1476            throw new UserManagementException("No signup page found for site " + siteName + " and language " + language, StatusError.NO_SIGNUP_PAGE);
1477        }
1478
1479        // Add site information in the parameters.
1480        i18nParams.put("siteTitle", new I18nizableText(site.getTitle()));
1481        i18nParams.put("siteUrl", new I18nizableText(site.getUrl()));
1482        
1483        String subject = fromInvitation ? _userSignUpConfiguration.getSubjectForInvitationToSignUpEmail(i18nParams, language) : _userSignUpConfiguration.getSubjectForSignUpEmail(i18nParams, language);
1484        String textBody = fromInvitation ? _userSignUpConfiguration.getTextBodyForInvitationToSignUpEmail(i18nParams, language) : _userSignUpConfiguration.getTextBodyForSignUpEmail(i18nParams, language);
1485        String htmlBody = fromInvitation ? _userSignUpConfiguration.getHtmlBodyForInvitationToSignUpEmail(i18nParams, language) : _userSignUpConfiguration.getHtmlBodyForSignUpEmail(i18nParams, language);
1486
1487        try
1488        {
1489            List<String> errorReport = new ArrayList<>();
1490            
1491            // Send the e-mail.
1492            SendMailHelper.newMail()
1493                          .withSubject(subject)
1494                          .withHTMLBody(htmlBody)
1495                          .withTextBody(textBody)
1496                          .withSender(from)
1497                          .withRecipient(email)
1498                          .withErrorReport(errorReport)
1499                          .sendMail();
1500            
1501            if (errorReport.contains(email))
1502            {
1503                throw new UserManagementException("Error sending the sign-up confirmation mail.", StatusError.MAIL_ERROR);
1504            }
1505        }
1506        catch (MessagingException | IOException e)
1507        {
1508            throw new UserManagementException("Error sending the sign-up confirmation mail.", StatusError.MAIL_ERROR, e);
1509        }
1510    }
1511    
1512    /**
1513     * Send a sign-up confirmation link by e-mail.
1514     * @param siteName the site name.
1515     * @param language the e-mail language.
1516     * @param user the created user
1517     * @throws UserManagementException if an error occurs.
1518     */
1519    protected void sendSignupValidatedMail(String siteName, String language, User user) throws UserManagementException
1520    {
1521        Site site = _siteManager.getSite(siteName);
1522        String from = site.getValue("site-mail-from");
1523        String email = user.getEmail();
1524
1525        if (getLogger().isDebugEnabled())
1526        {
1527            getLogger().debug("Sending signup validation e-mail to " + email);
1528        }
1529        
1530        // Prepare mail.
1531        Map<String, I18nizableTextParameter> i18nParams = new HashMap<>();
1532        i18nParams.put("siteName", new I18nizableText(siteName));
1533        i18nParams.put("fullName", new I18nizableText(user.getFullName()));
1534        i18nParams.put("email", new I18nizableText(user.getEmail()));
1535        i18nParams.put("login", new I18nizableText(user.getIdentity().getLogin()));
1536
1537        // Add site information in the parameters.
1538        i18nParams.put("siteTitle", new I18nizableText(site.getTitle()));
1539        i18nParams.put("siteUrl", new I18nizableText(site.getUrl()));
1540
1541        String subject = _userSignUpConfiguration.getSubjectForSignUpValidatedEmail(i18nParams, language);
1542        String textBody = _userSignUpConfiguration.getTextBodyForSignUpValidatedEmail(i18nParams, language);
1543        String htmlBody = _userSignUpConfiguration.getHtmlBodyForSignUpValidatedEmail(i18nParams, language);
1544
1545        try
1546        {
1547            // Send the e-mail.
1548            SendMailHelper.newMail()
1549                          .withSubject(subject)
1550                          .withHTMLBody(htmlBody)
1551                          .withTextBody(textBody)
1552                          .withSender(from)
1553                          .withRecipient(email)
1554                          .sendMail();
1555        }
1556        catch (MessagingException | IOException e)
1557        {
1558            throw new UserManagementException("Error sending the sign-up validation mail.", StatusError.MAIL_ERROR, e);
1559        }
1560    }
1561
1562    /**
1563     * Update the sign-up request token: reset the date and set a new token.
1564     * @param siteName the site name.
1565     * @param email the user e-mail.
1566     * @param newToken the new token.
1567     * @param population The id of the population
1568     * @param userDirectoryId The id of the user directory of the population
1569     * @throws UserManagementException if an error occurs.
1570     */
1571    protected void updateTempToken(String siteName, String email, String newToken, String population, String userDirectoryId) throws UserManagementException
1572    {
1573        try (SqlSession sqlSession = getSession())
1574        {
1575            String stmtId = "UserSignupManager.updateTempToken";
1576            
1577            Map<String, Object> params = new HashMap<>();
1578            params.put("tempUsersTable", _tempUsersTable);
1579            
1580            Timestamp now = new Timestamp(System.currentTimeMillis());
1581            params.put("subscription_date", now);
1582            params.put("token", newToken);
1583            params.put("site", siteName);
1584            params.put("email", email);
1585            params.put("population", population);
1586            params.put("userDirectory", userDirectoryId);
1587            
1588            sqlSession.update(stmtId, params);
1589            sqlSession.commit();
1590        }
1591        catch (Exception e)
1592        {
1593            throw new UserManagementException("Database error while resetting the subscription for user [" + email + "]", StatusError.DATABASE_ERROR, e);
1594        }
1595    }
1596
1597    /**
1598     * Create a user password change request in the database.
1599     * @param siteName the site name.
1600     * @param login the user login.
1601     * @param token the generated token.
1602     * @param population the population
1603     * @throws UserManagementException if an error occurs.
1604     */
1605    public void addPasswordToken(String siteName, String login, String token, String population) throws UserManagementException
1606    {
1607        try (SqlSession sqlSession = getSession())
1608        {
1609            String stmtId = "UserSignupManager.hasToken";
1610            
1611            Map<String, Object> params = new HashMap<>();
1612            params.put("pwdChangeTable", _pwdChangeTable);
1613            
1614            params.put("site", siteName);
1615            params.put("login", login);
1616            params.put("population", population);
1617            
1618            List<Object> pwdEntries = sqlSession.selectList(stmtId, params);
1619            
1620            if (pwdEntries.isEmpty())
1621            {
1622                // Insert a new token.
1623                stmtId = "UserSignupManager.addPasswordToken";
1624                params = new HashMap<>();
1625                params.put("pwdChangeTable", _pwdChangeTable);
1626                
1627                Timestamp now = new Timestamp(System.currentTimeMillis());
1628                params.put("site", siteName);
1629                params.put("login", login);
1630                params.put("request_date", now);
1631                params.put("token", token);
1632                params.put("population", population);
1633                
1634                sqlSession.insert(stmtId, params);
1635            }
1636            else
1637            {
1638                // Update the existing token.
1639                stmtId = "UserSignupManager.updatePasswordToken";
1640                params = new HashMap<>();
1641                params.put("pwdChangeTable", _pwdChangeTable);
1642                
1643                Timestamp now = new Timestamp(System.currentTimeMillis());
1644                params.put("request_date", now);
1645                params.put("token", token);
1646                params.put("site", siteName);
1647                params.put("login", login);
1648                params.put("population", population);
1649                
1650                sqlSession.update(stmtId, params);
1651            }
1652            
1653            // commit insert or update
1654            sqlSession.commit();
1655        }
1656        catch (Exception e)
1657        {
1658            throw new UserManagementException("Database error while inserting a password change token for " + login, StatusError.DATABASE_ERROR, e);
1659        }
1660    }
1661
1662    /**
1663     * Get a temporary user from his site name and e-mail.
1664     * @param siteName the site name.
1665     * @param email The temporary user e-mail. Cannot be null.
1666     * @param population the population
1667     * @param userDirectoryId the id of the user directory of the population
1668     * @return the temporary user or null if not found.
1669     * @throws UserManagementException if an error occurs.
1670     */
1671    protected TempUser getTempUser(String siteName, String email, String population, String userDirectoryId) throws UserManagementException
1672    {
1673        return getTempUser(siteName, email, null, population, userDirectoryId);
1674    }
1675    
1676    /**
1677     * Get temporary users properties
1678     * @param emails the emails of temporary users
1679     * @param siteName the site name of temporary users
1680     * @return The temporary users properties
1681     * @throws UserManagementException
1682     */
1683    @Callable
1684    public Map<String, Object> getTempUsersProperties(List<String> emails, String siteName) throws UserManagementException
1685    {
1686        List<Map<String, Object>> tempUsers = new ArrayList<>();
1687        List<String> unknownTempUsers = new ArrayList<>();
1688        
1689        for (String email : emails)
1690        {
1691            TempUser tempUser = getTempUser(siteName, email, null, null);
1692            if (tempUser != null)
1693            {
1694                tempUsers.add(_tempUser2json(tempUser, false, true));
1695            }
1696            else
1697            {
1698                unknownTempUsers.add(email);
1699            }
1700        }
1701        
1702        return Map.of("tempusers", tempUsers, "unknownTempusers", unknownTempUsers);
1703    }
1704    
1705    
1706    /**
1707     * Get the temporary users from a given site matching the search parameters
1708     * @param siteName the site name
1709     * @param searchParameters the search parameters
1710     * @param offset index of the start of search
1711     * @param limit the maximum number of results
1712     * @param sorts The sorters
1713     * @return the temporary users as JSON object
1714     * @throws UserManagementException if an error occurs.
1715     */
1716    @Callable
1717    public Map<String, Object> searchTempUsers(String siteName, Map<String, Object> searchParameters, int offset, int limit, List<Map<String, String>> sorts) throws UserManagementException
1718    {
1719        // Remove expired token from user request
1720        removeExpiredTokens();
1721        
1722        Map<String, Object> results = new HashMap<>();
1723        
1724        List<Map<String, Object>> tempUsers = getTempUsers(siteName, searchParameters, offset, limit, sorts).stream()
1725            .map(u -> _tempUser2json(u, true, false))
1726            .collect(Collectors.toList());
1727        
1728        int totalCount = getTotalCount(siteName, searchParameters);
1729        
1730        results.put("users", tempUsers);
1731        results.put("total", totalCount);
1732        
1733        return results;
1734    }
1735    
1736    private Map<String, Object> _tempUser2json(TempUser tempUser, boolean full, boolean withRights)
1737    {
1738        Map<String, Object> userInfos = new HashMap<>();
1739        
1740        String populationId = tempUser.getPopulation();
1741        userInfos.put("population", populationId);
1742        
1743        String udId = tempUser.getUserDirectoryId();
1744        userInfos.put("userDirectory", udId);
1745        
1746        if (full)
1747        {
1748            UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(populationId);
1749            if (userPopulation != null)
1750            {
1751                userInfos.put("populationLabel", userPopulation.getLabel());
1752                
1753                UserDirectory userDirectory = userPopulation.getUserDirectory(udId);
1754                if (userDirectory != null)
1755                {
1756                    String udLabel = userDirectory.getLabel();
1757                    if (StringUtils.isEmpty(udLabel))
1758                    {
1759                        String udModelId = userDirectory.getUserDirectoryModelId();
1760                        UserDirectoryModel udModel = _userDirectoryFactory.getExtension(udModelId);
1761                        userInfos.put("userDirectoryLabel", udModel.getLabel());
1762                    }
1763                    else
1764                    {
1765                        userInfos.put("userDirectoryLabel", udLabel);
1766                    }
1767                }
1768            }
1769        }
1770        
1771        userInfos.put("email", tempUser.getEmail());
1772        userInfos.put("firstname", tempUser.getFirstname());
1773        userInfos.put("lastname", tempUser.getLastname());
1774        userInfos.put("origin", tempUser.getOrigin().name());
1775        userInfos.put("siteName", tempUser.getSite());
1776        
1777        Date rawSubscriptionDate = tempUser.getSubscriptionDate();
1778        ZonedDateTime subscriptionDate = rawSubscriptionDate.toInstant().atZone(ZoneId.systemDefault());
1779        ZonedDateTime expirationDate = subscriptionDate.toLocalDate().atStartOfDay(ZoneId.systemDefault()).plusDays(_userSignUpConfiguration.getTokenValidity());
1780        
1781        userInfos.put("subscriptionDate", subscriptionDate);
1782        userInfos.put("expirationDate", expirationDate);
1783        
1784        userInfos.put("expired", expirationDate.toLocalDate().isBefore(LocalDate.now()));
1785        
1786        if (withRights)
1787        {
1788            boolean canHandle = tempUser.getOrigin() == TempUserOrigin.INVITATION && getAllowedUserPopulationForInvitation(tempUser.getSite()).contains(tempUser.getPopulation());
1789            userInfos.put("canHandle", canHandle);
1790        }
1791        
1792        return userInfos;
1793    }
1794    
1795    /**
1796     * Verify that current user is allowed to handle invitation for a given population
1797     * @param siteName the site name.
1798     * @param populationId The population id
1799     * @throws UserManagementException if user is not allowed
1800     */
1801    protected void checkUserAllowedForInvitation(String siteName, String populationId) throws UserManagementException
1802    {
1803        UserIdentity currentUser = _currentUserProvider.getUser();
1804        
1805        boolean hasRight = _rightManager.hasRight(currentUser, "Web_Rights_HandleInvitations", "/cms") == RightResult.RIGHT_ALLOW
1806                || _rightManager.hasRight(currentUser, "Web_Rights_HandleInvitations_OwnPopulation", "/cms") == RightResult.RIGHT_ALLOW && currentUser.getPopulationId().equals(populationId);
1807        
1808        if (!hasRight)
1809        {
1810            throw new UserManagementException("User is not allowed to handle invitations for population '" + populationId + "' and site name '" + siteName + "'", StatusError.USER_NOT_ALLOWED);
1811        }
1812    }
1813    
1814    /**
1815     * Get the ids of user populations the current user can handle for invitation
1816     * @param siteName the site name
1817     * @return the ids of user populations handled by current users
1818     */
1819    public Set<String> getAllowedUserPopulationForInvitation(String siteName)
1820    {
1821        UserIdentity currentUser = _currentUserProvider.getUser();
1822        boolean canHandleAll = _rightManager.hasRight(currentUser, "Web_Rights_HandleInvitations", "/cms") == RightResult.RIGHT_ALLOW;
1823        
1824        Set<String> populationIds = _populationContextHelper.getUserPopulationsOnContexts(List.of("/sites/" + siteName, "/sites-fo/" + siteName), false);
1825        return populationIds.stream()
1826                .filter(pId -> canHandleAll || _rightManager.hasRight(currentUser, "Web_Rights_HandleInvitations_OwnPopulation", "/cms") == RightResult.RIGHT_ALLOW && currentUser.getPopulationId().equals(pId))
1827                .collect(Collectors.toSet());
1828    }
1829    
1830    /**
1831     * Get the temporary users from a given site matching the search parameters
1832     * @param siteName the site name. Can not be null
1833     * @param searchParameters The search parameters
1834     * @param offset index of the start of search
1835     * @param limit the maximum number of results
1836     * @param sorts The sorters. Can be null or empty
1837     * @return the temporary users
1838     * @throws UserManagementException if an error occurs.
1839     */
1840    public List<TempUser> getTempUsers(String siteName, Map<String, Object> searchParameters, int offset, int limit, List<Map<String, String>> sorts) throws UserManagementException
1841    {
1842        try (SqlSession sqlSession = getSession(); Connection connection = sqlSession.getConnection();)
1843        {
1844            String stmtId = "UserSignupManager.searchTempUsers";
1845            
1846            Map<String, Object> params = new HashMap<>();
1847            params.put("tempUsersTable", _tempUsersTable);
1848            params.put("databaseType", ConnectionHelper.getDatabaseType(connection));
1849            
1850            params.put("site", siteName);
1851            if (sorts != null && !sorts.isEmpty())
1852            {
1853                params.put("sorts", sorts);
1854            }
1855            else
1856            {
1857                // default sort by email
1858                params.put("sorts", List.of(Map.of("property", "email", "direction", "ASC")));
1859            }
1860            
1861            for (Entry<String, Object> searchParameter : searchParameters.entrySet())
1862            {
1863                String paramName = searchParameter.getKey();
1864                Object value = searchParameter.getValue();
1865                
1866                if (value != null && !(value instanceof String) || value instanceof String strValue && StringUtils.isNotEmpty(strValue))
1867                {
1868                    params.put(paramName, "pattern".equals(paramName) ? StringUtils.lowerCase((String) value) : value);
1869                }
1870            }
1871            
1872            return sqlSession.selectList(stmtId, params, new RowBounds(offset, limit));
1873        }
1874        catch (Exception e)
1875        {
1876            throw new UserManagementException("Database error while getting temporay users for site '" + siteName + "'", StatusError.DATABASE_ERROR, e);
1877        }
1878    }
1879    
1880    /**
1881     * Get the number of temporary users matching the search parameters
1882     * @param siteName the site name. Can not be null
1883     * @param searchParameters The search parameters
1884     * @return the total number of results
1885     * @throws UserManagementException
1886     */
1887    public int getTotalCount(String siteName, Map<String, Object> searchParameters) throws UserManagementException
1888    {
1889        try (SqlSession session = getSession(); Connection connection = session.getConnection();)
1890        {
1891           
1892            Map<String, Object> params = new HashMap<>();
1893            params.put("tempUsersTable", _tempUsersTable);
1894            params.put("databaseType", ConnectionHelper.getDatabaseType(connection));
1895            
1896            params.put("site", siteName);
1897            
1898            for (Entry<String, Object> searchParameter : searchParameters.entrySet())
1899            {
1900                String paramName = searchParameter.getKey();
1901                Object value = searchParameter.getValue();
1902                
1903                if (value != null && !(value instanceof String) || value instanceof String strValue && StringUtils.isNotEmpty(strValue))
1904                {
1905                    params.put(paramName, value);
1906                }
1907            }
1908            return session.selectOne("UserSignupManager.getTotalCount", params);
1909        }
1910        catch (SQLException e)
1911        {
1912            throw new UserManagementException("Database error while getting temporay users for site '" + siteName + "'", StatusError.DATABASE_ERROR, e);
1913        }
1914    }
1915
1916    /**
1917     * Get a temporary user from his site name, e-mail and/or token.
1918     * At least one of e-mail and token must be provided.
1919     * @param siteName the site name.
1920     * @param email The temporary user e-mail. Can be null.
1921     * @param token The temporary user token. Can be null.
1922     * @param population the population. Must be not null if email is not null
1923     * @param userDirectoryId the id of the user directory of the population. Must be not null if email is not null
1924     * @return the temporary user or null if not found.
1925     * @throws UserManagementException if an error occurs.
1926     */
1927    protected TempUser getTempUser(String siteName, String email, String token, String population, String userDirectoryId) throws UserManagementException
1928    {
1929        if (StringUtils.isEmpty(email) && StringUtils.isEmpty(token))
1930        {
1931            throw new UserManagementException("Either e-mail or token must be provided.");
1932        }
1933        
1934        try (SqlSession sqlSession = getSession())
1935        {
1936            // Does this email already exists
1937            String stmtId = "UserSignupManager.getTempUser";
1938            
1939            Map<String, Object> params = new HashMap<>();
1940            params.put("tempUsersTable", _tempUsersTable);
1941            
1942            params.put("site", siteName);
1943            if (StringUtils.isNotEmpty(email))
1944            {
1945                params.put("email", email);
1946                if (StringUtils.isNotEmpty(population))
1947                {
1948                    params.put("population", population);
1949                }
1950                if (StringUtils.isNotEmpty(userDirectoryId))
1951                {
1952                    params.put("userDirectory", userDirectoryId);
1953                }
1954            }
1955            
1956            if (StringUtils.isNotEmpty(token))
1957            {
1958                params.put("token", token);
1959            }
1960            
1961            return sqlSession.selectOne(stmtId, params);
1962        }
1963        catch (Exception e)
1964        {
1965            throw new UserManagementException("Database error while getting the request for user [" + email + "] and token [" + token + "]", StatusError.DATABASE_ERROR, e);
1966        }
1967    }
1968
1969    /**
1970     * Remove the temporary .
1971     * @param siteName the site name.
1972     * @param email the user e-mail address.
1973     * @param token the request token.
1974     * @param population the population
1975     * @param userDirectoryId the id of the user directory of the population
1976     * @throws UserManagementException if an error occurs.
1977     */
1978    protected void removeTempUser(String siteName, String email, String token, String population, String userDirectoryId) throws UserManagementException
1979    {
1980        try (SqlSession sqlSession = getSession())
1981        {
1982            String stmtId = "UserSignupManager.removeTempUser";
1983            
1984            Map<String, Object> params = new HashMap<>();
1985            params.put("tempUsersTable", _tempUsersTable);
1986            
1987            params.put("site", siteName);
1988            params.put("email", email);
1989            
1990            if (StringUtils.isNotBlank(token))
1991            {
1992                params.put("token", token);
1993            }
1994            if (StringUtils.isNotBlank(population))
1995            {
1996                params.put("population", population);
1997            }
1998            if (StringUtils.isNotBlank(userDirectoryId))
1999            {
2000                params.put("userDirectory", userDirectoryId);
2001            }
2002            sqlSession.delete(stmtId, params);
2003            sqlSession.commit();
2004        }
2005        catch (Exception e)
2006        {
2007            throw new UserManagementException("Database error while removing the token of user " + email, StatusError.DATABASE_ERROR, e);
2008        }
2009    }
2010
2011    /**
2012     * Remove the password change request.
2013     * @param siteName the site name.
2014     * @param login the user login.
2015     * @param token the request token.
2016     * @param population the population
2017     * @throws UserManagementException if an error occurs.
2018     */
2019    protected void removePasswordToken(String siteName, String login, String token, String population) throws UserManagementException
2020    {
2021        try (SqlSession sqlSession = getSession())
2022        {
2023            String stmtId = "UserSignupManager.removePasswordToken";
2024            
2025            Map<String, Object> params = new HashMap<>();
2026            params.put("pwdChangeTable", _pwdChangeTable);
2027            
2028            params.put("site", siteName);
2029            params.put("login", login);
2030            params.put("token", token);
2031            params.put("population", population);
2032            
2033            sqlSession.delete(stmtId, params);
2034            sqlSession.commit();
2035        }
2036        catch (Exception e)
2037        {
2038            throw new UserManagementException("Database error while removing the token of user " + login, StatusError.DATABASE_ERROR, e);
2039        }
2040    }
2041
2042    /**
2043     * Send a sign-up confirmation link by e-mail.
2044     * @param siteName the site name.
2045     * @param language the e-mail language.
2046     * @param user the user object.
2047     * @param token the generated token.
2048     * @throws UserManagementException if an error occurs.
2049     */
2050    protected void sendResetPasswordMail(String siteName, String language, User user, String token) throws UserManagementException
2051    {
2052        String login = user.getIdentity().getLogin();
2053        String population = user.getIdentity().getPopulationId();
2054
2055        if (getLogger().isDebugEnabled())
2056        {
2057            getLogger().debug("Sending reset password e-mail to " + login);
2058        }
2059
2060        Site site = _siteManager.getSite(siteName);
2061        String from = site.getValue("site-mail-from");
2062
2063        // Prepare mail
2064        Map<String, I18nizableTextParameter> i18nParams = new HashMap<>();
2065        i18nParams.put("siteName", new I18nizableText(siteName));
2066        i18nParams.put("login", new I18nizableText(login));
2067        i18nParams.put("email", new I18nizableText(user.getEmail()));
2068        i18nParams.put("fullName", new I18nizableText(user.getFullName()));
2069        i18nParams.put("token", new I18nizableText(token));
2070
2071        Page passwordPage = getPwdChangePage(siteName, language);
2072        if (passwordPage != null)
2073        {
2074            // Compute the confirmation URI and add it to the parameters.
2075            String confirmUri = getResetPasswordUri(passwordPage, login, population, token, true);
2076            i18nParams.put("confirmUri", new I18nizableText(confirmUri));
2077        }
2078        else
2079        {
2080            throw new UserManagementException("No password change page found for site " + siteName + " and language " + language, StatusError.NO_PASSWORD_CHANGE_PAGE);
2081        }
2082
2083        // Add site information in the parameters.
2084        i18nParams.put("siteTitle", new I18nizableText(site.getTitle()));
2085        i18nParams.put("siteUrl", new I18nizableText(site.getUrl()));
2086
2087        String subject = _userSignUpConfiguration.getSubjectForResetPwdEmail(i18nParams, language);
2088        String textBody = _userSignUpConfiguration.getTextBodyForResetPwdEmail(i18nParams, language);
2089        String htmlBody = _userSignUpConfiguration.getHtmlBodyForResetPwdEmail(i18nParams, language);
2090
2091        try
2092        {
2093            // Send the e-mail.
2094            SendMailHelper.newMail()
2095                          .withSubject(subject)
2096                          .withHTMLBody(htmlBody)
2097                          .withTextBody(textBody)
2098                          .withSender(from)
2099                          .withRecipient(user.getEmail())
2100                          .sendMail();
2101        }
2102        catch (MessagingException | IOException e)
2103        {
2104            throw new UserManagementException("Error sending a password reset e-mail.", StatusError.MAIL_ERROR, e);
2105        }
2106    }
2107    
2108    /**
2109     * Get the URI to reset a password
2110     * @param passwordPage The passwodr page. Can not be null.
2111     * @param login the user login
2112     * @param population the user population
2113     * @param token the generated token
2114     * @param absolute true to get absolute uri
2115     * @return the computed uri
2116     */
2117    public String getResetPasswordUri(Page passwordPage, String login, String population, String token, boolean absolute)
2118    {
2119        String encodedLogin = URIUtils.encodeParameter(login);
2120        String encodedPopulation = URIUtils.encodeParameter(population);
2121
2122        // Compute the confirmation URI and add it to the parameters.
2123        String confirmUri = ResolveURIComponent.resolve("page", passwordPage.getId(), false, absolute);
2124        return confirmUri + "?login=" + encodedLogin + "&population=" + encodedPopulation + "&token=" + token;
2125    }
2126
2127    /**
2128     * Bean representing a user sign-up request.
2129     */
2130    public static class TempUser
2131    {
2132        /** The site name. */
2133        protected String _site;
2134        
2135        /** The user e-mail. */
2136        protected String _email;
2137
2138        /** The user subscription date. */
2139        protected Date _subscriptionDate;
2140
2141        /** The request token. */
2142        protected String _token;
2143
2144        /** The id of the population */
2145        protected String _population;
2146
2147        /** The id of the user directory of the population */
2148        protected String _userDirectoryId;
2149        
2150        /** The user last name */
2151        protected String _lastname;
2152        
2153        /** The user first name */
2154        protected String _firstname;
2155        
2156        /** The user origin */
2157        protected TempUserOrigin _origin;
2158        
2159        /**
2160         * Enumeration for temporary user origin
2161         *
2162         */
2163        public enum TempUserOrigin 
2164        {
2165            /** When the signup request comes from an invitation */
2166            INVITATION,
2167            /** When the signup request is an user request  */
2168            USER_REQUEST,
2169            /** When the origin of signup request is unknown */
2170            UNKNWON
2171        }
2172
2173        /**
2174         * Constructor.
2175         * @param site the site
2176         * @param email the user's email
2177         * @param subscriptionDate the date of subscription
2178         * @param token the user's token
2179         * @param population The id of the population
2180         * @param userDirectoryId The id of the user directory of the population
2181         * @param lastname the user's lastname
2182         * @param firstname the user's firstname
2183         * @param origin the origin of this temp user
2184         */
2185        public TempUser(String site, String email, Date subscriptionDate, String token, String population, String userDirectoryId, String lastname, String firstname, String origin)
2186        {
2187            this._site = site;
2188            this._email = email;
2189            this._subscriptionDate = subscriptionDate;
2190            this._token = token;
2191            this._population = population;
2192            this._userDirectoryId = userDirectoryId;
2193            this._lastname = lastname;
2194            this._firstname = firstname;
2195            this._origin = StringUtils.isNotBlank(origin) ? TempUserOrigin.valueOf(origin) : TempUserOrigin.UNKNWON;
2196        }
2197        /**
2198         * Get the site.
2199         * @return the site
2200         */
2201        public String getSite()
2202        {
2203            return _site;
2204        }
2205        /**
2206         * Set the site.
2207         * @param site the site to set
2208         */
2209        public void setSite(String site)
2210        {
2211            this._site = site;
2212        }
2213        /**
2214         * Get the email.
2215         * @return _the email
2216         */
2217        public String getEmail()
2218        {
2219            return _email;
2220        }
2221        /**
2222         * Set the email.
2223         * @param email the email to set
2224         */
2225        public void setEmail(String email)
2226        {
2227            this._email = email;
2228        }
2229        /**
2230         * Get the subscriptionDate.
2231         * @return _the subscriptionDate
2232         */
2233        public Date getSubscriptionDate()
2234        {
2235            return _subscriptionDate;
2236        }
2237        /**
2238         * Set the subscriptionDate.
2239         * @param subscriptionDate the subscriptionDate to set
2240         */
2241        public void setSubscriptionDate(Date subscriptionDate)
2242        {
2243            this._subscriptionDate = subscriptionDate;
2244        }
2245        /**
2246         * Get the token.
2247         * @return _the token
2248         */
2249        public String getToken()
2250        {
2251            return _token;
2252        }
2253        /**
2254         * Set the token.
2255         * @param token the token to set
2256         */
2257        public void setToken(String token)
2258        {
2259            this._token = token;
2260        }
2261        /**
2262         * Get the population.
2263         * @return the population
2264         */
2265        public String getPopulation()
2266        {
2267            return _population;
2268        }
2269        /**
2270         * Set the population.
2271         * @param population the population to set
2272         */
2273        public void setPopulation(String population)
2274        {
2275            this._population = population;
2276        }
2277        /**
2278         * Get the user directory id.
2279         * @return the user directory id
2280         */
2281        public String getUserDirectoryId()
2282        {
2283            return _userDirectoryId;
2284        }
2285        /**
2286         * Set the user directory index.
2287         * @param userDirectoryId the user directory id to set
2288         */
2289        public void setUserDirectoryIndex(String userDirectoryId)
2290        {
2291            this._userDirectoryId = userDirectoryId;
2292        }
2293        
2294        /**
2295         * Get the user firstname
2296         * @return the user firstname
2297         */
2298        public String getFirstname()
2299        {
2300            return _firstname;
2301        }
2302        
2303        /**
2304         * Set the user firstname
2305         * @param firstname the user firstname
2306         */
2307        public void setFirstname(String firstname)
2308        {
2309            _firstname = firstname;
2310        }
2311        
2312        /**
2313         * Get the user firstname
2314         * @return the user firstname
2315         */
2316        public String getLastname()
2317        {
2318            return _lastname;
2319        }
2320        
2321        /**
2322         * Set the user lastname
2323         * @param lastname the user lastname
2324         */
2325        public void setLastname(String lastname)
2326        {
2327            _lastname = lastname;
2328        }
2329        
2330        /**
2331         * Return the temp user origin
2332         * @return true the temp user origin
2333         */
2334        public TempUserOrigin getOrigin()
2335        {
2336            return _origin;
2337        }
2338        
2339        /**
2340         * Set the origin of this temporary user
2341         * @param origin the origin
2342         */
2343        public void setOrigin(TempUserOrigin origin)
2344        {
2345            _origin = origin;
2346        }
2347    }
2348
2349}