001/* 002 * Copyright 2012 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.web.usermanagement; 017 018import java.io.IOException; 019import java.net.MalformedURLException; 020import java.util.Arrays; 021import java.util.Collection; 022 023import javax.jcr.Repository; 024import javax.jcr.RepositoryException; 025import javax.jcr.Session; 026 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.cocoon.ProcessingException; 030import org.apache.cocoon.components.source.impl.SitemapSource; 031import org.apache.cocoon.environment.ObjectModelHelper; 032import org.apache.cocoon.environment.Request; 033import org.apache.cocoon.generation.ServiceableGenerator; 034import org.apache.cocoon.xml.AttributesImpl; 035import org.apache.cocoon.xml.XMLUtils; 036import org.apache.commons.lang.StringUtils; 037import org.apache.excalibur.source.SourceResolver; 038import org.xml.sax.SAXException; 039 040import org.ametys.cms.CmsConstants; 041import org.ametys.cms.content.ContentHelper; 042import org.ametys.cms.repository.Content; 043import org.ametys.core.right.RightManager; 044import org.ametys.core.user.CurrentUserProvider; 045import org.ametys.core.user.UserIdentity; 046import org.ametys.core.user.directory.UserDirectory; 047import org.ametys.core.util.DateUtils; 048import org.ametys.core.util.IgnoreRootHandler; 049import org.ametys.plugins.core.impl.user.directory.JdbcUserDirectory; 050import org.ametys.plugins.repository.AmetysObjectResolver; 051import org.ametys.plugins.repository.UnknownAmetysObjectException; 052import org.ametys.plugins.repository.version.VersionableAmetysObject; 053import org.ametys.runtime.i18n.I18nizableText; 054import org.ametys.web.WebConstants; 055import org.ametys.web.cache.PageHelper; 056import org.ametys.web.content.GetSiteAction; 057import org.ametys.web.repository.page.Page; 058import org.ametys.web.repository.page.ZoneItem; 059import org.ametys.web.repository.site.SiteManager; 060import org.ametys.web.skin.Skin; 061import org.ametys.web.skin.SkinsManager; 062import org.ametys.web.synchronization.SynchronizeComponent; 063import org.ametys.web.usermanagement.UserSignupManager.SignupType; 064 065import com.google.common.collect.Multimap; 066 067/** 068 * Generate information to render the user signup service. 069 */ 070public class UserSignupGenerator extends ServiceableGenerator 071{ 072 /** The user signup manager. */ 073 protected UserSignupManager _userSignupManager; 074 /** The site Manager. */ 075 protected SiteManager _siteManager; 076 /** The ametys object resolver. */ 077 protected AmetysObjectResolver _resolver; 078 /** The component for live synchronization */ 079 protected SynchronizeComponent _synchronizeComponent; 080 /** The {@link SkinsManager}*/ 081 protected SkinsManager _skinManager; 082 /** The repository */ 083 protected Repository _repository; 084 /** Page helper */ 085 protected PageHelper _pageHelper; 086 /** The content helper */ 087 protected ContentHelper _contentHelper; 088 /** The current user provider */ 089 protected CurrentUserProvider _currentUserProvider; 090 /** The source resolver */ 091 protected SourceResolver _srcResolver; 092 /** Right manager */ 093 protected RightManager _rightManager; 094 095 096 @Override 097 public void service(ServiceManager serviceManager) throws ServiceException 098 { 099 super.service(serviceManager); 100 _userSignupManager = (UserSignupManager) serviceManager.lookup(UserSignupManager.ROLE); 101 _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE); 102 _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE); 103 _srcResolver = (SourceResolver) serviceManager.lookup(SourceResolver.ROLE); 104 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 105 _skinManager = (SkinsManager) serviceManager.lookup(SkinsManager.ROLE); 106 _synchronizeComponent = (SynchronizeComponent) serviceManager.lookup(SynchronizeComponent.ROLE); 107 _repository = (Repository) serviceManager.lookup(Repository.class.getName()); 108 _pageHelper = (PageHelper) serviceManager.lookup(PageHelper.ROLE); 109 _contentHelper = (ContentHelper) serviceManager.lookup(ContentHelper.ROLE); 110 _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE); 111 } 112 113 @Override 114 public void generate() throws IOException, SAXException, ProcessingException 115 { 116 Request request = ObjectModelHelper.getRequest(objectModel); 117 String siteName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME); 118 String language = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITEMAP_NAME); 119 Page page = (Page) request.getAttribute(Page.class.getName()); 120 ZoneItem zoneItem = (ZoneItem) request.getAttribute(WebConstants.REQUEST_ATTR_ZONEITEM); 121 122 SignupType signupType = _userSignupManager.getSignupType(siteName); 123 Page signupPage = _userSignupManager.getSignupPage(siteName, language); 124 Page pwdChangePage = _userSignupManager.getPwdChangePage(siteName, language); 125 126 @SuppressWarnings("unchecked") 127 Multimap<String, I18nizableText> errors = (Multimap<String, I18nizableText>) request.getAttribute("errors"); 128 String firstName = request.getParameter("firstname"); 129 String lastName = request.getParameter("lastname"); 130 String email = request.getParameter("email"); 131 String token = request.getParameter("token"); 132 133 contentHandler.startDocument(); 134 135 AttributesImpl attrs = new AttributesImpl(); 136 if (page != null) 137 { 138 _addNotEmptyAttribute (attrs, "current-page", page.getId()); 139 } 140 141 _addNotEmptyAttribute (attrs, "public-signup", String.valueOf(signupType == SignupType.PUBLIC)); 142 143 if (signupPage != null) 144 { 145 _addNotEmptyAttribute (attrs, "signup-page-id", signupPage.getId()); 146 } 147 if (pwdChangePage != null) 148 { 149 _addNotEmptyAttribute (attrs, "password-change-page-id", pwdChangePage.getId()); 150 } 151 152 UserIdentity userIdentity = _currentUserProvider.getUser(); 153 if (userIdentity != null) 154 { 155 _addNotEmptyAttribute (attrs, "fo-user-login", userIdentity.getLogin()); 156 _addNotEmptyAttribute (attrs, "fo-user-population", userIdentity.getPopulationId()); 157 } 158 159 // Keep for compatibity purpose but already sax in #saxInputs 160 _addNotEmptyAttribute (attrs, "firstname", firstName); 161 _addNotEmptyAttribute (attrs, "lastname", lastName); 162 _addNotEmptyAttribute (attrs, "email", email); 163 _addNotEmptyAttribute (attrs, "token", token); 164 165 XMLUtils.startElement(contentHandler, "user-signup", attrs); 166 167 if (errors != null) 168 { 169 saxErrors(errors); 170 } 171 172 Page tosPage = _userSignupManager.getGTUPage(zoneItem); 173 Page successPage = _userSignupManager.getSuccessPage(zoneItem); 174 Content tosContent = _userSignupManager.getGTUContent(zoneItem); 175 Content successContent = _userSignupManager.getSuccessContent(zoneItem); 176 177 UserDirectory userDirectory = _userSignupManager.getUserDirectory(zoneItem); 178 if (userDirectory instanceof JdbcUserDirectory jdbcUserDirectory && jdbcUserDirectory.useStrongPassword()) 179 { 180 jdbcUserDirectory.getStrongPasswordRequirements().toSAX(contentHandler, "password-requirements"); 181 } 182 183 XMLUtils.startElement(contentHandler, "user-inputs"); 184 saxUserInputs(request, zoneItem); 185 XMLUtils.endElement(contentHandler, "user-inputs"); 186 187 saxWarnings(signupType, signupPage, pwdChangePage, tosPage, successPage, tosContent, successContent); 188 189 XMLUtils.createElement(contentHandler, "has-captcha", String.valueOf(_pageHelper.isCaptchaRequired(page))); 190 191 saxTOSIfNeeded(zoneItem); 192 saxSuccessContentIfNeeded(zoneItem); 193 194 saxAdditionalInformation(siteName, language, zoneItem); 195 196 XMLUtils.endElement(contentHandler, "user-signup"); 197 198 contentHandler.endDocument(); 199 } 200 201 /** 202 * SAX the user inputs 203 * @param request the request 204 * @param zoneItem the zone item holding the service 205 * @throws SAXException if an error occurs 206 */ 207 protected void saxUserInputs(Request request, ZoneItem zoneItem) throws SAXException 208 { 209 _saxNotEmptyParameter(request, "firstname"); 210 _saxNotEmptyParameter(request, "lastname"); 211 _saxNotEmptyParameter(request, "email"); 212 _saxNotEmptyParameter(request, "token"); 213 } 214 215 /** 216 * Sax a non-empty request parameter 217 * @param request the request 218 * @param paramName the parameter name 219 * @throws SAXException if an error occurs 220 */ 221 protected void _saxNotEmptyParameter(Request request, String paramName) throws SAXException 222 { 223 _saxNotEmptyValue(paramName, request.getParameter(paramName)); 224 } 225 226 /** 227 * Sax a non-empty value 228 * @param tagName the tag name 229 * @param value the value 230 * @throws SAXException if an error occurs 231 */ 232 protected void _saxNotEmptyValue(String tagName, String value) throws SAXException 233 { 234 if (StringUtils.isNotEmpty(value)) 235 { 236 XMLUtils.createElement(contentHandler, tagName, value); 237 } 238 } 239 240 /** 241 * SAX additional information 242 * @param siteName The site name 243 * @param lang The content language 244 * @param zoneItem The zone item holding the service 245 * @throws SAXException if an error occurs 246 */ 247 protected void saxAdditionalInformation(String siteName, String lang, ZoneItem zoneItem) throws SAXException 248 { 249 // Nothing 250 } 251 252 /** 253 * SAX the general term of use content if needed 254 * @param zoneItem The current zone item 255 * @throws MalformedURLException if an error occurs 256 * @throws SAXException if an error occurs 257 * @throws IOException if an error occurs 258 */ 259 protected void saxTOSIfNeeded(ZoneItem zoneItem) throws MalformedURLException, SAXException, IOException 260 { 261 String tosMode = zoneItem.getServiceParameters().getValue("terms-of-service-mode"); 262 if ("CONTENT".equals(tosMode)) 263 { 264 String contentId = zoneItem.getServiceParameters().getValue("terms-of-service-content"); 265 saxContent(contentId, "tos"); 266 } 267 } 268 269 /** 270 * SAX the success content if needed 271 * @param zoneItem The current zone item 272 * @throws MalformedURLException if an error occurs 273 * @throws SAXException if an error occurs 274 * @throws IOException if an error occurs 275 */ 276 protected void saxSuccessContentIfNeeded(ZoneItem zoneItem) throws MalformedURLException, SAXException, IOException 277 { 278 String status = parameters.getParameter("status", null); 279 String step = parameters.getParameter("step", null); 280 if ("signup".equals(step) && "success".equals(status)) 281 { 282 String successMode = zoneItem.getServiceParameters().getValue("success-mode"); 283 if ("CONTENT".equals(successMode)) 284 { 285 String contentId = zoneItem.getServiceParameters().getValue("success-content"); 286 saxContent(contentId, "success"); 287 } 288 } 289 } 290 291 private void _addNotEmptyAttribute (AttributesImpl attrs, String name, String value) 292 { 293 if (StringUtils.isNotEmpty(value)) 294 { 295 attrs.addCDATAAttribute(name, value); 296 } 297 } 298 299 /** 300 * SAX the content of GTU 301 * @param contentId the id of GTU content 302 * @param rootTag The root XML tag 303 * @throws SAXException if an error occurs 304 * @throws IOException if failed to sax TGU content 305 * @throws MalformedURLException if failed to sax TGU content 306 */ 307 protected void saxContent(String contentId, String rootTag) throws SAXException, MalformedURLException, IOException 308 { 309 try 310 { 311 Content gtu = _resolver.resolveById(contentId); 312 313 XMLUtils.startElement(contentHandler, rootTag); 314 315 AttributesImpl attrs = new AttributesImpl(); 316 attrs.addCDATAAttribute("id", gtu.getId()); 317 attrs.addCDATAAttribute("name", gtu.getName()); 318 attrs.addCDATAAttribute("title", gtu.getTitle(null)); 319 attrs.addCDATAAttribute("lastModifiedAt", DateUtils.zonedDateTimeToString(gtu.getLastModified())); 320 321 XMLUtils.startElement(contentHandler, "content", attrs); 322 323 String uri = _contentHelper.getContentHtmlViewUrl(gtu, "main"); 324 SitemapSource src = null; 325 326 Request request = ObjectModelHelper.getRequest(objectModel); 327 328 String currentSiteName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME); 329 String currentSkinName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SKIN_ID); 330 request.setAttribute(GetSiteAction.OVERRIDE_SITE_REQUEST_ATTR, currentSiteName); 331 request.setAttribute(GetSiteAction.OVERRIDE_SKIN_REQUEST_ATTR, currentSkinName); 332 333 try 334 { 335 src = (SitemapSource) _srcResolver.resolveURI(uri); 336 src.toSAX(new IgnoreRootHandler(contentHandler)); 337 } 338 finally 339 { 340 _srcResolver.release(src); 341 request.removeAttribute(GetSiteAction.OVERRIDE_SITE_REQUEST_ATTR); 342 request.removeAttribute(GetSiteAction.OVERRIDE_SKIN_REQUEST_ATTR); 343 request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, currentSiteName); 344 request.setAttribute("siteName", currentSiteName); 345 request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, currentSkinName); 346 } 347 348 XMLUtils.endElement(contentHandler, "content"); 349 350 XMLUtils.endElement(contentHandler, rootTag); 351 } 352 catch (UnknownAmetysObjectException e) 353 { 354 getLogger().error("The GTU content with id '" + contentId + "' does not exist anymore"); 355 } 356 357 } 358 359 /** 360 * Generate errors. 361 * @param errors the error list. 362 * @throws SAXException if an error occurs. 363 */ 364 protected void saxErrors(Multimap<String, I18nizableText> errors) throws SAXException 365 { 366 XMLUtils.startElement(contentHandler, "errors"); 367 368 if (errors.containsKey("global")) 369 { 370 Collection<I18nizableText> globalErrors = errors.get("global"); 371 372 XMLUtils.startElement(contentHandler, "global"); 373 374 for (I18nizableText error : globalErrors) 375 { 376 AttributesImpl errorAttrs = new AttributesImpl(); 377 if (!error.isI18n()) 378 { 379 errorAttrs.addCDATAAttribute("type", error.getLabel()); 380 } 381 XMLUtils.createElement(contentHandler, "error", errorAttrs); 382 } 383 384 XMLUtils.endElement(contentHandler, "global"); 385 } 386 387 for (String field : errors.keySet()) 388 { 389 if (!field.equals("global")) 390 { 391 Collection<I18nizableText> fieldErrors = errors.get(field); 392 393 AttributesImpl attrs = new AttributesImpl(); 394 attrs.addCDATAAttribute("name", field); 395 396 XMLUtils.startElement(contentHandler, "field", attrs); 397 398 for (I18nizableText error : fieldErrors) 399 { 400 error.toSAX(contentHandler, "error"); 401 } 402 403 XMLUtils.endElement(contentHandler, "field"); 404 } 405 } 406 407 XMLUtils.endElement(contentHandler, "errors"); 408 } 409 410 /** 411 * Generate errors which could prevent the service from working. 412 * @param signupType the type of signup enabled for the site 413 * @param signupPage the signup page. 414 * @param pwdChangePage the lost password page. Can be null. 415 * @param tosPage the page of terms of use. Can be null. 416 * @param successPage the page of success. Can be null. 417 * @param tosContent the content for terms of use. Can be null. 418 * @param successContent the content for success. Can be null. 419 * @throws SAXException if an error occurs. 420 */ 421 protected void saxWarnings(SignupType signupType, Page signupPage, Page pwdChangePage, Page tosPage, Page successPage, Content tosContent, Content successContent) throws SAXException 422 { 423 424 XMLUtils.startElement(contentHandler, "warnings"); 425 426 if (signupType == null || signupType == SignupType.UNAUTHORIZED) 427 { 428 XMLUtils.startElement(contentHandler, "warning"); 429 new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_NOT_AUTHORIZED").toSAX(contentHandler); 430 XMLUtils.endElement(contentHandler, "warning"); 431 } 432 433 if (signupPage == null) 434 { 435 XMLUtils.startElement(contentHandler, "warning"); 436 new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_NO_SIGNUP_PAGE").toSAX(contentHandler); 437 XMLUtils.endElement(contentHandler, "warning"); 438 } 439 else if (!_rightManager.hasAnonymousReadAccess(signupPage)) 440 { 441 XMLUtils.startElement(contentHandler, "warning"); 442 new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_SIGNUP_PAGE_READ_ACCESS").toSAX(contentHandler); 443 XMLUtils.endElement(contentHandler, "warning"); 444 } 445 else if (!_isValid(signupPage)) 446 { 447 XMLUtils.startElement(contentHandler, "warning"); 448 new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_INVALID_SIGNUP_PAGE").toSAX(contentHandler); 449 XMLUtils.endElement(contentHandler, "warning"); 450 } 451 452 if (pwdChangePage == null) 453 { 454 XMLUtils.startElement(contentHandler, "warning"); 455 new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_NO_PASSWORD_CHANGE_PAGE").toSAX(contentHandler); 456 XMLUtils.endElement(contentHandler, "warning"); 457 } 458 else if (!_isValid(pwdChangePage)) 459 { 460 XMLUtils.startElement(contentHandler, "warning"); 461 new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_INVALID_PASSWORD_CHANGE_PAGE").toSAX(contentHandler); 462 XMLUtils.endElement(contentHandler, "warning"); 463 } 464 465 if (tosPage != null && !_isValid(tosPage)) 466 { 467 XMLUtils.startElement(contentHandler, "warning"); 468 new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_INVALID_TOS_PAGE").toSAX(contentHandler); 469 XMLUtils.endElement(contentHandler, "warning"); 470 } 471 472 if (successPage != null && !_isValid(successPage)) 473 { 474 XMLUtils.startElement(contentHandler, "warning"); 475 new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_INVALID_SUCCESS_PAGE").toSAX(contentHandler); 476 XMLUtils.endElement(contentHandler, "warning"); 477 } 478 479 if (tosContent != null && !_isValid(tosContent)) 480 { 481 XMLUtils.startElement(contentHandler, "warning"); 482 new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_INVALID_TOS_CONTENT").toSAX(contentHandler); 483 XMLUtils.endElement(contentHandler, "warning"); 484 } 485 486 if (successContent != null && !_isValid(successContent)) 487 { 488 XMLUtils.startElement(contentHandler, "warning"); 489 new I18nizableText("plugin.web", "PLUGINS_WEB_USER_SIGNUP_ERROR_INVALID_SUCCESS_CONTENT").toSAX(contentHandler); 490 XMLUtils.endElement(contentHandler, "warning"); 491 } 492 493 XMLUtils.endElement(contentHandler, "warnings"); 494 } 495 496 private boolean _isValid(Page page) 497 { 498 Skin skin = _skinManager.getSkin(page.getSite().getSkinId()); 499 500 Session liveSession = null; 501 try 502 { 503 liveSession = _repository.login(WebConstants.LIVE_WORKSPACE); 504 505 if (!_synchronizeComponent.isPageValid(page, skin) || !_synchronizeComponent.isHierarchyValid(page, liveSession)) 506 { 507 return false; 508 } 509 return true; 510 } 511 catch (RepositoryException e) 512 { 513 throw new RuntimeException("Unable to check live workspace", e); 514 } 515 finally 516 { 517 if (liveSession != null) 518 { 519 liveSession.logout(); 520 } 521 } 522 } 523 524 private boolean _isValid(Content content) 525 { 526 if (content instanceof VersionableAmetysObject) 527 { 528 return Arrays.asList(((VersionableAmetysObject) content).getAllLabels()).contains(CmsConstants.LIVE_LABEL); 529 } 530 531 return false; 532 } 533}