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.UnsupportedEncodingException;
019import java.net.URLEncoder;
020import java.sql.Timestamp;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.UUID;
027
028import javax.mail.MessagingException;
029
030import org.apache.avalon.framework.configuration.Configuration;
031import org.apache.avalon.framework.configuration.ConfigurationException;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.commons.lang.StringUtils;
035import org.apache.ibatis.session.SqlSession;
036import org.joda.time.DateTime;
037
038import org.ametys.cms.transformation.URIResolver;
039import org.ametys.cms.transformation.URIResolverExtensionPoint;
040import org.ametys.core.datasource.AbstractMyBatisDAO;
041import org.ametys.core.user.InvalidModificationException;
042import org.ametys.core.user.User;
043import org.ametys.core.user.UserManager;
044import org.ametys.core.user.directory.ModifiableUserDirectory;
045import org.ametys.core.user.directory.UserDirectory;
046import org.ametys.core.user.population.UserPopulationDAO;
047import org.ametys.core.util.I18nUtils;
048import org.ametys.core.util.mail.SendMailHelper;
049import org.ametys.plugins.repository.AmetysObjectIterable;
050import org.ametys.plugins.repository.AmetysObjectResolver;
051import org.ametys.plugins.repository.query.expression.Expression;
052import org.ametys.plugins.repository.query.expression.Expression.Operator;
053import org.ametys.runtime.i18n.I18nizableText;
054import org.ametys.runtime.parameter.Errors;
055import org.ametys.web.repository.page.Page;
056import org.ametys.web.repository.page.PageQueryHelper;
057import org.ametys.web.repository.site.Site;
058import org.ametys.web.repository.site.SiteManager;
059import org.ametys.web.site.SiteConfigurationExtensionPoint;
060import org.ametys.web.tags.TagExpression;
061
062/**
063 * Manages registration and password recovery of users.
064 */
065public class UserSignupManager extends AbstractMyBatisDAO
066{
067
068    /** The component role. */
069    public static final String ROLE = UserSignupManager.class.getName();
070
071    /** Return code which indicates no error. */
072    public static final int SIGNUP_NO_ERROR = 0;
073
074    /** Temporary signup return code: a user tried to sign-up but the e-mail already exists in the temporary table. */
075    public static final int SIGNUP_ERROR_TEMP_EMAIL_ALREADY_EXISTS = 1;
076
077    /** Temporary signup return code: a user tried to sign-up but the FO UsersManager already possesses a user with this e-mail as login. */
078    public static final int SIGNUP_ERROR_USER_ALREADY_EXISTS = 2;
079
080    /** Token return code: a user provided a token, but it doesn't exist. */
081    public static final int SIGNUP_TOKEN_UNKNOWN = 3;
082
083    /** Token return code: a user provided a token, but it itn't valid anymore. */
084    public static final int SIGNUP_TOKEN_EXPIRED = 4;
085
086    /** Reset token return code: a user asked for a new token, but no subscription request can be found with this e-mail. */
087    public static final int SIGNUP_RESET_ERROR_EMAIL_UNKNOWN = 5;
088
089    /** Lost password return code: the e-mail the user provided doesn't correspond to a user. */
090    public static final int LOST_PASSWORD_USER_UNKNOWN = 5;
091
092    /** The user manager. */
093    protected UserManager _userManager;
094    
095    /** The DAO for user populations */
096    protected UserPopulationDAO _userPopulationDAO;
097
098    /** The site manager. */
099    protected SiteManager _siteManager;
100
101    /** The site configuration extension point. */
102    protected SiteConfigurationExtensionPoint _siteConf;
103
104    /** The ametys object resolver. */
105    protected AmetysObjectResolver _resolver;
106
107    /** The ametys object resolver. */
108    protected URIResolverExtensionPoint _uriResolverEP;
109
110    /** The page URI resolver. */
111    protected URIResolver _pageUriResolver;
112
113    /** The i18n utils. */
114    protected I18nUtils _i18nUtils;
115
116    /** The user sign up configuration */
117    protected UserSignUpConfiguration _userSignUpConfiguration;
118    
119    /** The temporary users table. */
120    protected String _tempUsersTable;
121
122    /** The password change table. */
123    protected String _pwdChangeTable;
124    
125    @Override
126    public void service(ServiceManager serviceManager) throws ServiceException
127    {
128        super.service(serviceManager);
129        
130        _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE);
131        _userPopulationDAO = (UserPopulationDAO) serviceManager.lookup(UserPopulationDAO.ROLE);
132        _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE);
133        _siteConf = (SiteConfigurationExtensionPoint) serviceManager.lookup(SiteConfigurationExtensionPoint.ROLE);
134        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
135        _uriResolverEP = (URIResolverExtensionPoint) serviceManager.lookup(URIResolverExtensionPoint.ROLE);
136        _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE);
137        _userSignUpConfiguration = (UserSignUpConfiguration) serviceManager.lookup(UserSignUpConfiguration.ROLE);
138    }
139
140    @Override
141    public void configure(Configuration configuration) throws ConfigurationException
142    {
143        super.configure(configuration);
144        
145        // Configure pool and tables.
146        _tempUsersTable = configuration.getChild("temp-users-table").getValue();
147        _pwdChangeTable = configuration.getChild("pwd-change-table").getValue();
148    }
149
150    /**
151     * Test if public signup is allowed in a site.
152     * @param siteName the site to test.
153     * @return true if public signup is allowed, false otherwise.
154     */
155    public boolean isPublicSignupAllowed(String siteName)
156    {
157        Boolean publicSignup = _siteConf.getValueAsBoolean(siteName, "public-signup");
158
159        return publicSignup != null && publicSignup;
160    }
161
162    /**
163     * Get the sign-up page in a given site and sitemap.<br>
164     * If more than one page are tagged "sign-up", return the first.
165     * @param siteName the site name.
166     * @param language the sitemap name.
167     * @return the sign-up page or null if not found.
168     */
169    public Page getSignupPage(String siteName, String language)
170    {
171        Page page = null;
172
173        try (AmetysObjectIterable<Page> pages = getSignupPages(siteName, language);)
174        {
175            Iterator<Page> it = pages.iterator();
176            if (it.hasNext())
177            {
178                page = it.next();
179            }
180        }
181
182        return page;
183    }
184
185    /**
186     * Get all the pages tagged "sign-up".
187     * @param siteName the site name.
188     * @param language the sitemap name.
189     * @return an iterable on all the pages tagged "sign-up".
190     */
191    public AmetysObjectIterable<Page> getSignupPages(String siteName, String language)
192    {
193        Expression expression = new TagExpression(Operator.EQ, "USER_SIGNUP");
194        String query = PageQueryHelper.getPageXPathQuery(siteName, language, null, expression, null);
195
196        return _resolver.query(query);
197    }
198
199    /**
200     * Get the password change page in a given site and sitemap.
201     * If more than one page are tagged "password change", return the first.
202     * @param siteName the site name.
203     * @param language the sitemap name.
204     * @return the password change page or null if not found.
205     */
206    public Page getPwdChangePage(String siteName, String language)
207    {
208        Page page = null;
209
210        try (AmetysObjectIterable<Page> pages = getPwdChangePages(siteName, language);)
211        {
212            Iterator<Page> it = pages.iterator();
213            if (it.hasNext())
214            {
215                page = it.next();
216            }
217        }
218
219        return page;
220    }
221
222    /**
223     * Get all the pages tagged "password change".
224     * @param siteName the site name.
225     * @param language the sitemap name.
226     * @return an iterable on all the pages tagged "password change".
227     */
228    public AmetysObjectIterable<Page> getPwdChangePages(String siteName, String language)
229    {
230        Expression expression = new TagExpression(Operator.EQ, "USER_PASSWORD_CHANGE");
231        String query = PageQueryHelper.getPageXPathQuery(siteName, language, null, expression, null);
232
233        return _resolver.query(query);
234    }
235
236    /**
237     * Get the user main preferences page in a given site and sitemap.
238     * If more than one page are tagged "user main preferences", return the first.
239     * @param siteName the site name.
240     * @param language the sitemap name.
241     * @return the user main preferences page or null if not found.
242     */
243    public Page getUserMainPrefsPage(String siteName, String language)
244    {
245        Page page = null;
246
247        try (AmetysObjectIterable<Page> pages = getUserMainPrefsPages(siteName, language);)
248        { 
249            Iterator<Page> it = pages.iterator();
250            if (it.hasNext())
251            {
252                page = it.next();
253            }
254        }
255
256        return page;
257    }
258
259    /**
260     * Get all the pages tagged "user main preferences".
261     * @param siteName the site name.
262     * @param language the sitemap name.
263     * @return an iterable on all the pages tagged "user main preferences".
264     */
265    public AmetysObjectIterable<Page> getUserMainPrefsPages(String siteName, String language)
266    {
267        Expression expression = new TagExpression(Operator.EQ, "USER_PREFS_MAIN");
268        String query = PageQueryHelper.getPageXPathQuery(siteName, language, null, expression, null);
269
270        return _resolver.query(query);
271    }
272
273    /**
274     * Tests if the FO users manager knows of the user with the given e-mail as the login.
275     * @param email the e-mail to test.
276     * @param population The id of the population
277     * @return true if the user exists, false otherwise.
278     * @throws UserManagementException if an error occurs.
279     */
280    public boolean userExists(String email, String population) throws UserManagementException
281    {
282        return _userManager.getUser(population, email) != null;
283    }
284
285    /**
286     * Validate the user subscription data.
287     * @param siteName the site name.
288     * @param email the user e-mail.
289     * @param firstName the user first name.
290     * @param lastName the user last name.
291     * @param additionalValues the additional user values.
292     * @return a Map of the Errors by field.
293     * @throws UserManagementException if an error occurs.
294     */
295    public Map<String, Errors> validate(String siteName, String email, String firstName, String lastName, Map<String, String> additionalValues) throws UserManagementException
296    {
297        Map<String, String> userInfos = new HashMap<>();
298        
299        userInfos.putAll(additionalValues);
300        
301        // Standard info for user (provide a dummy password, as we do not know it yet).
302        userInfos.put("login", email);
303        userInfos.put("email", email);
304        userInfos.put("firstname", firstName);
305        userInfos.put("lastname", lastName);
306        userInfos.put("password", "password");
307
308        Map<String, Errors> usersManagerErrors = new HashMap<>(); //foUsersManager.validate(userInfos);
309        Map<String, Errors> errors = new HashMap<>(usersManagerErrors);
310
311        // If there are errors on the login, do not return it except if
312        if (errors.containsKey("login"))
313        {
314            if (!errors.containsKey("email"))
315            {
316                errors.put("email", errors.get("login"));
317            }
318            errors.remove("login");
319        }
320
321        return errors;
322    }
323
324    /**
325     * Validate the user password.
326     * @param siteName the site name.
327     * @param password the password to validate.
328     * @param login the login of the user
329     * @param population The id of the population
330     * @return a Map of the Errors by field.
331     * @throws UserManagementException if an error occurs.
332     */
333    public Map<String, Errors> validatePassword(String siteName, String password, String login, String population) throws UserManagementException
334    {
335        Map<String, String> userInfos = new HashMap<>();
336
337        userInfos.put("password", password);
338
339        UserDirectory userDirectory = _userManager.getUserDirectory(population, login);
340        if (!(userDirectory instanceof ModifiableUserDirectory))
341        {
342            throw new UserManagementException("The user subscription feature can't be used, as the UserDirectory is not modifiable.");
343        }
344        Map<String, Errors> usersManagerErrors = ((ModifiableUserDirectory) userDirectory).validate(userInfos);
345        Map<String, Errors> errors = new HashMap<>();
346
347        // Keep only errors related to the password field.
348        if (usersManagerErrors.containsKey("password"))
349        {
350            errors.put("password", usersManagerErrors.get("password"));
351        }
352
353        return errors;
354    }
355
356    /**
357     * Validate and store a sign-up request and send a confirmation e-mail.
358     * @param siteName the site name.
359     * @param language the sitemap name.
360     * @param email the user e-mail address.
361     * @param firstName the user first name.
362     * @param lastName the user last name.
363     * @param population the population
364     * @param userDirectoryId the id of the user directory of the population
365     * @return a status code indicating success or error.
366     * @throws UserManagementException if an error occurs.
367     */
368    public int temporarySignup(String siteName, String language, String email, String firstName, String lastName, String population, String userDirectoryId) throws UserManagementException
369    {
370        // Verify that public sign-up is allowed and throw an exception otherwise.
371        checkPublicSignup(siteName);
372
373        if (getLogger().isDebugEnabled())
374        {
375            getLogger().debug("A user is requesting a sign-up: " + firstName + " " + lastName + " (" + email + ").");
376        }
377
378        // Generate unique token.
379        String token = UUID.randomUUID().toString().replace("-", "");
380
381        int status = addTemporaryUser(siteName, email, firstName, lastName, token, population, userDirectoryId);
382
383        // Send validation e-mail with token.
384        if (status == SIGNUP_NO_ERROR)
385        {
386            sendSignupConfirmMail(siteName, language, email, firstName, lastName, token);
387        }
388
389        return status;
390    }
391
392    /**
393     * Reset a sign-up request: generate a new token and re-send the confirmation e-mail.
394     * @param siteName the site name.
395     * @param language the sitemap name.
396     * @param email the user e-mail address.
397     * @param population The id of the population
398     * @param userDirectoryId The id of the user directory of the population
399     * @return a status code indicating success or error.
400     * @throws UserManagementException if an error occurs.
401     */
402    public int resetTempSignup(String siteName, String language, String email, String population, String userDirectoryId) throws UserManagementException
403    {
404        // Verify that public sign-up is allowed and throw an exception otherwise.
405        checkPublicSignup(siteName);
406
407        if (getLogger().isDebugEnabled())
408        {
409            getLogger().debug("Resetting temporary signup for email: " + email + " in site " + siteName);
410        }
411
412        // Generate a new token.
413        String newToken = UUID.randomUUID().toString().replace("-", "");
414
415        // Test if the subscription request really exists.
416        TempUser tempUser = getTempUser(siteName, email, population, userDirectoryId);
417
418        if (tempUser == null)
419        {
420            // No subscription request with this e-mail.
421            return SIGNUP_RESET_ERROR_EMAIL_UNKNOWN;
422        }
423
424        updateTempToken(siteName, email, newToken, population, userDirectoryId);
425
426        sendSignupConfirmMail(siteName, language, email, tempUser.getFirstname(), tempUser.getLastname(), newToken);
427
428        return SIGNUP_NO_ERROR;
429    }
430
431
432    /**
433     * Create the user in the FO UsersManager from his temporary request.
434     * @param siteName the site name.
435     * @param email the user e-mail address.
436     * @param token the request token.
437     * @param password the user password.
438     * @param population The id of the population
439     * @param userDirectoryId The id of the user directory of the population
440     * @return a status code indicating success or error.
441     * @throws UserManagementException if an error occurs.
442     */
443    public int signup(String siteName, String email, String token, String password, String population, String userDirectoryId) throws UserManagementException
444    {
445        // Verify that public sign-up is allowed and throw an exception otherwise.
446        checkPublicSignup(siteName);
447
448        Map<String, String> userInfos = new HashMap<>();
449
450        TempUser tempUser = getTempUser(siteName, email, token, population, userDirectoryId);
451
452        if (tempUser == null)
453        {
454            return SIGNUP_TOKEN_UNKNOWN;
455        }
456
457        // Standard info for user.
458        userInfos.put("login", email);
459        userInfos.put("email", email);
460        userInfos.put("firstname", tempUser.getFirstname());
461        userInfos.put("lastname", tempUser.getLastname());
462        userInfos.put("password", password);
463
464        try
465        {
466            // Add the user.
467            ModifiableUserDirectory userDirectory = (ModifiableUserDirectory) _userPopulationDAO.getUserPopulation(population).getUserDirectory(userDirectoryId);
468            userDirectory.add(userInfos);
469
470            // Remove the temporary user.
471            removeTempUser(siteName, email, token, population, userDirectoryId);
472
473            return SIGNUP_NO_ERROR;
474        }
475        catch (InvalidModificationException e)
476        {
477            throw new UserManagementException("An error occurred signing up the user.", e);
478        }
479    }
480
481    /**
482     * Create a reset password request and send a confirmation e-mail.
483     * @param siteName the site name.
484     * @param language the sitemap name.
485     * @param login the user login.
486     * @param population the population
487     * @return a status code indicating success or error.
488     * @throws UserManagementException if an error occurs.
489     */
490    public int resetPassword(String siteName, String language, String login, String population) throws UserManagementException
491    {
492        // Get the corresponding user.
493        User user = _userManager.getUser(population, login);
494
495        if (user == null)
496        {
497            // No user with this e-mail as login.
498            return LOST_PASSWORD_USER_UNKNOWN;
499        }
500
501        // Generate a new token.
502        String token = UUID.randomUUID().toString().replace("-", "");
503
504        // Insert the token in the database.
505        addPasswordToken(siteName, login, token, population);
506
507        // Send the e-mail.
508        sendResetPasswordMail(siteName, language, user, token);
509
510        return SIGNUP_NO_ERROR;
511    }
512
513    /**
514     * Change the user password.
515     * @param siteName the site name.
516     * @param login the user login.
517     * @param token the password change request token.
518     * @param newPassword the new password.
519     * @param population the population
520     * @return a status code indicating success or error.
521     * @throws UserManagementException if an error occurs.
522     */
523    public int changeUserPassword(String siteName, String login, String token, String newPassword, String population) throws UserManagementException
524    {
525        int tokenStatus = checkPasswordToken(siteName, login, token, population);
526
527        if (tokenStatus != SIGNUP_NO_ERROR)
528        {
529            return tokenStatus;
530        }
531
532        Map<String, String> userInfos = new HashMap<>();
533
534        userInfos.put("login", login);
535        userInfos.put("password", newPassword);
536
537        try
538        {
539            UserDirectory userDirectory = _userManager.getUserDirectory(population, login);
540            if (!(userDirectory instanceof ModifiableUserDirectory))
541            {
542                throw new UserManagementException("The user's password can't be changed, as the UserDirectory is not modifiable.");
543            }
544            ((ModifiableUserDirectory) userDirectory).update(userInfos);
545
546            removePasswordToken(siteName, login, token, population);
547
548            return SIGNUP_NO_ERROR;
549        }
550        catch (InvalidModificationException e)
551        {
552            throw new UserManagementException("An error occurred signing up the user.", e);
553        }
554    }
555
556    /**
557     * Check the sign-up request token.
558     * @param siteName the site name.
559     * @param email the user e-mail.
560     * @param token the sign-up request token.
561     * @param population The id of the population
562     * @param userDirectoryId The id of the user directory of the population
563     * @return a status code indicating success or error.
564     * @throws UserManagementException if an error occurs.
565     */
566    public int checkToken(String siteName, String email, String token, String population, String userDirectoryId) throws UserManagementException
567    {
568        removeExpiredTokens();
569
570        try (SqlSession sqlSession = getSession())
571        {
572            String stmtId = "UserSignupManager.getSubscriptionDate";
573            
574            Map<String, Object> params = new HashMap<>();
575            params.put("tempUsersTable", _tempUsersTable);
576            
577            params.put("site", siteName);
578            params.put("email", email);
579            params.put("token", token);
580            params.put("population", population);
581            params.put("userDirectory", userDirectoryId);
582            
583            Date rawSubscriptionDate = sqlSession.selectOne(stmtId, params);
584
585            // Date verification.
586            if (rawSubscriptionDate == null)
587            {
588                return SIGNUP_TOKEN_UNKNOWN;
589            }
590            
591            // The validity limit
592            DateTime limit = new DateTime().withMillisOfDay(0).minusDays(_userSignUpConfiguration.getTokenValidity());
593            DateTime subscriptionDate = new DateTime(rawSubscriptionDate);
594
595            if (subscriptionDate.isBefore(limit))
596            {
597                return SIGNUP_TOKEN_EXPIRED;
598            }
599
600            return SIGNUP_NO_ERROR;
601        }
602        catch (Exception e)
603        {
604            throw new UserManagementException("Database error while testing the token for user [" + email + "]", e);
605        }
606    }
607
608    /**
609     * Check the password change request token.
610     * @param siteName the site name.
611     * @param login the user login.
612     * @param token the password change request token.
613     * @param population the population
614     * @return a status code indicating success or error.
615     * @throws UserManagementException if an error occurs.
616     */
617    public int checkPasswordToken(String siteName, String login, String token, String population) throws UserManagementException
618    {
619        removeExpiredPasswordTokens();
620
621        try (SqlSession sqlSession = getSession())
622        {
623            String stmtId = "UserSignupManager.getRequestDate";
624            
625            Map<String, Object> params = new HashMap<>();
626            params.put("pwdChangeTable", _pwdChangeTable);
627            
628            params.put("site", siteName);
629            params.put("login", login);
630            params.put("token", token);
631            params.put("population", population);
632            
633            Date rawRequestDate = sqlSession.selectOne(stmtId, params);
634
635            // Date verification.
636            if (rawRequestDate == null)
637            {
638                return SIGNUP_TOKEN_UNKNOWN;
639            }
640
641            // Check the validity.
642            DateTime limit = new DateTime().withMillisOfDay(0).minusDays(_userSignUpConfiguration.getTokenValidity());
643            DateTime requestDate = new DateTime(rawRequestDate);
644
645            if (requestDate.isBefore(limit))
646            {
647                return SIGNUP_TOKEN_EXPIRED;
648            }
649
650            return SIGNUP_NO_ERROR;
651        }
652        catch (Exception e)
653        {
654            throw new UserManagementException("Database error while testing the password token for user " + login, e);
655        }
656    }
657
658    /**
659     * Remove the expired sign-up request tokens.
660     * @throws UserManagementException if an error occurs.
661     */
662    public void removeExpiredTokens() throws UserManagementException
663    {
664        try (SqlSession sqlSession = getSession())
665        {
666            String stmtId = "UserSignupManager.deleteExpiredTokens";
667            
668            Map<String, Object> params = new HashMap<>();
669            params.put("tempUsersTable", _tempUsersTable);
670            
671            DateTime limit = new DateTime().withMillisOfDay(0).minusDays(_userSignUpConfiguration.getTokenValidity());
672            Timestamp limitTimestamp = new Timestamp(limit.getMillis());
673            params.put("threshold", limitTimestamp);
674            
675            sqlSession.delete(stmtId, params);
676            sqlSession.commit();
677        }
678        catch (Exception e)
679        {
680            throw new UserManagementException("Database error while removing the expired tokens.", e);
681        }
682    }
683
684    /**
685     * Remove the expired change password request tokens.
686     * @throws UserManagementException if an error occurs.
687     */
688    public void removeExpiredPasswordTokens() throws UserManagementException
689    {
690        try (SqlSession sqlSession = getSession())
691        {
692            String stmtId = "UserSignupManager.deleteExpiredPasswordTokens";
693            
694            Map<String, Object> params = new HashMap<>();
695            params.put("pwdChangeTable", _pwdChangeTable);
696            
697            DateTime limit = new DateTime().withMillisOfDay(0).minusDays(_userSignUpConfiguration.getTokenValidity());
698            Timestamp limitTimestamp = new Timestamp(limit.getMillis());
699            params.put("threshold", limitTimestamp);
700            
701            sqlSession.delete(stmtId, params);
702            sqlSession.commit();
703        }
704        catch (Exception e)
705        {
706            throw new UserManagementException("Database error while removing the expired tokens.", e);
707        }
708    }
709
710    /**
711     * Verify that public sign-up is allowed. If not, throw an exception.
712     * @param siteName the site name.
713     * @throws UserManagementException if public sign-up is not enabled.
714     */
715    protected void checkPublicSignup(String siteName) throws UserManagementException
716    {
717        if (!isPublicSignupAllowed(siteName))
718        {
719            throw new UserManagementException("Public signup is disabled for this site.");
720        }
721    }
722
723    /**
724     * Create a user sign-up request ("temporary" user) in the database.
725     * @param siteName the site name.
726     * @param email the user e-mail.
727     * @param firstName the user first name.
728     * @param lastName the user last name.
729     * @param token the generated token.
730     * @param population the population
731     * @param userDirectoryId the id of the user directory of the population
732     * @return a status code indicating success or error.
733     * @throws UserManagementException if an error occurs.
734     */
735    protected int addTemporaryUser(String siteName, String email, String firstName, String lastName, String token, String population, String userDirectoryId) throws UserManagementException
736    {
737        try (SqlSession sqlSession = getSession())
738        {
739            // Does this email already exists
740            String stmtId = "UserSignupManager.tempEmailExists";
741            
742            Map<String, Object> params = new HashMap<>();
743            params.put("tempUsersTable", _tempUsersTable);
744            
745            params.put("site", siteName);
746            params.put("email", email);
747            params.put("population", population);
748            params.put("userDirectory", userDirectoryId);
749            
750            List<String> emails = sqlSession.selectList(stmtId, params);
751            
752            if (!emails.isEmpty())
753            {
754                return SIGNUP_ERROR_TEMP_EMAIL_ALREADY_EXISTS;
755            }
756            
757            // Add temp user
758            stmtId = "UserSignupManager.addTempUser";
759            params = new HashMap<>();
760            params.put("tempUsersTable", _tempUsersTable);
761            
762            Timestamp now = new Timestamp(System.currentTimeMillis());
763            
764            params.put("site", siteName);
765            params.put("email", email);
766            params.put("population", population);
767            params.put("userDirectory", userDirectoryId);
768            params.put("firstname", firstName);
769            params.put("lastname", lastName);
770            params.put("subscription_date", now);
771            params.put("token", token);
772            
773            sqlSession.insert(stmtId, params);
774            sqlSession.commit();
775            
776            return SIGNUP_NO_ERROR;
777        }
778        catch (Exception e)
779        {
780            throw new UserManagementException("Database error while signing up a new user [" + email + "]", e);
781        }
782    }
783
784    /**
785     * Send a sign-up confirmation link by e-mail.
786     * @param siteName the site name.
787     * @param language the e-mail language.
788     * @param email the user e-mail.
789     * @param firstName the user first name.
790     * @param lastName the user last name.
791     * @param token the generated token.
792     * @throws UserManagementException if an error occurs.
793     */
794    protected void sendSignupConfirmMail(String siteName, String language, String email, String firstName, String lastName, String token) throws UserManagementException
795    {
796        if (getLogger().isDebugEnabled())
797        {
798            getLogger().debug("Sending signup confirmation e-mail to " + email);
799        }
800
801        String from = _siteConf.getValueAsString(siteName, "site-mail-from");
802
803        // Prepare mail.
804        Map<String, I18nizableText> i18nParams = new HashMap<>();
805        i18nParams.put("siteName", new I18nizableText(siteName));
806        i18nParams.put("email", new I18nizableText(email));
807        i18nParams.put("firstName", new I18nizableText(firstName));
808        i18nParams.put("lastName", new I18nizableText(lastName));
809        i18nParams.put("token", new I18nizableText(token));
810
811        Page signupPage = getSignupPage(siteName, language);
812        if (signupPage != null)
813        {
814            if (_pageUriResolver == null)
815            {
816                _pageUriResolver = _uriResolverEP.getResolverForType("page");
817            }
818
819            String encodedEmail;
820            try
821            {
822                encodedEmail = URLEncoder.encode(email, "UTF-8");
823            }
824            catch (UnsupportedEncodingException e)
825            {
826                // Should never happen.
827                throw new UserManagementException("Encoding error while sending a sign-up confirmation e-mail.", e);
828            }
829
830            // Compute the confirmation URI and add it to the parameters.
831            String confirmUri = _pageUriResolver.resolve(signupPage.getId(), false, true, false);
832            confirmUri = confirmUri + "?email=" + encodedEmail + "&token=" + token;
833
834            i18nParams.put("confirmUri", new I18nizableText(confirmUri));
835        }
836
837        // Add site information in the parameters.
838        Site site = _siteManager.getSite(siteName);
839        if (site != null)
840        {
841            i18nParams.put("siteTitle", new I18nizableText(site.getTitle()));
842            i18nParams.put("siteUrl", new I18nizableText(site.getUrl()));
843        }
844
845        String subject = _userSignUpConfiguration.getSubjectForSignUpEmail(i18nParams, language);
846        String textBody = _userSignUpConfiguration.getTextBodyForSignUpEmail(i18nParams, language);
847        String htmlBody = _userSignUpConfiguration.getHtmlBodyForSignUpEmail(i18nParams, language);
848
849        try
850        {
851            // Send the e-mail.
852            SendMailHelper.sendMail(subject, htmlBody, textBody, email, from);
853        }
854        catch (MessagingException e)
855        {
856            throw new UserManagementException("Error sending the sign-up confirmation mail.", e);
857        }
858    }
859
860    /**
861     * Update the sign-up request token: reset the date and set a new token.
862     * @param siteName the site name.
863     * @param email the user e-mail.
864     * @param newToken the new token.
865     * @param population The id of the population
866     * @param userDirectoryId The id of the user directory of the population
867     * @throws UserManagementException if an error occurs.
868     */
869    protected void updateTempToken(String siteName, String email, String newToken, String population, String userDirectoryId) throws UserManagementException
870    {
871        try (SqlSession sqlSession = getSession())
872        {
873            String stmtId = "UserSignupManager.updateTempToken";
874            
875            Map<String, Object> params = new HashMap<>();
876            params.put("tempUsersTable", _tempUsersTable);
877            
878            Timestamp now = new Timestamp(System.currentTimeMillis());
879            params.put("subscription_date", now);
880            params.put("token", newToken);
881            params.put("site", siteName);
882            params.put("email", email);
883            params.put("population", population);
884            params.put("userDirectory", userDirectoryId);
885            
886            sqlSession.update(stmtId, params);
887            sqlSession.commit();
888        }
889        catch (Exception e)
890        {
891            throw new UserManagementException("Database error while resetting the subscription for user [" + email + "]", e);
892        }
893    }
894
895    /**
896     * Create a user password change request in the database.
897     * @param siteName the site name.
898     * @param login the user login.
899     * @param token the generated token.
900     * @param population the population
901     * @throws UserManagementException if an error occurs.
902     */
903    protected void addPasswordToken(String siteName, String login, String token, String population) throws UserManagementException
904    {
905        try (SqlSession sqlSession = getSession())
906        {
907            String stmtId = "UserSignupManager.hasToken";
908            
909            Map<String, Object> params = new HashMap<>();
910            params.put("pwdChangeTable", _pwdChangeTable);
911            
912            params.put("site", siteName);
913            params.put("login", login);
914            params.put("population", population);
915            
916            List<Object> pwdEntries = sqlSession.selectList(stmtId, params);
917            
918            if (pwdEntries.isEmpty())
919            {
920                // Insert a new token.
921                stmtId = "UserSignupManager.addPasswordToken";
922                params = new HashMap<>();
923                params.put("pwdChangeTable", _pwdChangeTable);
924                
925                Timestamp now = new Timestamp(System.currentTimeMillis());
926                params.put("site", siteName);
927                params.put("login", login);
928                params.put("request_date", now);
929                params.put("token", token);
930                params.put("population", population);
931                
932                sqlSession.insert(stmtId, params);
933            }
934            else
935            {
936                // Update the existing token.
937                stmtId = "UserSignupManager.updatePasswordToken";
938                params = new HashMap<>();
939                params.put("pwdChangeTable", _pwdChangeTable);
940                
941                Timestamp now = new Timestamp(System.currentTimeMillis());
942                params.put("request_date", now);
943                params.put("token", token);
944                params.put("site", siteName);
945                params.put("login", login);
946                params.put("population", population);
947                
948                sqlSession.update(stmtId, params);
949            }
950            
951            // commit insert or update
952            sqlSession.commit();
953        }
954        catch (Exception e)
955        {
956            throw new UserManagementException("Database error while inserting a password change token for " + login, e);
957        }
958    }
959
960    /**
961     * Get a temporary user from his site name and e-mail.
962     * @param siteName the site name.
963     * @param email The temporary user e-mail. Cannot be null.
964     * @param population the population
965     * @param userDirectoryId the id of the user directory of the population
966     * @return the temporary user or null if not found.
967     * @throws UserManagementException if an error occurs.
968     */
969    protected TempUser getTempUser(String siteName, String email, String population, String userDirectoryId) throws UserManagementException
970    {
971        return getTempUser(siteName, email, null, population, userDirectoryId);
972    }
973
974    /**
975     * Get a temporary user from his site name, e-mail and/or token.
976     * At least one of e-mail and token must be provided.
977     * @param siteName the site name.
978     * @param email The temporary user e-mail. Can be null.
979     * @param token The temporary user token. Can be null.
980     * @param population the population. Must be not null if email is not null
981     * @param userDirectoryId the id of the user directory of the population. Must be not null if email is not null
982     * @return the temporary user or null if not found.
983     * @throws UserManagementException if an error occurs.
984     */
985    protected TempUser getTempUser(String siteName, String email, String token, String population, String userDirectoryId) throws UserManagementException
986    {
987        if (StringUtils.isEmpty(email) && StringUtils.isEmpty(token))
988        {
989            throw new UserManagementException("Either e-mail or token must be provided.");
990        }
991        
992        try (SqlSession sqlSession = getSession())
993        {
994            // Does this email already exists
995            String stmtId = "UserSignupManager.getTempUser";
996            
997            Map<String, Object> params = new HashMap<>();
998            params.put("tempUsersTable", _tempUsersTable);
999            
1000            params.put("site", siteName);
1001            if (StringUtils.isNotEmpty(email))
1002            {
1003                params.put("email", email);
1004                params.put("population", population);
1005                params.put("userDirectory", userDirectoryId);
1006            }
1007            if (StringUtils.isNotEmpty(token))
1008            {
1009                params.put("token", token);
1010            }
1011            
1012            return sqlSession.selectOne(stmtId, params);
1013        }
1014        catch (Exception e)
1015        {
1016            throw new UserManagementException("Database error while getting the request for user [" + email + "] and token [" + token + "]", e);
1017        }
1018    }
1019
1020    /**
1021     * Remove the temporary .
1022     * @param siteName the site name.
1023     * @param email the user e-mail address.
1024     * @param token the request token.
1025     * @param population the population
1026     * @param userDirectoryId the id of the user directory of the population
1027     * @throws UserManagementException if an error occurs.
1028     */
1029    protected void removeTempUser(String siteName, String email, String token, String population, String userDirectoryId) throws UserManagementException
1030    {
1031        try (SqlSession sqlSession = getSession())
1032        {
1033            String stmtId = "UserSignupManager.removeTempUser";
1034            
1035            Map<String, Object> params = new HashMap<>();
1036            params.put("tempUsersTable", _tempUsersTable);
1037            
1038            params.put("site", siteName);
1039            params.put("email", email);
1040            params.put("token", token);
1041            params.put("population", population);
1042            params.put("userDirectory", userDirectoryId);
1043            
1044            sqlSession.delete(stmtId, params);
1045            sqlSession.commit();
1046        }
1047        catch (Exception e)
1048        {
1049            throw new UserManagementException("Database error while removing the token of user " + email, e);
1050        }
1051    }
1052
1053    /**
1054     * Remove the password change request.
1055     * @param siteName the site name.
1056     * @param login the user login.
1057     * @param token the request token.
1058     * @param population the population
1059     * @throws UserManagementException if an error occurs.
1060     */
1061    protected void removePasswordToken(String siteName, String login, String token, String population) throws UserManagementException
1062    {
1063        try (SqlSession sqlSession = getSession())
1064        {
1065            String stmtId = "UserSignupManager.removePasswordToken";
1066            
1067            Map<String, Object> params = new HashMap<>();
1068            params.put("pwdChangeTable", _pwdChangeTable);
1069            
1070            params.put("site", siteName);
1071            params.put("login", login);
1072            params.put("token", token);
1073            params.put("population", population);
1074            
1075            sqlSession.delete(stmtId, params);
1076            sqlSession.commit();
1077        }
1078        catch (Exception e)
1079        {
1080            throw new UserManagementException("Database error while removing the token of user " + login, e);
1081        }
1082    }
1083
1084    /**
1085     * Send a sign-up confirmation link by e-mail.
1086     * @param siteName the site name.
1087     * @param language the e-mail language.
1088     * @param user the user object.
1089     * @param token the generated token.
1090     * @throws UserManagementException if an error occurs.
1091     */
1092    protected void sendResetPasswordMail(String siteName, String language, User user, String token) throws UserManagementException
1093    {
1094        String login = user.getIdentity().getLogin();
1095        String population = user.getIdentity().getPopulationId();
1096
1097        if (getLogger().isDebugEnabled())
1098        {
1099            getLogger().debug("Sending reset password e-mail to " + login);
1100        }
1101
1102        String from = _siteConf.getValueAsString(siteName, "site-mail-from");
1103
1104        // Prepare mail
1105        Map<String, I18nizableText> i18nParams = new HashMap<>();
1106        i18nParams.put("siteName", new I18nizableText(siteName));
1107        i18nParams.put("login", new I18nizableText(login));
1108        i18nParams.put("email", new I18nizableText(user.getEmail()));
1109        i18nParams.put("fullName", new I18nizableText(user.getFullName()));
1110        i18nParams.put("token", new I18nizableText(token));
1111
1112        Page passwordPage = getPwdChangePage(siteName, language);
1113        if (passwordPage != null)
1114        {
1115            if (_pageUriResolver == null)
1116            {
1117                _pageUriResolver = _uriResolverEP.getResolverForType("page");
1118            }
1119
1120            String encodedLogin;
1121            String encodedPopulation;
1122            try
1123            {
1124                encodedLogin = URLEncoder.encode(login, "UTF-8");
1125                encodedPopulation = URLEncoder.encode(population, "UTF-8");
1126            }
1127            catch (UnsupportedEncodingException e)
1128            {
1129                // Should never happen.
1130                throw new UserManagementException("Encoding error while sending a password reset confirmation e-mail.", e);
1131            }
1132
1133            // Compute the confirmation URI and add it to the parameters.
1134            String confirmUri = _pageUriResolver.resolve(passwordPage.getId(), false, true, false);
1135            confirmUri = confirmUri + "?login=" + encodedLogin + "&population=" + encodedPopulation + "&token=" + token;
1136
1137            i18nParams.put("confirmUri", new I18nizableText(confirmUri));
1138        }
1139
1140        // Add site information in the parameters.
1141        Site site = _siteManager.getSite(siteName);
1142        if (site != null)
1143        {
1144            i18nParams.put("siteTitle", new I18nizableText(site.getTitle()));
1145            i18nParams.put("siteUrl", new I18nizableText(site.getUrl()));
1146        }
1147
1148        String subject = _userSignUpConfiguration.getSubjectForResetPwdEmail(i18nParams, language);
1149        String textBody = _userSignUpConfiguration.getTextBodyForResetPwdEmail(i18nParams, language);
1150        String htmlBody = _userSignUpConfiguration.getHtmlBodyForResetPwdEmail(i18nParams, language);
1151
1152        try
1153        {
1154            // Send the e-mail.
1155            SendMailHelper.sendMail(subject, htmlBody, textBody, user.getEmail(), from);
1156        }
1157        catch (MessagingException e)
1158        {
1159            throw new UserManagementException("Error sending a password reset e-mail.", e);
1160        }
1161    }
1162
1163    /**
1164     * Bean representing a user sign-up request.
1165     */
1166    public static class TempUser
1167    {
1168        /** The site name. */
1169        protected String _site;
1170        
1171        /** The user e-mail. */
1172        protected String _email;
1173
1174        /** The user first name. */
1175        protected String _firstname;
1176
1177        /** The user last name. */
1178        protected String _lastname;
1179
1180        /** The user subscription date. */
1181        protected Date _subscriptionDate;
1182
1183        /** The request token. */
1184        protected String _token;
1185
1186        /** The id of the population */
1187        protected String _population;
1188
1189        /** The id of the user directory of the population */
1190        protected String _userDirectoryId;
1191
1192        /**
1193         * Constructor.
1194         * @param site the site
1195         * @param email the user's email
1196         * @param firstname the user's firstname
1197         * @param lastname the user's lastname
1198         * @param subscriptionDate the date of subscription
1199         * @param token the user's token
1200         * @param population The id of the population
1201         * @param userDirectoryId The id of the user directory of the population
1202         */
1203        public TempUser(String site, String email, String firstname, String lastname, Date subscriptionDate, String token, String population, String userDirectoryId)
1204        {
1205            this._site = site;
1206            this._email = email;
1207            this._firstname = firstname;
1208            this._lastname = lastname;
1209            this._subscriptionDate = subscriptionDate;
1210            this._token = token;
1211            this._population = population;
1212            this._userDirectoryId = userDirectoryId;
1213        }
1214        /**
1215         * Get the site.
1216         * @return the site
1217         */
1218        public String getSite()
1219        {
1220            return _site;
1221        }
1222        /**
1223         * Set the site.
1224         * @param site the site to set
1225         */
1226        public void setSite(String site)
1227        {
1228            this._site = site;
1229        }
1230        /**
1231         * Get the email.
1232         * @return _the email
1233         */
1234        public String getEmail()
1235        {
1236            return _email;
1237        }
1238        /**
1239         * Set the email.
1240         * @param email the email to set
1241         */
1242        public void setEmail(String email)
1243        {
1244            this._email = email;
1245        }
1246        /**
1247         * Get the firstname.
1248         * @return _the firstname
1249         */
1250        public String getFirstname()
1251        {
1252            return _firstname;
1253        }
1254        /**
1255         * Set the firstname.
1256         * @param firstname the firstname to set
1257         */
1258        public void setFirstname(String firstname)
1259        {
1260            this._firstname = firstname;
1261        }
1262        /**
1263         * Get the lastname.
1264         * @return _the lastname
1265         */
1266        public String getLastname()
1267        {
1268            return _lastname;
1269        }
1270        /**
1271         * Set the lastname.
1272         * @param lastname the lastname to set
1273         */
1274        public void setLastname(String lastname)
1275        {
1276            this._lastname = lastname;
1277        }
1278        /**
1279         * Get the subscriptionDate.
1280         * @return _the subscriptionDate
1281         */
1282        public Date getSubscriptionDate()
1283        {
1284            return _subscriptionDate;
1285        }
1286        /**
1287         * Set the subscriptionDate.
1288         * @param subscriptionDate the subscriptionDate to set
1289         */
1290        public void setSubscriptionDate(Date subscriptionDate)
1291        {
1292            this._subscriptionDate = subscriptionDate;
1293        }
1294        /**
1295         * Get the token.
1296         * @return _the token
1297         */
1298        public String getToken()
1299        {
1300            return _token;
1301        }
1302        /**
1303         * Set the token.
1304         * @param token the token to set
1305         */
1306        public void setToken(String token)
1307        {
1308            this._token = token;
1309        }
1310        /**
1311         * Get the population.
1312         * @return the population
1313         */
1314        public String getPopulation()
1315        {
1316            return _population;
1317        }
1318        /**
1319         * Set the population.
1320         * @param population the population to set
1321         */
1322        public void setPopulation(String population)
1323        {
1324            this._population = population;
1325        }
1326        /**
1327         * Get the user directory id.
1328         * @return the user directory id
1329         */
1330        public String getUserDirectoryId()
1331        {
1332            return _userDirectoryId;
1333        }
1334        /**
1335         * Set the user directory index.
1336         * @param userDirectoryId the user directory id to set
1337         */
1338        public void setUserDirectoryIndex(String userDirectoryId)
1339        {
1340            this._userDirectoryId = userDirectoryId;
1341        }
1342
1343    }
1344
1345}