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.util.Arrays;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.Map;
023import java.util.Set;
024
025import org.apache.avalon.framework.parameters.Parameters;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.cocoon.acting.ServiceableAction;
029import org.apache.cocoon.environment.ObjectModelHelper;
030import org.apache.cocoon.environment.Redirector;
031import org.apache.cocoon.environment.Request;
032import org.apache.cocoon.environment.SourceResolver;
033import org.apache.commons.lang.StringUtils;
034
035import org.ametys.core.captcha.CaptchaHelper;
036import org.ametys.core.user.UserManager;
037import org.ametys.core.user.directory.ModifiableUserDirectory;
038import org.ametys.core.user.population.UserPopulationDAO;
039import org.ametys.plugins.repository.AmetysObjectResolver;
040import org.ametys.runtime.i18n.I18nizableText;
041import org.ametys.runtime.parameter.Errors;
042import org.ametys.runtime.parameter.Parameter;
043import org.ametys.runtime.parameter.ParameterHelper.ParameterType;
044import org.ametys.web.cache.PageHelper;
045import org.ametys.web.repository.page.Page;
046import org.ametys.web.repository.page.ZoneItem;
047import org.ametys.web.site.SiteConfigurationExtensionPoint;
048
049import com.google.common.collect.ArrayListMultimap;
050import com.google.common.collect.Multimap;
051
052/**
053 * Handle the user sign-up actions.
054 */
055public class UserSignupAction extends ServiceableAction
056{
057    /** The UsersManager standard fields. */
058    public static final Set<String> STANDARD_FIELDS = new HashSet<>(Arrays.asList("login", "firstname", "lastname", "email", "address", "password"));
059    
060    private static final String __SIGNUP_SERVICE_PARAMETER_USERDIRECTORY = "userdirectory";
061    
062    /** The user signup manager. */
063    protected UserSignupManager _userSignupManager;
064    
065    /** The user manager. */
066    protected UserManager _userManager;
067    
068    /** The DAO for user populations */
069    protected UserPopulationDAO _userPopulationDAO;
070    /** The site configuration EP. */
071    protected SiteConfigurationExtensionPoint _siteConfiguration;
072    
073    /** The Ametys Resolver */
074    protected AmetysObjectResolver _resolver;
075    
076    /** Page helper */
077    protected PageHelper _pageHelper;
078
079    @Override
080    public void service(ServiceManager serviceManager) throws ServiceException
081    {
082        super.service(serviceManager);
083        _userSignupManager = (UserSignupManager) serviceManager.lookup(UserSignupManager.ROLE);
084        _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE);
085        _userPopulationDAO = (UserPopulationDAO) serviceManager.lookup(UserPopulationDAO.ROLE);
086        _siteConfiguration = (SiteConfigurationExtensionPoint) serviceManager.lookup(SiteConfigurationExtensionPoint.ROLE);
087        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
088        _pageHelper = (PageHelper) serviceManager.lookup(PageHelper.ROLE);
089    }
090    
091    @Override
092    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
093    {
094        Request request = ObjectModelHelper.getRequest(objectModel);
095        String siteName = (String) request.getAttribute("site");
096        String language = (String) request.getAttribute("sitemapLanguage");
097        
098        Map<String, String> results = new HashMap<>();
099        
100        String signup = request.getParameter("signup");
101        String submitPassword = request.getParameter("pwd-submit");
102        String mode = request.getParameter("mode");
103        String email = request.getParameter("email");
104        String token = request.getParameter("token");
105        
106        Multimap<String, I18nizableText> errors = ArrayListMultimap.create();
107        
108        ZoneItem zoneItem = (ZoneItem) request.getAttribute(ZoneItem.class.getName());
109        String[] populationAndDirectory = zoneItem.getServiceParameters().getString(__SIGNUP_SERVICE_PARAMETER_USERDIRECTORY).split("#", 2);
110        String populationId = populationAndDirectory[0];
111        String userDirectoryId = populationAndDirectory[1];
112        
113        try
114        {
115            if ("new-token".equals(mode))
116            {
117                // Action which requests a new token.
118                results.put("step", "new-token");
119                resetTempSignup(request, siteName, language, populationId, userDirectoryId, errors);
120                if (errors.isEmpty())
121                {
122                    results.put("status", "success");
123                }
124            }
125            else if ("true".equals(submitPassword))
126            {
127                // If the password has been entered, signup the user.
128                results.put("step", "signup");
129                doSignup(request, siteName, email, token, populationId, userDirectoryId, errors);
130                if (errors.isEmpty())
131                {
132                    results.put("status", "success");
133                }
134                else 
135                {
136                    results.put("step", "password");
137                }
138            }
139            else if (StringUtils.isNotEmpty(token) && StringUtils.isNotEmpty(email))
140            {
141                // If a token is provided, check it.
142                results.put("step", "password");
143                checkToken(request, siteName, email, token, populationId, userDirectoryId, errors);
144                if (errors.isEmpty())
145                {
146                    results.put("status", "success");
147                }
148            }
149            else if ("true".equals(signup))
150            {
151                // A signup request has been made.
152                results.put("step", "temp-signup");
153                temporarySignup(request, parameters, siteName, language, populationId, userDirectoryId, errors);
154                if (errors.isEmpty())
155                {
156                    results.put("status", "success");
157                }
158            }
159        }
160        catch (Exception e)
161        {
162            errors.put("global", new I18nizableText("general-error"));
163            
164            getLogger().error("An error occurred signing a user up.", e);
165        }
166        
167        request.setAttribute("errors", errors);
168        
169        return results;
170    }
171
172    /**
173     * Store a user's sign-up request.
174     * @param request the user request.
175     * @param parameters the action parameters.
176     * @param siteName the site name.
177     * @param language the language.
178     * @param populationId The id of the population
179     * @param userDirectoryId The id of the user directory of the population
180     * @param errors the Map to fill with errors to display to the user.
181     * @throws UserManagementException if an error occurs.
182     */
183    protected void temporarySignup(Request request, Parameters parameters, String siteName, String language, String populationId, String userDirectoryId, Multimap<String, I18nizableText> errors) throws UserManagementException
184    {
185        String firstName = request.getParameter("firstname");
186        String lastName = request.getParameter("lastname");
187        String email = request.getParameter("email");
188        String tos = request.getParameter("tos");
189        String tosPageId = parameters.getParameter("tos-page-id", "");
190        Page page = _resolver.resolveById(request.getParameter("page-id"));
191        
192        ModifiableUserDirectory userDirectory = (ModifiableUserDirectory) _userPopulationDAO.getUserPopulation(populationId).getUserDirectory(userDirectoryId);
193        
194        Map<String, String> additionalFields = getAdditionalValues(request, userDirectory);
195        
196        if (_pageHelper.isCaptchaRequired(page))
197        {   
198            String captchaValue = request.getParameter("captcha");
199            String captchaKey = request.getParameter("captcha-key");
200
201            // Validate the input
202            if (!CaptchaHelper.checkAndInvalidate(captchaKey, captchaValue))
203            {
204                errors.put("captcha", new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_FORM_ERROR_INVALID_CAPTCHA"));
205            }
206        }
207        
208        if (StringUtils.isNotEmpty(tosPageId) && !"true".equals(tos))
209        {
210            errors.put("tos", new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_FORM_ERROR_TOS_NOT_CHECKED"));
211        }
212        if (_userSignupManager.userExists(email, populationId))
213        {
214            errors.put("global", new I18nizableText("user-email-already-exists"));
215        }
216        
217        Map<String, Errors> inputErrors = _userSignupManager.validate(siteName, email, firstName, lastName, additionalFields);
218        
219        for (String field : inputErrors.keySet())
220        {
221            errors.putAll(field, inputErrors.get(field).getErrors());
222        }
223        
224        if (errors.isEmpty())
225        {
226            int status = _userSignupManager.temporarySignup(siteName, language, email, firstName, lastName, populationId, userDirectoryId);
227            
228            if (status == UserSignupManager.SIGNUP_ERROR_TEMP_EMAIL_ALREADY_EXISTS)
229            {
230                errors.put("global", new I18nizableText("temp-email-already-exists"));
231            }
232        }
233    }
234    
235    /**
236     * Reset a user's sign-up request.
237     * @param request the user request.
238     * @param siteName the site name.
239     * @param language the language.
240     * @param populationId The id of the population
241     * @param userDirectoryId The id of the user directory of the population
242     * @param errors the Map to fill with errors to display to the user.
243     * @throws UserManagementException if an error occurs.
244     */
245    protected void resetTempSignup(Request request, String siteName, String language, String populationId, String userDirectoryId, Multimap<String, I18nizableText> errors) throws UserManagementException
246    {
247        String email = request.getParameter("email");
248        
249        int status = _userSignupManager.resetTempSignup(siteName, language, email, populationId, userDirectoryId);
250        
251        if (status == UserSignupManager.SIGNUP_RESET_ERROR_EMAIL_UNKNOWN)
252        {
253            errors.put("global", new I18nizableText("temp-email-unknown"));
254        }
255    }
256    
257    /**
258     * Check that a token is valid.
259     * @param request the user request.
260     * @param siteName the site name.
261     * @param email the user e-mail.
262     * @param token the sign-up token that was sent to the user. 
263     * @param populationId The id of the population
264     * @param userDirectoryId The id of the user directory of the population
265     * @param errors the Map to fill with errors to display to the user.
266     * @throws UserManagementException if an error occurs.
267     */
268    protected void checkToken(Request request, String siteName, String email, String token, String populationId, String userDirectoryId, Multimap<String, I18nizableText> errors) throws UserManagementException
269    {
270        int tokenStatus = _userSignupManager.checkToken(siteName, email, token, populationId, userDirectoryId);
271        
272        if (tokenStatus == UserSignupManager.SIGNUP_TOKEN_UNKNOWN)
273        {
274            errors.put("global", new I18nizableText("error-token-unknown"));
275        }
276        else if (tokenStatus == UserSignupManager.SIGNUP_TOKEN_EXPIRED)
277        {
278            errors.put("global", new I18nizableText("error-token-expired"));
279        }
280    }
281    
282    /**
283     * Sign-up the user: create a real user from his temporary information.
284     * @param request the user request.
285     * @param siteName the site name.
286     * @param email the user e-mail.
287     * @param token the sign-up token that was sent to the user. 
288     * @param populationId The id of the population
289     * @param userDirectoryId The id of the user directory of the population
290     * @param errors the Map to fill with errors to display to the user.
291     * @throws UserManagementException if an error occurs.
292     */
293    protected void doSignup(Request request, String siteName, String email, String token, String populationId, String userDirectoryId, Multimap<String, I18nizableText> errors) throws UserManagementException
294    {
295        String password = request.getParameter("password");
296        String passwordConfirmation = request.getParameter("password-confirmation");
297        
298        if (StringUtils.isBlank(password))
299        {
300            errors.put("password", new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_PASSWORD_EMPTY"));
301        }
302        else if (!password.equals(passwordConfirmation))
303        {
304            errors.put("password", new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_PASSWORD_CONFIRMATION_DOESNT_MATCH"));
305        }
306        
307        if (errors.isEmpty())
308        {
309            int status = _userSignupManager.signup(siteName, email, token, password, populationId, userDirectoryId);
310            
311            if (status == UserSignupManager.SIGNUP_TOKEN_UNKNOWN)
312            {
313                errors.put("global", new I18nizableText("error-token-unknown"));
314            }
315            else if (status == UserSignupManager.SIGNUP_TOKEN_EXPIRED)
316            {
317                errors.put("global", new I18nizableText("error-token-expired"));
318            }
319        }
320    }
321    
322    /**
323     * Get FO user manager's custom field values.
324     * @param request the request.
325     * @param userDirectory the user directory
326     * @return the custom field values.
327     * @throws UserManagementException if an error occurs.
328     */
329    protected Map<String, String> getAdditionalValues(Request request, ModifiableUserDirectory userDirectory) throws UserManagementException
330    {
331        Map<String, String> additionalFields = new HashMap<>();
332        
333        Collection<? extends Parameter<ParameterType>> fields = userDirectory.getModel();
334        
335        for (Parameter<ParameterType> field : fields)
336        {
337            String fieldId = field.getId();
338            if (!STANDARD_FIELDS.contains(fieldId))
339            {
340                String value = request.getParameter(fieldId);
341                
342                additionalFields.put(fieldId, value);
343            }
344        }
345        
346        return additionalFields;
347    }
348}