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