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.List; 023import java.util.Map; 024import java.util.Set; 025 026import org.apache.avalon.framework.parameters.Parameters; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.cocoon.acting.ServiceableAction; 030import org.apache.cocoon.environment.ObjectModelHelper; 031import org.apache.cocoon.environment.Redirector; 032import org.apache.cocoon.environment.Request; 033import org.apache.cocoon.environment.SourceResolver; 034import org.apache.commons.lang.StringUtils; 035 036import org.ametys.core.captcha.CaptchaHelper; 037import org.ametys.core.user.UserManager; 038import org.ametys.core.user.directory.ModifiableUserDirectory; 039import org.ametys.core.user.directory.UserDirectory; 040import org.ametys.core.user.population.UserPopulation; 041import org.ametys.core.user.population.UserPopulationDAO; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043import org.ametys.runtime.i18n.I18nizableText; 044import org.ametys.runtime.model.ModelItem; 045import org.ametys.web.URIPrefixHandler; 046import org.ametys.web.WebConstants; 047import org.ametys.web.cache.PageHelper; 048import org.ametys.web.renderingcontext.RenderingContextHandler; 049import org.ametys.web.repository.page.Page; 050import org.ametys.web.repository.page.ZoneItem; 051import org.ametys.web.site.SiteConfigurationExtensionPoint; 052import org.ametys.web.usermanagement.UserManagementException.StatusError; 053 054import com.google.common.collect.ArrayListMultimap; 055import com.google.common.collect.Multimap; 056 057/** 058 * Handle the user sign-up actions. 059 */ 060public class UserSignupAction extends ServiceableAction 061{ 062 /** The UsersManager standard fields. */ 063 public static final Set<String> STANDARD_FIELDS = new HashSet<>(Arrays.asList("login", "firstname", "lastname", "email", "address", "password")); 064 065 /** The user signup manager. */ 066 protected UserSignupManager _userSignupManager; 067 068 /** The user manager. */ 069 protected UserManager _userManager; 070 071 /** The DAO for user populations */ 072 protected UserPopulationDAO _userPopulationDAO; 073 /** The site configuration EP. */ 074 protected SiteConfigurationExtensionPoint _siteConfiguration; 075 076 /** The Ametys Resolver */ 077 protected AmetysObjectResolver _resolver; 078 079 /** Page helper */ 080 protected PageHelper _pageHelper; 081 /** The rendering context */ 082 protected RenderingContextHandler _renderingContextHandler; 083 /** The URI prefix handler */ 084 protected URIPrefixHandler _prefixHandler; 085 086 @Override 087 public void service(ServiceManager serviceManager) throws ServiceException 088 { 089 super.service(serviceManager); 090 _userSignupManager = (UserSignupManager) serviceManager.lookup(UserSignupManager.ROLE); 091 _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE); 092 _userPopulationDAO = (UserPopulationDAO) serviceManager.lookup(UserPopulationDAO.ROLE); 093 _siteConfiguration = (SiteConfigurationExtensionPoint) serviceManager.lookup(SiteConfigurationExtensionPoint.ROLE); 094 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 095 _pageHelper = (PageHelper) serviceManager.lookup(PageHelper.ROLE); 096 _renderingContextHandler = (RenderingContextHandler) serviceManager.lookup(RenderingContextHandler.ROLE); 097 _prefixHandler = (URIPrefixHandler) manager.lookup(URIPrefixHandler.ROLE); 098 } 099 100 @Override 101 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 102 { 103 Request request = ObjectModelHelper.getRequest(objectModel); 104 String siteName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME); 105 String language = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITEMAP_NAME); 106 107 Map<String, String> results = new HashMap<>(); 108 109 String signup = request.getParameter("signup"); 110 String submitPassword = request.getParameter("pwd-submit"); 111 String mode = request.getParameter("mode"); 112 String email = request.getParameter("email"); 113 String firstname = request.getParameter("firstname"); 114 String lastname = request.getParameter("lastname"); 115 String token = request.getParameter("token"); 116 117 Multimap<String, I18nizableText> errors = ArrayListMultimap.create(); 118 119 ZoneItem zoneItem = (ZoneItem) request.getAttribute(WebConstants.REQUEST_ATTR_ZONEITEM); 120 121 try 122 { 123 UserDirectory userDirectory = _userSignupManager.getUserDirectory(zoneItem); 124 String populationId = userDirectory.getPopulationId(); 125 String userDirectoryId = userDirectory.getId(); 126 127 if ("new-token".equals(mode)) 128 { 129 // Action which requests a new token. 130 results.put("step", "new-token"); 131 resetTempSignup(request, siteName, language, populationId, userDirectoryId, errors); 132 if (errors.isEmpty()) 133 { 134 results.put("status", "success"); 135 } 136 } 137 else if ("true".equals(submitPassword)) 138 { 139 // If the password has been entered, signup the user. 140 results.put("step", "signup"); 141 doSignup(request, parameters, siteName, language, firstname, lastname, email, token, populationId, userDirectoryId, errors); 142 if (errors.isEmpty()) 143 { 144 results.put("status", "success"); 145 } 146 else 147 { 148 results.put("step", "password"); 149 } 150 } 151 else if (StringUtils.isNotEmpty(token) && StringUtils.isNotEmpty(email)) 152 { 153 // If a token is provided, check it. 154 results.put("step", "password"); 155 checkToken(request, siteName, email, token, populationId, userDirectoryId, errors); 156 if (errors.isEmpty()) 157 { 158 results.put("status", "success"); 159 } 160 } 161 else if ("true".equals(signup)) 162 { 163 // A signup request has been made. 164 results.put("step", "temp-signup"); 165 temporarySignup(request, parameters, siteName, language, populationId, userDirectoryId, errors); 166 if (errors.isEmpty()) 167 { 168 results.put("status", "success"); 169 } 170 } 171 else if (!_userSignupManager.isPublicSignupAllowed(siteName)) 172 { 173 // Can not access to signup form (first step) if public signup is unauthorized 174 errors.put("global", new I18nizableText("error-unauthorized")); 175 } 176 } 177 catch (Exception e) 178 { 179 errors.put("global", new I18nizableText("general-error")); 180 181 getLogger().error("An error occurred signing a user up.", e); 182 } 183 184 request.setAttribute("errors", errors); 185 186 return results; 187 } 188 189 /** 190 * Store a user's sign-up request. 191 * @param request the user request. 192 * @param parameters the action parameters. 193 * @param siteName the site name. 194 * @param language the language. 195 * @param populationId The id of the population 196 * @param userDirectoryId The id of the user directory of the population 197 * @param errors the Map to fill with errors to display to the user. 198 * @throws UserManagementException if an error occurs. 199 */ 200 protected void temporarySignup(Request request, Parameters parameters, String siteName, String language, String populationId, String userDirectoryId, Multimap<String, I18nizableText> errors) throws UserManagementException 201 { 202 String email = request.getParameter("email"); 203 204 Page page = _resolver.resolveById(request.getParameter("page-id")); 205 206 UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(populationId); 207 if (userPopulation == null) 208 { 209 throw new IllegalStateException("The signup service in page '" + page.getTitle() + " (" + page.getId() + ")' is configured with the unexisting '" + populationId + "' user population identifier. Please reconfigure the service."); 210 } 211 ModifiableUserDirectory userDirectory = (ModifiableUserDirectory) userPopulation.getUserDirectory(userDirectoryId); 212 if (userDirectory == null) 213 { 214 throw new IllegalStateException("The signup service in page '" + page.getTitle() + " (" + page.getId() + ")' is configured with the unexisting '" + userDirectoryId + "' user directory identifier. Please reconfigure the service."); 215 } 216 217 Map<String, String> additionalFields = getAdditionalValues(request, userDirectory); 218 219 if (_pageHelper.isCaptchaRequired(page)) 220 { 221 String captchaValue = request.getParameter("captcha"); 222 String captchaKey = request.getParameter("captcha-key"); 223 224 // Validate the input 225 if (!CaptchaHelper.checkAndInvalidate(captchaKey, captchaValue)) 226 { 227 errors.put("captcha", new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_FORM_ERROR_INVALID_CAPTCHA")); 228 } 229 } 230 231 232 Map<String, List<I18nizableText>> inputErrors = _userSignupManager.validate(siteName, email, additionalFields); 233 234 for (String field : inputErrors.keySet()) 235 { 236 errors.putAll(field, inputErrors.get(field)); 237 } 238 239 if (errors.isEmpty()) 240 { 241 try 242 { 243 _userSignupManager.temporarySignup(siteName, language, email, populationId, userDirectoryId); 244 } 245 catch (UserManagementException e) 246 { 247 switch (e.getStatusError()) 248 { 249 case USER_ALREADY_EXISTS: 250 errors.put("global", new I18nizableText("user-email-already-exists")); 251 break; 252 case TEMP_USER_ALREADY_EXISTS: 253 errors.put("global", new I18nizableText("temp-email-already-exists")); 254 break; 255 default: 256 throw e; 257 } 258 259 } 260 } 261 } 262 263 /** 264 * Reset a user's sign-up request. 265 * @param request the user request. 266 * @param siteName the site name. 267 * @param language the language. 268 * @param populationId The id of the population 269 * @param userDirectoryId The id of the user directory of the population 270 * @param errors the Map to fill with errors to display to the user. 271 * @throws UserManagementException if an error occurs. 272 */ 273 protected void resetTempSignup(Request request, String siteName, String language, String populationId, String userDirectoryId, Multimap<String, I18nizableText> errors) throws UserManagementException 274 { 275 String email = request.getParameter("email"); 276 277 try 278 { 279 _userSignupManager.resetTempSignup(siteName, language, email, populationId, userDirectoryId); 280 } 281 catch (UserManagementException e) 282 { 283 switch (e.getStatusError()) 284 { 285 case UNKNOWN_EMAIL: 286 errors.put("global", new I18nizableText("temp-email-unknown")); 287 break; 288 default: 289 throw e; 290 } 291 } 292 } 293 294 /** 295 * Check that a token is valid. 296 * @param request the user request. 297 * @param siteName the site name. 298 * @param email the user e-mail. 299 * @param token the sign-up token that was sent to the user. 300 * @param populationId The id of the population 301 * @param userDirectoryId The id of the user directory of the population 302 * @param errors the Map to fill with errors to display to the user. 303 * @throws UserManagementException if an error occurs. 304 */ 305 protected void checkToken(Request request, String siteName, String email, String token, String populationId, String userDirectoryId, Multimap<String, I18nizableText> errors) throws UserManagementException 306 { 307 try 308 { 309 _userSignupManager.checkToken(siteName, email, token, populationId, userDirectoryId); 310 } 311 catch (UserManagementException e) 312 { 313 switch (e.getStatusError()) 314 { 315 case TOKEN_UNKNOWN: 316 errors.put("global", new I18nizableText("error-token-unknown")); 317 break; 318 case TOKEN_EXPIRED: 319 errors.put("global", new I18nizableText("error-token-expired")); 320 break; 321 default: 322 throw e; 323 } 324 } 325 } 326 327 /** 328 * Sign-up the user: create a real user from his temporary information. 329 * @param request the user request. 330 * @param parameters the action parameters. 331 * @param siteName the site name. 332 * @param language the current language. 333 * @param firstname the user firstname. 334 * @param lastname the user lastname. 335 * @param email the user e-mail. 336 * @param token the sign-up token that was sent to the user. 337 * @param populationId The id of the population 338 * @param userDirectoryId The id of the user directory of the population 339 * @param errors the Map to fill with errors to display to the user. 340 * @throws UserManagementException if an error occurs. 341 */ 342 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 343 { 344 String password = request.getParameter("password"); 345 String passwordConfirmation = request.getParameter("password-confirmation"); 346 String tos = request.getParameter("tos"); 347 String tosMode = parameters.getParameter("tos-mode", ""); 348 349 if (StringUtils.isBlank(password)) 350 { 351 errors.put("password", new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_PASSWORD_EMPTY")); 352 } 353 else if (!password.equals(passwordConfirmation)) 354 { 355 errors.put("password", new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_PASSWORD_CONFIRMATION_DOESNT_MATCH")); 356 } 357 358 if (StringUtils.isBlank(firstname)) 359 { 360 errors.put("firstname", new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_FIRSTNAME_EMPTY")); 361 } 362 363 if (StringUtils.isBlank(lastname)) 364 { 365 errors.put("lastname", new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_LASTNAME_EMPTY")); 366 } 367 368 if (!"NONE".equals(tosMode) && !"true".equals(tos)) 369 { 370 errors.put("tos", new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_FORM_ERROR_TOS_NOT_CHECKED")); 371 } 372 373 UserDirectory userDirectory = _userPopulationDAO.getUserPopulation(populationId).getUserDirectory(userDirectoryId); 374 if (!(userDirectory instanceof ModifiableUserDirectory modifiableUserDirectory)) 375 { 376 throw new UserManagementException("The user subscription feature can't be used, as the UserDirectory is not modifiable.", StatusError.UNMODIFIABLE_USER_DIRECTORY); 377 } 378 379 // Standard info for user. 380 Map<String, String> userInfos = new HashMap<>(); 381 userInfos.put("email", email); 382 userInfos.put("firstname", firstname); 383 userInfos.put("lastname", lastname); 384 userInfos.put("password", password); 385 386 Map<String, List<I18nizableText>> inputErrors = modifiableUserDirectory.validate(userInfos); 387 inputErrors.remove("login"); // ignore errors on login (user do not have login yet) 388 for (String field : inputErrors.keySet()) 389 { 390 errors.putAll(field, inputErrors.get(field)); 391 } 392 393 if (errors.isEmpty()) 394 { 395 try 396 { 397 _userSignupManager.signup(siteName, language, firstname, lastname, email, token, password, populationId, userDirectoryId, errors); 398 } 399 catch (UserManagementException e) 400 { 401 switch (e.getStatusError()) 402 { 403 case TOKEN_UNKNOWN: 404 errors.put("global", new I18nizableText("error-token-unknown")); 405 break; 406 case TOKEN_EXPIRED: 407 errors.put("global", new I18nizableText("error-token-expired")); 408 break; 409 default: 410 throw e; 411 } 412 } 413 } 414 } 415 416 /** 417 * Get FO user manager's custom field values. 418 * @param request the request. 419 * @param userDirectory the user directory 420 * @return the custom field values. 421 * @throws UserManagementException if an error occurs. 422 */ 423 protected Map<String, String> getAdditionalValues(Request request, ModifiableUserDirectory userDirectory) throws UserManagementException 424 { 425 Map<String, String> additionalFields = new HashMap<>(); 426 427 Collection< ? extends ModelItem> fields = userDirectory.getModelItems(); 428 429 for (ModelItem field : fields) 430 { 431 String fieldId = field.getName(); 432 if (!STANDARD_FIELDS.contains(fieldId)) 433 { 434 String value = request.getParameter(fieldId); 435 436 additionalFields.put(fieldId, value); 437 } 438 } 439 440 return additionalFields; 441 } 442}