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