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