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.ui.mail.StandardMailBodyHelper;
055import org.ametys.core.user.CurrentUserProvider;
056import org.ametys.core.user.InvalidModificationException;
057import org.ametys.core.user.User;
058import org.ametys.core.user.User.UserCreationOrigin;
059import org.ametys.core.user.UserIdentity;
060import org.ametys.core.user.UserManager;
061import org.ametys.core.user.directory.ModifiableUserDirectory;
062import org.ametys.core.user.directory.NotUniqueUserException;
063import org.ametys.core.user.directory.UserDirectory;
064import org.ametys.core.user.directory.UserDirectoryFactory;
065import org.ametys.core.user.directory.UserDirectoryModel;
066import org.ametys.core.user.population.PopulationContextHelper;
067import org.ametys.core.user.population.UserPopulation;
068import org.ametys.core.user.population.UserPopulationDAO;
069import org.ametys.core.util.I18nUtils;
070import org.ametys.core.util.URIUtils;
071import org.ametys.core.util.mail.SendMailHelper;
072import org.ametys.plugins.repository.AmetysObjectIterable;
073import org.ametys.plugins.repository.AmetysObjectResolver;
074import org.ametys.plugins.repository.AmetysRepositoryException;
075import org.ametys.plugins.repository.query.expression.Expression;
076import org.ametys.plugins.repository.query.expression.Expression.Operator;
077import org.ametys.runtime.i18n.I18nizableText;
078import org.ametys.runtime.i18n.I18nizableTextParameter;
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, List<I18nizableText>> 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, List<I18nizableText>> usersManagerErrors = new HashMap<>(); //foUsersManager.validate(userInfos);
631        Map<String, List<I18nizableText>> 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 List<I18nizableText> 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 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, List<I18nizableText>> usersManagerErrors = modifiableUserDirectory.validate(userInfos);
667        List<I18nizableText> errors = new ArrayList<>();
668
669        // Keep only errors related to the password field.
670        if (usersManagerErrors.containsKey("password"))
671        {
672            errors.addAll(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 = _userManager.getUserByDirectory(population, userDirectoryId, 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=" + URIUtils.encodeParameter(lastname);
1461                }
1462                if (StringUtils.isNotEmpty(firstname))
1463                {
1464                    confirmUri += "&firstname=" + URIUtils.encodeParameter(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            String wrappedHTMLBody = null;
1492            try
1493            {
1494                wrappedHTMLBody = htmlBody != null ? StandardMailBodyHelper.newHTMLBody()
1495                    .withTitle(subject)
1496                    .withMessage(htmlBody)
1497                    .withLink(site.getUrl(), new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_SITE_LINK_TITLE"))
1498                    .build() : null;
1499            }
1500            catch (IOException e)
1501            {
1502                wrappedHTMLBody = htmlBody;
1503                getLogger().warn("Failed to build wrapped HTML body for signup up email", e);
1504            }
1505            
1506            // Send the e-mail.
1507            SendMailHelper.newMail()
1508                          .withSubject(subject)
1509                          .withHTMLBody(wrappedHTMLBody)
1510                          .withTextBody(textBody)
1511                          .withSender(from)
1512                          .withRecipient(email)
1513                          .withErrorReport(errorReport)
1514                          .sendMail();
1515            
1516            if (errorReport.contains(email))
1517            {
1518                throw new UserManagementException("Error sending the sign-up confirmation mail.", StatusError.MAIL_ERROR);
1519            }
1520        }
1521        catch (MessagingException | IOException e)
1522        {
1523            throw new UserManagementException("Error sending the sign-up confirmation mail.", StatusError.MAIL_ERROR, e);
1524        }
1525    }
1526    
1527    /**
1528     * Send a sign-up confirmation link by e-mail.
1529     * @param siteName the site name.
1530     * @param language the e-mail language.
1531     * @param user the created user
1532     * @throws UserManagementException if an error occurs.
1533     */
1534    protected void sendSignupValidatedMail(String siteName, String language, User user) throws UserManagementException
1535    {
1536        Site site = _siteManager.getSite(siteName);
1537        String from = site.getValue("site-mail-from");
1538        String email = user.getEmail();
1539
1540        if (getLogger().isDebugEnabled())
1541        {
1542            getLogger().debug("Sending signup validation e-mail to " + email);
1543        }
1544        
1545        // Prepare mail.
1546        Map<String, I18nizableTextParameter> i18nParams = new HashMap<>();
1547        i18nParams.put("siteName", new I18nizableText(siteName));
1548        i18nParams.put("fullName", new I18nizableText(user.getFullName()));
1549        i18nParams.put("email", new I18nizableText(user.getEmail()));
1550        i18nParams.put("login", new I18nizableText(user.getIdentity().getLogin()));
1551
1552        // Add site information in the parameters.
1553        i18nParams.put("siteTitle", new I18nizableText(site.getTitle()));
1554        i18nParams.put("siteUrl", new I18nizableText(site.getUrl()));
1555
1556        String subject = _userSignUpConfiguration.getSubjectForSignUpValidatedEmail(i18nParams, language);
1557        String textBody = _userSignUpConfiguration.getTextBodyForSignUpValidatedEmail(i18nParams, language);
1558        String htmlBody = _userSignUpConfiguration.getHtmlBodyForSignUpValidatedEmail(i18nParams, language);
1559
1560        try
1561        {
1562            String wrappedHTMLBody = null;
1563            try
1564            {
1565                wrappedHTMLBody = htmlBody != null ? StandardMailBodyHelper.newHTMLBody()
1566                    .withTitle(subject)
1567                    .withMessage(htmlBody)
1568                    .withLink(site.getUrl(), new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_SITE_LINK_TITLE"))
1569                    .build() : null;
1570            }
1571            catch (IOException e)
1572            {
1573                wrappedHTMLBody = htmlBody;
1574                getLogger().warn("Failed to build wrapped HTML body for signup up validation email", e);
1575            }
1576            
1577            // Send the e-mail.
1578            SendMailHelper.newMail()
1579                          .withSubject(subject)
1580                          .withHTMLBody(wrappedHTMLBody)
1581                          .withTextBody(textBody)
1582                          .withSender(from)
1583                          .withRecipient(email)
1584                          .sendMail();
1585        }
1586        catch (MessagingException | IOException e)
1587        {
1588            throw new UserManagementException("Error sending the sign-up validation mail.", StatusError.MAIL_ERROR, e);
1589        }
1590    }
1591
1592    /**
1593     * Update the sign-up request token: reset the date and set a new token.
1594     * @param siteName the site name.
1595     * @param email the user e-mail.
1596     * @param newToken the new token.
1597     * @param population The id of the population
1598     * @param userDirectoryId The id of the user directory of the population
1599     * @throws UserManagementException if an error occurs.
1600     */
1601    protected void updateTempToken(String siteName, String email, String newToken, String population, String userDirectoryId) throws UserManagementException
1602    {
1603        try (SqlSession sqlSession = getSession())
1604        {
1605            String stmtId = "UserSignupManager.updateTempToken";
1606            
1607            Map<String, Object> params = new HashMap<>();
1608            params.put("tempUsersTable", _tempUsersTable);
1609            
1610            Timestamp now = new Timestamp(System.currentTimeMillis());
1611            params.put("subscription_date", now);
1612            params.put("token", newToken);
1613            params.put("site", siteName);
1614            params.put("email", email);
1615            params.put("population", population);
1616            params.put("userDirectory", userDirectoryId);
1617            
1618            sqlSession.update(stmtId, params);
1619            sqlSession.commit();
1620        }
1621        catch (Exception e)
1622        {
1623            throw new UserManagementException("Database error while resetting the subscription for user [" + email + "]", StatusError.DATABASE_ERROR, e);
1624        }
1625    }
1626
1627    /**
1628     * Create a user password change request in the database.
1629     * @param siteName the site name.
1630     * @param login the user login.
1631     * @param token the generated token.
1632     * @param population the population
1633     * @throws UserManagementException if an error occurs.
1634     */
1635    public void addPasswordToken(String siteName, String login, String token, String population) throws UserManagementException
1636    {
1637        try (SqlSession sqlSession = getSession())
1638        {
1639            String stmtId = "UserSignupManager.hasToken";
1640            
1641            Map<String, Object> params = new HashMap<>();
1642            params.put("pwdChangeTable", _pwdChangeTable);
1643            
1644            params.put("site", siteName);
1645            params.put("login", login);
1646            params.put("population", population);
1647            
1648            List<Object> pwdEntries = sqlSession.selectList(stmtId, params);
1649            
1650            if (pwdEntries.isEmpty())
1651            {
1652                // Insert a new token.
1653                stmtId = "UserSignupManager.addPasswordToken";
1654                params = new HashMap<>();
1655                params.put("pwdChangeTable", _pwdChangeTable);
1656                
1657                Timestamp now = new Timestamp(System.currentTimeMillis());
1658                params.put("site", siteName);
1659                params.put("login", login);
1660                params.put("request_date", now);
1661                params.put("token", token);
1662                params.put("population", population);
1663                
1664                sqlSession.insert(stmtId, params);
1665            }
1666            else
1667            {
1668                // Update the existing token.
1669                stmtId = "UserSignupManager.updatePasswordToken";
1670                params = new HashMap<>();
1671                params.put("pwdChangeTable", _pwdChangeTable);
1672                
1673                Timestamp now = new Timestamp(System.currentTimeMillis());
1674                params.put("request_date", now);
1675                params.put("token", token);
1676                params.put("site", siteName);
1677                params.put("login", login);
1678                params.put("population", population);
1679                
1680                sqlSession.update(stmtId, params);
1681            }
1682            
1683            // commit insert or update
1684            sqlSession.commit();
1685        }
1686        catch (Exception e)
1687        {
1688            throw new UserManagementException("Database error while inserting a password change token for " + login, StatusError.DATABASE_ERROR, e);
1689        }
1690    }
1691
1692    /**
1693     * Get a temporary user from his site name and e-mail.
1694     * @param siteName the site name.
1695     * @param email The temporary user e-mail. Cannot be null.
1696     * @param population the population
1697     * @param userDirectoryId the id of the user directory of the population
1698     * @return the temporary user or null if not found.
1699     * @throws UserManagementException if an error occurs.
1700     */
1701    protected TempUser getTempUser(String siteName, String email, String population, String userDirectoryId) throws UserManagementException
1702    {
1703        return getTempUser(siteName, email, null, population, userDirectoryId);
1704    }
1705    
1706    /**
1707     * Get temporary users properties
1708     * @param emails the emails of temporary users
1709     * @param siteName the site name of temporary users
1710     * @return The temporary users properties
1711     * @throws UserManagementException if an error occurs.
1712     */
1713    @Callable
1714    public Map<String, Object> getTempUsersProperties(List<String> emails, String siteName) throws UserManagementException
1715    {
1716        List<Map<String, Object>> tempUsers = new ArrayList<>();
1717        List<String> unknownTempUsers = new ArrayList<>();
1718        
1719        for (String email : emails)
1720        {
1721            TempUser tempUser = getTempUser(siteName, email, null, null);
1722            if (tempUser != null)
1723            {
1724                tempUsers.add(_tempUser2json(tempUser, false, true));
1725            }
1726            else
1727            {
1728                unknownTempUsers.add(email);
1729            }
1730        }
1731        
1732        return Map.of("tempusers", tempUsers, "unknownTempusers", unknownTempUsers);
1733    }
1734    
1735    
1736    /**
1737     * Get the temporary users from a given site matching the search parameters
1738     * @param siteName the site name
1739     * @param searchParameters the search parameters
1740     * @param offset index of the start of search
1741     * @param limit the maximum number of results
1742     * @param sorts The sorters
1743     * @return the temporary users as JSON object
1744     * @throws UserManagementException if an error occurs.
1745     */
1746    @Callable
1747    public Map<String, Object> searchTempUsers(String siteName, Map<String, Object> searchParameters, int offset, int limit, List<Map<String, String>> sorts) throws UserManagementException
1748    {
1749        // Remove expired token from user request
1750        removeExpiredTokens();
1751        
1752        Map<String, Object> results = new HashMap<>();
1753        
1754        List<Map<String, Object>> tempUsers = getTempUsers(siteName, searchParameters, offset, limit, sorts).stream()
1755            .map(u -> _tempUser2json(u, true, false))
1756            .collect(Collectors.toList());
1757        
1758        int totalCount = getTotalCount(siteName, searchParameters);
1759        
1760        results.put("users", tempUsers);
1761        results.put("total", totalCount);
1762        
1763        return results;
1764    }
1765    
1766    private Map<String, Object> _tempUser2json(TempUser tempUser, boolean full, boolean withRights)
1767    {
1768        Map<String, Object> userInfos = new HashMap<>();
1769        
1770        String populationId = tempUser.getPopulation();
1771        userInfos.put("population", populationId);
1772        
1773        String udId = tempUser.getUserDirectoryId();
1774        userInfos.put("userDirectory", udId);
1775        
1776        if (full)
1777        {
1778            UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(populationId);
1779            if (userPopulation != null)
1780            {
1781                userInfos.put("populationLabel", userPopulation.getLabel());
1782                
1783                UserDirectory userDirectory = userPopulation.getUserDirectory(udId);
1784                if (userDirectory != null)
1785                {
1786                    String udLabel = userDirectory.getLabel();
1787                    if (StringUtils.isEmpty(udLabel))
1788                    {
1789                        String udModelId = userDirectory.getUserDirectoryModelId();
1790                        UserDirectoryModel udModel = _userDirectoryFactory.getExtension(udModelId);
1791                        userInfos.put("userDirectoryLabel", udModel.getLabel());
1792                    }
1793                    else
1794                    {
1795                        userInfos.put("userDirectoryLabel", udLabel);
1796                    }
1797                }
1798            }
1799        }
1800        
1801        userInfos.put("email", tempUser.getEmail());
1802        userInfos.put("firstname", tempUser.getFirstname());
1803        userInfos.put("lastname", tempUser.getLastname());
1804        userInfos.put("origin", tempUser.getOrigin().name());
1805        userInfos.put("siteName", tempUser.getSite());
1806        
1807        Date rawSubscriptionDate = tempUser.getSubscriptionDate();
1808        ZonedDateTime subscriptionDate = rawSubscriptionDate.toInstant().atZone(ZoneId.systemDefault());
1809        ZonedDateTime expirationDate = subscriptionDate.toLocalDate().atStartOfDay(ZoneId.systemDefault()).plusDays(_userSignUpConfiguration.getTokenValidity());
1810        
1811        userInfos.put("subscriptionDate", subscriptionDate);
1812        userInfos.put("expirationDate", expirationDate);
1813        
1814        userInfos.put("expired", expirationDate.toLocalDate().isBefore(LocalDate.now()));
1815        
1816        if (withRights)
1817        {
1818            boolean canHandle = tempUser.getOrigin() == TempUserOrigin.INVITATION && getAllowedUserPopulationForInvitation(tempUser.getSite()).contains(tempUser.getPopulation());
1819            userInfos.put("canHandle", canHandle);
1820        }
1821        
1822        return userInfos;
1823    }
1824    
1825    /**
1826     * Verify that current user is allowed to handle invitation for a given population
1827     * @param siteName the site name.
1828     * @param populationId The population id
1829     * @throws UserManagementException if user is not allowed
1830     */
1831    protected void checkUserAllowedForInvitation(String siteName, String populationId) throws UserManagementException
1832    {
1833        UserIdentity currentUser = _currentUserProvider.getUser();
1834        
1835        boolean hasRight = _rightManager.hasRight(currentUser, "Web_Rights_HandleInvitations", "/cms") == RightResult.RIGHT_ALLOW
1836                || _rightManager.hasRight(currentUser, "Web_Rights_HandleInvitations_OwnPopulation", "/cms") == RightResult.RIGHT_ALLOW && currentUser.getPopulationId().equals(populationId);
1837        
1838        if (!hasRight)
1839        {
1840            throw new UserManagementException("User is not allowed to handle invitations for population '" + populationId + "' and site name '" + siteName + "'", StatusError.USER_NOT_ALLOWED);
1841        }
1842    }
1843    
1844    /**
1845     * Get the ids of user populations the current user can handle for invitation
1846     * @param siteName the site name
1847     * @return the ids of user populations handled by current users
1848     */
1849    public Set<String> getAllowedUserPopulationForInvitation(String siteName)
1850    {
1851        UserIdentity currentUser = _currentUserProvider.getUser();
1852        boolean canHandleAll = _rightManager.hasRight(currentUser, "Web_Rights_HandleInvitations", "/cms") == RightResult.RIGHT_ALLOW;
1853        
1854        Set<String> populationIds = _populationContextHelper.getUserPopulationsOnContexts(List.of("/sites/" + siteName, "/sites-fo/" + siteName), false);
1855        return populationIds.stream()
1856                .filter(pId -> canHandleAll || _rightManager.hasRight(currentUser, "Web_Rights_HandleInvitations_OwnPopulation", "/cms") == RightResult.RIGHT_ALLOW && currentUser.getPopulationId().equals(pId))
1857                .collect(Collectors.toSet());
1858    }
1859    
1860    /**
1861     * Get the temporary users from a given site matching the search parameters
1862     * @param siteName the site name. Can not be null
1863     * @param searchParameters The search parameters
1864     * @param offset index of the start of search
1865     * @param limit the maximum number of results
1866     * @param sorts The sorters. Can be null or empty
1867     * @return the temporary users
1868     * @throws UserManagementException if an error occurs.
1869     */
1870    public List<TempUser> getTempUsers(String siteName, Map<String, Object> searchParameters, int offset, int limit, List<Map<String, String>> sorts) throws UserManagementException
1871    {
1872        try (SqlSession sqlSession = getSession(); Connection connection = sqlSession.getConnection();)
1873        {
1874            String stmtId = "UserSignupManager.searchTempUsers";
1875            
1876            Map<String, Object> params = new HashMap<>();
1877            params.put("tempUsersTable", _tempUsersTable);
1878            params.put("databaseType", ConnectionHelper.getDatabaseType(connection));
1879            
1880            params.put("site", siteName);
1881            if (sorts != null && !sorts.isEmpty())
1882            {
1883                params.put("sorts", sorts);
1884            }
1885            else
1886            {
1887                // default sort by email
1888                params.put("sorts", List.of(Map.of("property", "email", "direction", "ASC")));
1889            }
1890            
1891            for (Entry<String, Object> searchParameter : searchParameters.entrySet())
1892            {
1893                String paramName = searchParameter.getKey();
1894                Object value = searchParameter.getValue();
1895                
1896                if (value != null && !(value instanceof String) || value instanceof String strValue && StringUtils.isNotEmpty(strValue))
1897                {
1898                    params.put(paramName, "pattern".equals(paramName) ? StringUtils.lowerCase((String) value) : value);
1899                }
1900            }
1901            
1902            return sqlSession.selectList(stmtId, params, new RowBounds(offset, limit));
1903        }
1904        catch (Exception e)
1905        {
1906            throw new UserManagementException("Database error while getting temporay users for site '" + siteName + "'", StatusError.DATABASE_ERROR, e);
1907        }
1908    }
1909    
1910    /**
1911     * Get the number of temporary users matching the search parameters
1912     * @param siteName the site name. Can not be null
1913     * @param searchParameters The search parameters
1914     * @return the total number of results
1915     * @throws UserManagementException if an error occurs.
1916     */
1917    public int getTotalCount(String siteName, Map<String, Object> searchParameters) throws UserManagementException
1918    {
1919        try (SqlSession session = getSession(); Connection connection = session.getConnection();)
1920        {
1921           
1922            Map<String, Object> params = new HashMap<>();
1923            params.put("tempUsersTable", _tempUsersTable);
1924            params.put("databaseType", ConnectionHelper.getDatabaseType(connection));
1925            
1926            params.put("site", siteName);
1927            
1928            for (Entry<String, Object> searchParameter : searchParameters.entrySet())
1929            {
1930                String paramName = searchParameter.getKey();
1931                Object value = searchParameter.getValue();
1932                
1933                if (value != null && !(value instanceof String) || value instanceof String strValue && StringUtils.isNotEmpty(strValue))
1934                {
1935                    params.put(paramName, value);
1936                }
1937            }
1938            return session.selectOne("UserSignupManager.getTotalCount", params);
1939        }
1940        catch (SQLException e)
1941        {
1942            throw new UserManagementException("Database error while getting temporay users for site '" + siteName + "'", StatusError.DATABASE_ERROR, e);
1943        }
1944    }
1945
1946    /**
1947     * Get a temporary user from his site name, e-mail and/or token.
1948     * At least one of e-mail and token must be provided.
1949     * @param siteName the site name.
1950     * @param email The temporary user e-mail. Can be null.
1951     * @param token The temporary user token. Can be null.
1952     * @param population the population. Must be not null if email is not null
1953     * @param userDirectoryId the id of the user directory of the population. Must be not null if email is not null
1954     * @return the temporary user or null if not found.
1955     * @throws UserManagementException if an error occurs.
1956     */
1957    protected TempUser getTempUser(String siteName, String email, String token, String population, String userDirectoryId) throws UserManagementException
1958    {
1959        if (StringUtils.isEmpty(email) && StringUtils.isEmpty(token))
1960        {
1961            throw new UserManagementException("Either e-mail or token must be provided.");
1962        }
1963        
1964        try (SqlSession sqlSession = getSession())
1965        {
1966            // Does this email already exists
1967            String stmtId = "UserSignupManager.getTempUser";
1968            
1969            Map<String, Object> params = new HashMap<>();
1970            params.put("tempUsersTable", _tempUsersTable);
1971            
1972            params.put("site", siteName);
1973            if (StringUtils.isNotEmpty(email))
1974            {
1975                params.put("email", email);
1976                if (StringUtils.isNotEmpty(population))
1977                {
1978                    params.put("population", population);
1979                }
1980                if (StringUtils.isNotEmpty(userDirectoryId))
1981                {
1982                    params.put("userDirectory", userDirectoryId);
1983                }
1984            }
1985            
1986            if (StringUtils.isNotEmpty(token))
1987            {
1988                params.put("token", token);
1989            }
1990            
1991            return sqlSession.selectOne(stmtId, params);
1992        }
1993        catch (Exception e)
1994        {
1995            throw new UserManagementException("Database error while getting the request for user [" + email + "] and token [" + token + "]", StatusError.DATABASE_ERROR, e);
1996        }
1997    }
1998
1999    /**
2000     * Remove the temporary .
2001     * @param siteName the site name.
2002     * @param email the user e-mail address.
2003     * @param token the request token.
2004     * @param population the population
2005     * @param userDirectoryId the id of the user directory of the population
2006     * @throws UserManagementException if an error occurs.
2007     */
2008    protected void removeTempUser(String siteName, String email, String token, String population, String userDirectoryId) throws UserManagementException
2009    {
2010        try (SqlSession sqlSession = getSession())
2011        {
2012            String stmtId = "UserSignupManager.removeTempUser";
2013            
2014            Map<String, Object> params = new HashMap<>();
2015            params.put("tempUsersTable", _tempUsersTable);
2016            
2017            params.put("site", siteName);
2018            params.put("email", email);
2019            
2020            if (StringUtils.isNotBlank(token))
2021            {
2022                params.put("token", token);
2023            }
2024            if (StringUtils.isNotBlank(population))
2025            {
2026                params.put("population", population);
2027            }
2028            if (StringUtils.isNotBlank(userDirectoryId))
2029            {
2030                params.put("userDirectory", userDirectoryId);
2031            }
2032            sqlSession.delete(stmtId, params);
2033            sqlSession.commit();
2034        }
2035        catch (Exception e)
2036        {
2037            throw new UserManagementException("Database error while removing the token of user " + email, StatusError.DATABASE_ERROR, e);
2038        }
2039    }
2040
2041    /**
2042     * Remove the password change request.
2043     * @param siteName the site name.
2044     * @param login the user login.
2045     * @param token the request token.
2046     * @param population the population
2047     * @throws UserManagementException if an error occurs.
2048     */
2049    protected void removePasswordToken(String siteName, String login, String token, String population) throws UserManagementException
2050    {
2051        try (SqlSession sqlSession = getSession())
2052        {
2053            String stmtId = "UserSignupManager.removePasswordToken";
2054            
2055            Map<String, Object> params = new HashMap<>();
2056            params.put("pwdChangeTable", _pwdChangeTable);
2057            
2058            params.put("site", siteName);
2059            params.put("login", login);
2060            params.put("token", token);
2061            params.put("population", population);
2062            
2063            sqlSession.delete(stmtId, params);
2064            sqlSession.commit();
2065        }
2066        catch (Exception e)
2067        {
2068            throw new UserManagementException("Database error while removing the token of user " + login, StatusError.DATABASE_ERROR, e);
2069        }
2070    }
2071
2072    /**
2073     * Send a sign-up confirmation link by e-mail.
2074     * @param siteName the site name.
2075     * @param language the e-mail language.
2076     * @param user the user object.
2077     * @param token the generated token.
2078     * @throws UserManagementException if an error occurs.
2079     */
2080    protected void sendResetPasswordMail(String siteName, String language, User user, String token) throws UserManagementException
2081    {
2082        String login = user.getIdentity().getLogin();
2083        String population = user.getIdentity().getPopulationId();
2084
2085        if (getLogger().isDebugEnabled())
2086        {
2087            getLogger().debug("Sending reset password e-mail to " + login);
2088        }
2089
2090        Site site = _siteManager.getSite(siteName);
2091        String from = site.getValue("site-mail-from");
2092
2093        // Prepare mail
2094        Map<String, I18nizableTextParameter> i18nParams = new HashMap<>();
2095        i18nParams.put("siteName", new I18nizableText(siteName));
2096        i18nParams.put("login", new I18nizableText(login));
2097        i18nParams.put("email", new I18nizableText(user.getEmail()));
2098        i18nParams.put("fullName", new I18nizableText(user.getFullName()));
2099        i18nParams.put("token", new I18nizableText(token));
2100
2101        Page passwordPage = getPwdChangePage(siteName, language);
2102        if (passwordPage != null)
2103        {
2104            // Compute the confirmation URI and add it to the parameters.
2105            String confirmUri = getResetPasswordUri(passwordPage, login, population, token, true);
2106            i18nParams.put("confirmUri", new I18nizableText(confirmUri));
2107        }
2108        else
2109        {
2110            throw new UserManagementException("No password change page found for site " + siteName + " and language " + language, StatusError.NO_PASSWORD_CHANGE_PAGE);
2111        }
2112
2113        // Add site information in the parameters.
2114        i18nParams.put("siteTitle", new I18nizableText(site.getTitle()));
2115        i18nParams.put("siteUrl", new I18nizableText(site.getUrl()));
2116
2117        String subject = _userSignUpConfiguration.getSubjectForResetPwdEmail(i18nParams, language);
2118        String textBody = _userSignUpConfiguration.getTextBodyForResetPwdEmail(i18nParams, language);
2119        String htmlBody = _userSignUpConfiguration.getHtmlBodyForResetPwdEmail(i18nParams, language);
2120
2121        try
2122        {
2123            String wrappedHTMLBody = null;
2124            try
2125            {
2126                wrappedHTMLBody = htmlBody != null ? StandardMailBodyHelper.newHTMLBody()
2127                    .withTitle(subject)
2128                    .withMessage(htmlBody)
2129                    .withLink(site.getUrl(), new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_SITE_LINK_TITLE"))
2130                    .build() : null;
2131            }
2132            catch (IOException e)
2133            {
2134                wrappedHTMLBody = htmlBody;
2135                getLogger().warn("Failed to build wrapped HTML body for reset password email", e);
2136            }
2137            
2138            // Send the e-mail.
2139            SendMailHelper.newMail()
2140                          .withSubject(subject)
2141                          .withHTMLBody(wrappedHTMLBody)
2142                          .withTextBody(textBody)
2143                          .withSender(from)
2144                          .withRecipient(user.getEmail())
2145                          .sendMail();
2146        }
2147        catch (MessagingException | IOException e)
2148        {
2149            throw new UserManagementException("Error sending a password reset e-mail.", StatusError.MAIL_ERROR, e);
2150        }
2151    }
2152    
2153    /**
2154     * Get the URI to reset a password
2155     * @param passwordPage The passwodr page. Can not be null.
2156     * @param login the user login
2157     * @param population the user population
2158     * @param token the generated token
2159     * @param absolute true to get absolute uri
2160     * @return the computed uri
2161     */
2162    public String getResetPasswordUri(Page passwordPage, String login, String population, String token, boolean absolute)
2163    {
2164        String encodedLogin = URIUtils.encodeParameter(login);
2165        String encodedPopulation = URIUtils.encodeParameter(population);
2166
2167        // Compute the confirmation URI and add it to the parameters.
2168        String confirmUri = ResolveURIComponent.resolve("page", passwordPage.getId(), false, absolute);
2169        return confirmUri + "?login=" + encodedLogin + "&population=" + encodedPopulation + "&token=" + token;
2170    }
2171
2172    /**
2173     * Bean representing a user sign-up request.
2174     */
2175    public static class TempUser
2176    {
2177        /** The site name. */
2178        protected String _site;
2179        
2180        /** The user e-mail. */
2181        protected String _email;
2182
2183        /** The user subscription date. */
2184        protected Date _subscriptionDate;
2185
2186        /** The request token. */
2187        protected String _token;
2188
2189        /** The id of the population */
2190        protected String _population;
2191
2192        /** The id of the user directory of the population */
2193        protected String _userDirectoryId;
2194        
2195        /** The user last name */
2196        protected String _lastname;
2197        
2198        /** The user first name */
2199        protected String _firstname;
2200        
2201        /** The user origin */
2202        protected TempUserOrigin _origin;
2203        
2204        /**
2205         * Enumeration for temporary user origin
2206         *
2207         */
2208        public enum TempUserOrigin 
2209        {
2210            /** When the signup request comes from an invitation */
2211            INVITATION,
2212            /** When the signup request is an user request  */
2213            USER_REQUEST,
2214            /** When the origin of signup request is unknown */
2215            UNKNWON
2216        }
2217
2218        /**
2219         * Constructor.
2220         * @param site the site
2221         * @param email the user's email
2222         * @param subscriptionDate the date of subscription
2223         * @param token the user's token
2224         * @param population The id of the population
2225         * @param userDirectoryId The id of the user directory of the population
2226         * @param lastname the user's lastname
2227         * @param firstname the user's firstname
2228         * @param origin the origin of this temp user
2229         */
2230        public TempUser(String site, String email, Date subscriptionDate, String token, String population, String userDirectoryId, String lastname, String firstname, String origin)
2231        {
2232            this._site = site;
2233            this._email = email;
2234            this._subscriptionDate = subscriptionDate;
2235            this._token = token;
2236            this._population = population;
2237            this._userDirectoryId = userDirectoryId;
2238            this._lastname = lastname;
2239            this._firstname = firstname;
2240            this._origin = StringUtils.isNotBlank(origin) ? TempUserOrigin.valueOf(origin) : TempUserOrigin.UNKNWON;
2241        }
2242        /**
2243         * Get the site.
2244         * @return the site
2245         */
2246        public String getSite()
2247        {
2248            return _site;
2249        }
2250        /**
2251         * Set the site.
2252         * @param site the site to set
2253         */
2254        public void setSite(String site)
2255        {
2256            this._site = site;
2257        }
2258        /**
2259         * Get the email.
2260         * @return _the email
2261         */
2262        public String getEmail()
2263        {
2264            return _email;
2265        }
2266        /**
2267         * Set the email.
2268         * @param email the email to set
2269         */
2270        public void setEmail(String email)
2271        {
2272            this._email = email;
2273        }
2274        /**
2275         * Get the subscriptionDate.
2276         * @return _the subscriptionDate
2277         */
2278        public Date getSubscriptionDate()
2279        {
2280            return _subscriptionDate;
2281        }
2282        /**
2283         * Set the subscriptionDate.
2284         * @param subscriptionDate the subscriptionDate to set
2285         */
2286        public void setSubscriptionDate(Date subscriptionDate)
2287        {
2288            this._subscriptionDate = subscriptionDate;
2289        }
2290        /**
2291         * Get the token.
2292         * @return _the token
2293         */
2294        public String getToken()
2295        {
2296            return _token;
2297        }
2298        /**
2299         * Set the token.
2300         * @param token the token to set
2301         */
2302        public void setToken(String token)
2303        {
2304            this._token = token;
2305        }
2306        /**
2307         * Get the population.
2308         * @return the population
2309         */
2310        public String getPopulation()
2311        {
2312            return _population;
2313        }
2314        /**
2315         * Set the population.
2316         * @param population the population to set
2317         */
2318        public void setPopulation(String population)
2319        {
2320            this._population = population;
2321        }
2322        /**
2323         * Get the user directory id.
2324         * @return the user directory id
2325         */
2326        public String getUserDirectoryId()
2327        {
2328            return _userDirectoryId;
2329        }
2330        /**
2331         * Set the user directory index.
2332         * @param userDirectoryId the user directory id to set
2333         */
2334        public void setUserDirectoryIndex(String userDirectoryId)
2335        {
2336            this._userDirectoryId = userDirectoryId;
2337        }
2338        
2339        /**
2340         * Get the user firstname
2341         * @return the user firstname
2342         */
2343        public String getFirstname()
2344        {
2345            return _firstname;
2346        }
2347        
2348        /**
2349         * Set the user firstname
2350         * @param firstname the user firstname
2351         */
2352        public void setFirstname(String firstname)
2353        {
2354            _firstname = firstname;
2355        }
2356        
2357        /**
2358         * Get the user firstname
2359         * @return the user firstname
2360         */
2361        public String getLastname()
2362        {
2363            return _lastname;
2364        }
2365        
2366        /**
2367         * Set the user lastname
2368         * @param lastname the user lastname
2369         */
2370        public void setLastname(String lastname)
2371        {
2372            _lastname = lastname;
2373        }
2374        
2375        /**
2376         * Return the temp user origin
2377         * @return true the temp user origin
2378         */
2379        public TempUserOrigin getOrigin()
2380        {
2381            return _origin;
2382        }
2383        
2384        /**
2385         * Set the origin of this temporary user
2386         * @param origin the origin
2387         */
2388        public void setOrigin(TempUserOrigin origin)
2389        {
2390            _origin = origin;
2391        }
2392    }
2393
2394}