001/* 002 * (C) Copyright 2011-2018 Nuxeo (http://nuxeo.com/) and others. 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 * Contributors: 016 * Nuxeo - initial API and implementation 017 */ 018 019package org.nuxeo.ecm.user.invite; 020 021import static org.apache.commons.lang3.StringUtils.isBlank; 022import static org.nuxeo.ecm.user.invite.RegistrationRules.FACET_REGISTRATION_CONFIGURATION; 023import static org.nuxeo.ecm.user.invite.RegistrationRules.FIELD_CONFIGURATION_NAME; 024import static org.nuxeo.ecm.user.invite.UserInvitationService.ValidationMethod.EMAIL; 025import static org.nuxeo.ecm.user.invite.UserRegistrationConfiguration.DEFAULT_CONFIGURATION_NAME; 026 027import java.io.IOException; 028import java.io.Serializable; 029import java.io.StringReader; 030import java.io.StringWriter; 031import java.io.Writer; 032import java.util.Date; 033import java.util.HashMap; 034import java.util.List; 035import java.util.Map; 036import java.util.Set; 037 038import javax.mail.Message; 039import javax.mail.MessagingException; 040import javax.mail.Session; 041import javax.mail.Transport; 042import javax.mail.internet.InternetAddress; 043import javax.mail.internet.MimeMessage; 044import javax.naming.InitialContext; 045import javax.naming.NamingException; 046 047import org.apache.commons.lang3.StringUtils; 048import org.apache.commons.logging.Log; 049import org.apache.commons.logging.LogFactory; 050import org.nuxeo.ecm.core.api.CoreSession; 051import org.nuxeo.ecm.core.api.DocumentModel; 052import org.nuxeo.ecm.core.api.DocumentModelList; 053import org.nuxeo.ecm.core.api.DocumentRef; 054import org.nuxeo.ecm.core.api.IdRef; 055import org.nuxeo.ecm.core.api.NuxeoException; 056import org.nuxeo.ecm.core.api.NuxeoPrincipal; 057import org.nuxeo.ecm.core.api.PathRef; 058import org.nuxeo.ecm.core.api.PropertyException; 059import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 060import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 061import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService; 062import org.nuxeo.ecm.core.api.repository.RepositoryManager; 063import org.nuxeo.ecm.core.api.security.ACE; 064import org.nuxeo.ecm.core.api.security.ACL; 065import org.nuxeo.ecm.core.api.security.ACP; 066import org.nuxeo.ecm.core.event.Event; 067import org.nuxeo.ecm.core.event.EventContext; 068import org.nuxeo.ecm.core.event.EventService; 069import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 070import org.nuxeo.ecm.platform.rendering.api.RenderingException; 071import org.nuxeo.ecm.platform.usermanager.NuxeoPrincipalImpl; 072import org.nuxeo.ecm.platform.usermanager.UserConfig; 073import org.nuxeo.ecm.platform.usermanager.UserManager; 074import org.nuxeo.ecm.platform.usermanager.exceptions.UserAlreadyExistsException; 075import org.nuxeo.runtime.api.Framework; 076import org.nuxeo.runtime.model.ComponentInstance; 077import org.nuxeo.runtime.model.DefaultComponent; 078 079import freemarker.template.Configuration; 080import freemarker.template.Template; 081import freemarker.template.TemplateException; 082 083public class UserInvitationComponent extends DefaultComponent implements UserInvitationService { 084 085 public static final String PARAM_ORIGINATING_USER = "registration:originatingUser"; 086 087 protected static Log log = LogFactory.getLog(UserInvitationService.class); 088 089 public static final String NUXEO_URL_KEY = "nuxeo.url"; 090 091 protected String repoName = null; 092 093 protected String testRendering = null; 094 095 protected RenderingHelper rh = new RenderingHelper(); 096 097 protected Map<String, UserRegistrationConfiguration> configurations = new HashMap<>(); 098 099 private static final String INVITATION_SUBMITTED_EVENT = "invitationSubmitted"; 100 101 private static final String INVITATION_ACCEPTED_EVENT = "invitationAccepted"; 102 103 private static final String INVITATION_REJECTED_EVENT = "invitationRejected"; 104 105 private static final String INVITATION_VALIDATED_EVENT = "invitationValidated"; 106 107 public String getTestedRendering() { 108 return testRendering; 109 } 110 111 protected String getTargetRepositoryName() { 112 if (repoName == null) { 113 RepositoryManager rm = Framework.getService(RepositoryManager.class); 114 repoName = rm.getDefaultRepositoryName(); 115 } 116 return repoName; 117 } 118 119 protected boolean userAlreadyExists(UserRegistrationInfo userRegistrationInfo) { 120 DocumentModel user = Framework.getService(UserManager.class).getUserModel(userRegistrationInfo.getLogin()); 121 return user != null; 122 } 123 124 protected String getJavaMailJndiName() { 125 return Framework.getProperty("jndi.java.mail", "java:/Mail"); 126 } 127 128 @Override 129 public DocumentModel getUserRegistrationModel(String configurationName) { 130 // Test if the configuration is defined 131 if (StringUtils.isEmpty(configurationName)) { 132 configurationName = DEFAULT_CONFIGURATION_NAME; 133 } 134 // Get the DocumentModel for the doctype defined in the configuration 135 UserRegistrationModelCreator creator = new UserRegistrationModelCreator(configurationName); 136 creator.runUnrestricted(); 137 return creator.getUserRegistrationModel(); 138 } 139 140 @Override 141 public DocumentModel getRegistrationRulesDocument(CoreSession session, String configurationName) { 142 // By default, configuration is hold by the root request document 143 return getOrCreateRootDocument(session, configurationName); 144 } 145 146 public DocumentModel getOrCreateRootDocument(CoreSession session, String configurationName) { 147 UserRegistrationConfiguration configuration = getConfiguration(configurationName); 148 149 String targetPath = configuration.getContainerParentPath() + configuration.getContainerName(); 150 DocumentRef targetRef = new PathRef(targetPath); 151 DocumentModel root; 152 153 if (!session.exists(targetRef)) { 154 root = session.createDocumentModel(configuration.getContainerDocType()); 155 root.setPathInfo(configuration.getContainerParentPath(), configuration.getContainerName()); 156 root.setPropertyValue("dc:title", configuration.getContainerTitle()); 157 // XXX ACLs ?!!! 158 root = session.createDocument(root); 159 } else { 160 root = session.getDocument(targetRef); 161 } 162 163 // Add configuration facet 164 if (!root.hasFacet(FACET_REGISTRATION_CONFIGURATION)) { 165 root.addFacet(FACET_REGISTRATION_CONFIGURATION); 166 root.setPropertyValue(FIELD_CONFIGURATION_NAME, configuration.getName()); 167 root = session.saveDocument(root); 168 } 169 return root; 170 } 171 172 protected class UserRegistrationModelCreator extends UnrestrictedSessionRunner { 173 174 DocumentModel userRegistrationModel; 175 176 protected UserRegistrationConfiguration configuration; 177 178 public UserRegistrationModelCreator(String configurationName) { 179 super(getTargetRepositoryName()); 180 configuration = getConfiguration(configurationName); 181 } 182 183 @Override 184 public void run() { 185 userRegistrationModel = session.createDocumentModel(configuration.getRequestDocType()); 186 } 187 188 public DocumentModel getUserRegistrationModel() { 189 return userRegistrationModel; 190 } 191 } 192 193 protected class RegistrationCreator extends UnrestrictedSessionRunner { 194 195 protected Map<String, Serializable> additionnalInfo; 196 197 protected String registrationUuid; 198 199 protected ValidationMethod validationMethod; 200 201 protected DocumentModel userRegistrationModel; 202 203 protected UserRegistrationConfiguration configuration; 204 205 public String getRegistrationUuid() { 206 return registrationUuid; 207 } 208 209 public RegistrationCreator(String configurationName, DocumentModel userRegistrationModel, 210 Map<String, Serializable> additionnalInfo, ValidationMethod validationMethod) { 211 super(getTargetRepositoryName()); 212 this.userRegistrationModel = userRegistrationModel; 213 this.additionnalInfo = additionnalInfo; 214 this.validationMethod = validationMethod; 215 configuration = getConfiguration(configurationName); 216 } 217 218 @Override 219 public void run() { 220 221 String title = "registration request for " 222 + userRegistrationModel.getPropertyValue(configuration.getUserInfoUsernameField()) + " (" 223 + userRegistrationModel.getPropertyValue(configuration.getUserInfoEmailField()) + " " 224 + userRegistrationModel.getPropertyValue(configuration.getUserInfoCompanyField()) + ") "; 225 PathSegmentService pss = Framework.getService(PathSegmentService.class); 226 String name = pss.generatePathSegment(title + "-" + System.currentTimeMillis()); 227 228 String targetPath = getOrCreateRootDocument(session, configuration.getName()).getPathAsString(); 229 230 userRegistrationModel.setPathInfo(targetPath, name); 231 userRegistrationModel.setPropertyValue("dc:title", title); 232 233 // validation method 234 userRegistrationModel.setPropertyValue("registration:validationMethod", validationMethod.toString()); 235 236 // additionnal infos 237 if (additionnalInfo != null && !additionnalInfo.isEmpty()) { 238 for (String key : additionnalInfo.keySet()) { 239 try { 240 userRegistrationModel.setPropertyValue(key, additionnalInfo.get(key)); 241 } catch (PropertyException e) { 242 // skip silently 243 } 244 } 245 } 246 247 userRegistrationModel = session.createDocument(userRegistrationModel); 248 249 registrationUuid = userRegistrationModel.getId(); 250 251 sendEvent(session, userRegistrationModel, getNameEventRegistrationSubmitted()); 252 253 session.save(); 254 } 255 256 } 257 258 protected class RegistrationApprover extends UnrestrictedSessionRunner { 259 260 private final UserManager userManager; 261 262 protected String uuid; 263 264 protected Map<String, Serializable> additionnalInfo; 265 266 public RegistrationApprover(String registrationUuid, Map<String, Serializable> additionnalInfo) { 267 super(getTargetRepositoryName()); 268 uuid = registrationUuid; 269 this.additionnalInfo = additionnalInfo; 270 this.userManager = Framework.getService(UserManager.class); 271 } 272 273 @Override 274 public void run() { 275 276 DocumentModel doc = session.getDocument(new IdRef(uuid)); 277 String validationMethod = (String) doc.getPropertyValue("registration:validationMethod"); 278 279 NuxeoPrincipal targetPrincipal = userManager.getPrincipal((String) doc.getPropertyValue("userinfo:login")); 280 if (targetPrincipal != null) { 281 throw new UserAlreadyExistsException(); 282 } 283 284 targetPrincipal = userManager.getPrincipal((String) doc.getPropertyValue("userinfo:email")); 285 if (targetPrincipal != null) { 286 DocumentModel target = session.getDocument( 287 new IdRef((String) doc.getPropertyValue("docinfo:documentId"))); 288 ACP acp = target.getACP(); 289 Map<String, Serializable> contextData = new HashMap<>(); 290 contextData.put("notify", true); 291 contextData.put("comment", doc.getPropertyValue("registration:comment")); 292 acp.addACE(ACL.LOCAL_ACL, 293 ACE.builder(targetPrincipal.getName(), (String) doc.getPropertyValue("docinfo:permission")) 294 .creator((String) doc.getPropertyValue("docinfo:creator")) 295 .contextData(contextData) 296 .build()); 297 target.setACP(acp, true); 298 // test Validation Method 299 } else if (StringUtils.equals(EMAIL.toString(), validationMethod)) { 300 sendValidationEmail(additionnalInfo, doc); 301 } 302 303 doc.setPropertyValue("registration:accepted", true); 304 if (doc.getAllowedStateTransitions().contains("approve")) { 305 doc.followTransition("approve"); 306 } 307 doc = session.saveDocument(doc); 308 session.save(); 309 310 sendEvent(session, doc, getNameEventRegistrationAccepted()); 311 } 312 } 313 314 protected class RegistrationRejector extends UnrestrictedSessionRunner { 315 316 protected String uuid; 317 318 protected Map<String, Serializable> additionnalInfo; 319 320 public RegistrationRejector(String registrationUuid, Map<String, Serializable> additionnalInfo) { 321 super(getTargetRepositoryName()); 322 uuid = registrationUuid; 323 this.additionnalInfo = additionnalInfo; 324 } 325 326 @Override 327 public void run() { 328 329 DocumentModel doc = session.getDocument(new IdRef(uuid)); 330 331 doc.setPropertyValue("registration:accepted", false); 332 if (doc.getAllowedStateTransitions().contains("reject")) { 333 doc.followTransition("reject"); 334 } 335 doc = session.saveDocument(doc); 336 session.save(); 337 338 sendEvent(session, doc, getNameEventRegistrationRejected()); 339 } 340 } 341 342 protected class RegistrationAcceptator extends UnrestrictedSessionRunner { 343 344 protected String uuid; 345 346 protected Map<String, Serializable> registrationData = new HashMap<>(); 347 348 protected Map<String, Serializable> additionnalInfo; 349 350 public RegistrationAcceptator(String uuid, Map<String, Serializable> additionnalInfo) { 351 super(getTargetRepositoryName()); 352 this.uuid = uuid; 353 this.additionnalInfo = additionnalInfo; 354 } 355 356 public Map<String, Serializable> getRegistrationData() { 357 return registrationData; 358 } 359 360 @Override 361 public void run() { 362 DocumentRef idRef = new IdRef(uuid); 363 364 DocumentModel registrationDoc = session.getDocument(idRef); 365 366 // additionnal infos 367 for (String key : additionnalInfo.keySet()) { 368 try { 369 if (DefaultInvitationUserFactory.PASSWORD_KEY.equals(key)) { 370 // add the password as a transient context data 371 registrationDoc.putContextData(DefaultInvitationUserFactory.PASSWORD_KEY, 372 additionnalInfo.get(key)); 373 } else { 374 registrationDoc.setPropertyValue(key, additionnalInfo.get(key)); 375 } 376 } catch (PropertyException e) { 377 // skip silently 378 } 379 } 380 381 NuxeoPrincipal principal = null; 382 if (registrationDoc.getLifeCyclePolicy().equals("registrationRequest")) { 383 if (registrationDoc.getCurrentLifeCycleState().equals("approved")) { 384 try { 385 UserInvitationService userRegistrationService = Framework.getService( 386 UserInvitationService.class); 387 UserRegistrationConfiguration config = userRegistrationService.getConfiguration( 388 registrationDoc); 389 RegistrationRules rules = userRegistrationService.getRegistrationRules(config.getName()); 390 if (rules.allowUserCreation()) { 391 principal = userRegistrationService.createUser(session, registrationDoc); 392 } 393 registrationDoc.followTransition("accept"); 394 } catch (NuxeoException e) { 395 e.addInfo("Unable to complete registration"); 396 throw e; 397 } 398 } else { 399 if (registrationDoc.getCurrentLifeCycleState().equals("accepted")) { 400 throw new AlreadyProcessedRegistrationException( 401 "Registration request has already been processed"); 402 } else { 403 throw new UserRegistrationException("Registration request has not been accepted yet"); 404 } 405 } 406 } 407 408 session.saveDocument(registrationDoc); 409 session.save(); 410 sendEvent(session, registrationDoc, getNameEventRegistrationValidated()); 411 registrationDoc.detach(sessionIsAlreadyUnrestricted); 412 registrationData.put(REGISTRATION_DATA_DOC, registrationDoc); 413 registrationData.put(REGISTRATION_DATA_USER, principal); 414 } 415 416 } 417 418 protected class RequestIdValidator extends UnrestrictedSessionRunner { 419 420 protected String uuid; 421 422 public RequestIdValidator(String uuid) { 423 super(getTargetRepositoryName()); 424 this.uuid = uuid; 425 } 426 427 @Override 428 public void run() { 429 DocumentRef idRef = new IdRef(uuid); 430 // Check if the id matches an existing document 431 if (!session.exists(idRef)) { 432 throw new UserRegistrationException("There is no existing registration request with id " + uuid); 433 } 434 435 // Check if the request has not been already validated 436 DocumentModel registrationDoc = session.getDocument(idRef); 437 if (registrationDoc.getCurrentLifeCycleState().equals("accepted")) { 438 throw new AlreadyProcessedRegistrationException("Registration request has already been processed"); 439 } 440 } 441 } 442 443 protected EventContext sendEvent(CoreSession session, DocumentModel source, String evName) 444 throws UserRegistrationException { 445 try { 446 EventService evService = Framework.getService(EventService.class); 447 EventContext evContext = new DocumentEventContext(session, session.getPrincipal(), source); 448 449 Event event = evContext.newEvent(evName); 450 451 evService.fireEvent(event); 452 453 return evContext; 454 } catch (UserRegistrationException ue) { 455 log.warn("Error during event processing", ue); 456 throw ue; 457 } 458 459 } 460 461 protected void sendValidationEmail(Map<String, Serializable> additionnalInfo, DocumentModel registrationDoc) { 462 UserRegistrationConfiguration configuration = getConfiguration(registrationDoc); 463 sendEmail(additionnalInfo, registrationDoc, configuration.getValidationEmailTemplate(), 464 configuration.getValidationEmailTitle()); 465 } 466 467 protected void sendEmail(Map<String, Serializable> additionnalInfo, DocumentModel registrationDoc, 468 String emailTemplatePath, String emailTitle) { 469 UserRegistrationConfiguration configuration = getConfiguration(registrationDoc); 470 471 String emailAdress = (String) registrationDoc.getPropertyValue(configuration.getUserInfoEmailField()); 472 473 Map<String, Serializable> input = new HashMap<>(); 474 Map<String, Serializable> userinfo = new HashMap<>(); 475 userinfo.put("firstName", registrationDoc.getPropertyValue(configuration.getUserInfoFirstnameField())); 476 userinfo.put("lastName", registrationDoc.getPropertyValue(configuration.getUserInfoLastnameField())); 477 userinfo.put("login", registrationDoc.getPropertyValue(configuration.getUserInfoUsernameField())); 478 userinfo.put("id", registrationDoc.getId()); 479 480 String documentTitle = ""; 481 482 if (registrationDoc.hasSchema("docinfo")) { 483 documentTitle = (String) registrationDoc.getPropertyValue("docinfo:documentTitle"); 484 } 485 input.put("documentTitle", documentTitle); 486 input.put("configurationName", configuration.getName()); 487 input.put("comment", registrationDoc.getPropertyValue("registration:comment")); 488 input.put(UserInvitationService.REGISTRATION_CONFIGURATION_NAME, configuration.getName()); 489 input.put("userinfo", (Serializable) userinfo); 490 input.put("info", (Serializable) additionnalInfo); 491 input.put("userAlreadyExists", checkUserFromRegistrationExistence(registrationDoc)); 492 input.put("productName", Framework.getProperty("org.nuxeo.ecm.product.name")); 493 StringWriter writer = new StringWriter(); 494 495 try { 496 rh.getRenderingEngine().render(emailTemplatePath, input, writer); 497 } catch (RenderingException e) { 498 throw new NuxeoException("Error during rendering email", e); 499 } 500 501 // render custom email subject 502 emailTitle = renderSubjectTemplate(emailTitle, input); 503 504 String body = writer.getBuffer().toString(); 505 String copyTo = (String) registrationDoc.getPropertyValue("registration:copyTo"); 506 if (!isTestModeSet()) { 507 try { 508 generateMail(emailAdress, copyTo, emailTitle, body); 509 } catch (NamingException | MessagingException e) { 510 throw new NuxeoException("Error while sending mail: ", e); 511 } 512 } else { 513 testRendering = body; 514 } 515 } 516 517 private String renderSubjectTemplate(String emailTitle, Map<String, Serializable> input) { 518 Configuration stringCfg = rh.getEngineConfiguration(); 519 Writer out; 520 try { 521 Template templ = new Template("subjectTemplate", new StringReader(emailTitle), stringCfg); 522 out = new StringWriter(); 523 templ.process(input, out); 524 out.flush(); 525 } catch (IOException | TemplateException e) { 526 throw new NuxeoException("Error while rendering email subject: ", e); 527 } 528 return out.toString(); 529 } 530 531 protected static boolean isTestModeSet() { 532 return Framework.isTestModeSet() || !isBlank(Framework.getProperty("org.nuxeo.ecm.tester.name")); 533 } 534 535 protected boolean checkUserFromRegistrationExistence(DocumentModel registrationDoc) { 536 UserRegistrationConfiguration configuration = getConfiguration(registrationDoc); 537 return null != Framework.getService(UserManager.class).getPrincipal( 538 (String) registrationDoc.getPropertyValue(configuration.getUserInfoUsernameField())); 539 } 540 541 protected void generateMail(String destination, String copy, String title, String content) 542 throws NamingException, MessagingException { 543 544 InitialContext ic = new InitialContext(); 545 Session session = (Session) ic.lookup(getJavaMailJndiName()); 546 547 MimeMessage msg = new MimeMessage(session); 548 msg.setFrom(new InternetAddress(session.getProperty("mail.from"))); 549 msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(destination, false)); 550 if (!isBlank(copy)) { 551 msg.addRecipient(Message.RecipientType.CC, new InternetAddress(copy, false)); 552 } 553 554 msg.setSubject(title, "UTF-8"); 555 msg.setSentDate(new Date()); 556 msg.setContent(content, "text/html; charset=utf-8"); 557 558 Transport.send(msg); 559 } 560 561 @Override 562 public String submitRegistrationRequest(DocumentModel userRegistrationModel, 563 Map<String, Serializable> additionnalInfo, ValidationMethod validationMethod, boolean autoAccept) { 564 return submitRegistrationRequest(DEFAULT_CONFIGURATION_NAME, userRegistrationModel, additionnalInfo, 565 validationMethod, autoAccept); 566 } 567 568 @Override 569 public DocumentModelList getRegistrationsForUser(final String docId, final String username, 570 final String configurationName) { 571 final DocumentModelList registrationDocs = new DocumentModelListImpl(); 572 new UnrestrictedSessionRunner(getTargetRepositoryName()) { 573 @Override 574 public void run() { 575 String query = "SELECT * FROM Document WHERE ecm:currentLifeCycleState != 'validated' AND" 576 + " ecm:mixinType = '" + getConfiguration(configurationName).getRequestDocType() 577 + "' AND docinfo:documentId = '%s' AND" 578 + getConfiguration(configurationName).getUserInfoUsernameField() 579 + " = '%s' AND ecm:isVersion = 0"; 580 query = String.format(query, docId, username); 581 registrationDocs.addAll(session.query(query)); 582 } 583 }.runUnrestricted(); 584 return registrationDocs; 585 } 586 587 protected static boolean isEmailExist(UserRegistrationConfiguration configuration, DocumentModel userRegistration) { 588 String email = (String) userRegistration.getPropertyValue(configuration.getUserInfoEmailField()); 589 if (isBlank(email)) { 590 return false; 591 } 592 593 Map<String, Serializable> filter = new HashMap<>(1); 594 filter.put(UserConfig.EMAIL_COLUMN, email); 595 596 DocumentModelList users = Framework.getService(UserManager.class).searchUsers(filter, null); 597 return !users.isEmpty(); 598 } 599 600 @Override 601 public String submitRegistrationRequest(String configurationName, DocumentModel userRegistrationModel, 602 Map<String, Serializable> additionnalInfo, ValidationMethod validationMethod, boolean autoAccept) { 603 604 // First check that we have the originating user for that request 605 if (StringUtils.isBlank((String)additionnalInfo.get(PARAM_ORIGINATING_USER))) { 606 throw new IllegalArgumentException("Originating user should be provided in a registration request"); 607 } 608 RegistrationCreator creator = new RegistrationCreator(configurationName, userRegistrationModel, additionnalInfo, 609 validationMethod); 610 creator.runUnrestricted(); 611 String registrationUuid = creator.getRegistrationUuid(); 612 613 UserRegistrationConfiguration currentConfig = getConfiguration(configurationName); 614 boolean userAlreadyExists = null != Framework.getService(UserManager.class).getPrincipal( 615 (String) userRegistrationModel.getPropertyValue(currentConfig.getUserInfoUsernameField())); 616 617 if (!userAlreadyExists && isEmailExist(currentConfig, userRegistrationModel)) { 618 log.info("Trying to submit a registration from an existing email with a different username."); 619 throw new UserAlreadyExistsException(); 620 } 621 622 // Directly accept registration if the configuration allow it and the 623 // user already exists 624 RegistrationRules registrationRules = getRegistrationRules(configurationName); 625 boolean byPassAdminValidation = autoAccept; 626 byPassAdminValidation |= userAlreadyExists && registrationRules.allowDirectValidationForExistingUser(); 627 byPassAdminValidation |= !userAlreadyExists && registrationRules.allowDirectValidationForNonExistingUser(); 628 if (byPassAdminValidation) { 629 // Build validationBaseUrl with nuxeo.url property as request is not 630 // accessible. 631 if (!additionnalInfo.containsKey("enterPasswordUrl")) { 632 additionnalInfo.put("enterPasswordUrl", buildEnterPasswordUrl(currentConfig)); 633 } 634 acceptRegistrationRequest(registrationUuid, additionnalInfo); 635 } 636 return registrationUuid; 637 } 638 639 protected String buildEnterPasswordUrl(UserRegistrationConfiguration configuration) { 640 String baseUrl = Framework.getProperty(NUXEO_URL_KEY); 641 642 baseUrl = isBlank(baseUrl) ? "/" : baseUrl; 643 if (!baseUrl.endsWith("/")) { 644 baseUrl += "/"; 645 } 646 return baseUrl.concat(configuration.getEnterPasswordUrl()); 647 } 648 649 @Override 650 public void acceptRegistrationRequest(String requestId, Map<String, Serializable> additionnalInfo) 651 throws UserRegistrationException { 652 RegistrationApprover acceptor = new RegistrationApprover(requestId, additionnalInfo); 653 acceptor.runUnrestricted(); 654 655 } 656 657 @Override 658 public void rejectRegistrationRequest(String requestId, Map<String, Serializable> additionnalInfo) 659 throws UserRegistrationException { 660 661 RegistrationRejector rejector = new RegistrationRejector(requestId, additionnalInfo); 662 rejector.runUnrestricted(); 663 664 } 665 666 @Override 667 public Map<String, Serializable> validateRegistration(String requestId, Map<String, Serializable> additionnalInfo) 668 throws UserRegistrationException { 669 RegistrationAcceptator validator = new RegistrationAcceptator(requestId, additionnalInfo); 670 validator.runUnrestricted(); 671 return validator.getRegistrationData(); 672 } 673 674 @Override 675 public Map<String, Serializable> validateRegistrationAndSendEmail(String requestId, 676 Map<String, Serializable> additionnalInfo) throws UserRegistrationException { 677 678 Map<String, Serializable> registrationInfo = validateRegistration(requestId, additionnalInfo); 679 680 Map<String, Serializable> input = new HashMap<>(); 681 input.putAll(registrationInfo); 682 input.put("info", (Serializable) additionnalInfo); 683 StringWriter writer = new StringWriter(); 684 685 UserRegistrationConfiguration configuration = getConfiguration( 686 (DocumentModel) registrationInfo.get(REGISTRATION_DATA_DOC)); 687 try { 688 rh.getRenderingEngine().render(configuration.getSuccessEmailTemplate(), input, writer); 689 } catch (RenderingException e) { 690 throw new NuxeoException("Error during rendering email", e); 691 } 692 693 String emailAdress = ((NuxeoPrincipalImpl) registrationInfo.get("registeredUser")).getEmail(); 694 String body = writer.getBuffer().toString(); 695 String title = configuration.getValidationEmailTitle(); 696 if (!Framework.isTestModeSet()) { 697 try { 698 generateMail(emailAdress, null, title, body); 699 } catch (NamingException | MessagingException e) { 700 throw new NuxeoException("Error while sending mail : ", e); 701 } 702 } else { 703 testRendering = body; 704 } 705 706 return registrationInfo; 707 } 708 709 @Override 710 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 711 if ("configuration".equals(extensionPoint)) { 712 UserRegistrationConfiguration newConfig = (UserRegistrationConfiguration) contribution; 713 714 if (configurations.containsKey(newConfig.getName())) { 715 if (newConfig.isMerge()) { 716 configurations.get(newConfig.getName()).mergeWith(newConfig); 717 } else if (newConfig.isRemove()) { 718 configurations.remove(newConfig.getName()); 719 } else { 720 log.warn( 721 "Trying to register an existing userRegistration configuration without removing or merging it, in: " 722 + contributor.getName()); 723 } 724 } else { 725 configurations.put(newConfig.getName(), newConfig); 726 } 727 } 728 } 729 730 protected InvitationUserFactory getRegistrationUserFactory(UserRegistrationConfiguration configuration) { 731 InvitationUserFactory factory = null; 732 Class<? extends InvitationUserFactory> factoryClass = configuration.getRegistrationUserFactory(); 733 if (factoryClass != null) { 734 try { 735 factory = factoryClass.getConstructor().newInstance(); 736 } catch (ReflectiveOperationException e) { 737 log.warn("Failed to instanciate RegistrationUserFactory", e); 738 } 739 } 740 if (factory == null) { 741 factory = new DefaultInvitationUserFactory(); 742 } 743 return factory; 744 } 745 746 @Override 747 public NuxeoPrincipal createUser(CoreSession session, DocumentModel registrationDoc) 748 throws UserRegistrationException { 749 UserRegistrationConfiguration configuration = getConfiguration(registrationDoc); 750 return getRegistrationUserFactory(configuration).doCreateUser(session, registrationDoc, configuration); 751 } 752 753 protected class RootDocumentGetter extends UnrestrictedSessionRunner { 754 755 protected DocumentModel doc; 756 757 protected String configurationName; 758 759 protected RootDocumentGetter(String configurationName) { 760 super(getTargetRepositoryName()); 761 this.configurationName = configurationName; 762 } 763 764 @Override 765 public void run() { 766 doc = getOrCreateRootDocument(session, configurationName); 767 doc.detach(true); 768 } 769 770 public DocumentModel getDoc() { 771 return doc; 772 } 773 } 774 775 @Override 776 public UserRegistrationConfiguration getConfiguration() { 777 return getConfiguration(DEFAULT_CONFIGURATION_NAME); 778 } 779 780 @Override 781 public UserRegistrationConfiguration getConfiguration(DocumentModel requestDoc) { 782 try { 783 DocumentModel parent = requestDoc.getCoreSession().getDocument(requestDoc.getParentRef()); 784 String configurationName = DEFAULT_CONFIGURATION_NAME; 785 if (parent.hasFacet(FACET_REGISTRATION_CONFIGURATION)) { 786 configurationName = (String) parent.getPropertyValue(FIELD_CONFIGURATION_NAME); 787 } else if (requestDoc.hasFacet(FACET_REGISTRATION_CONFIGURATION)) { 788 configurationName = (String) requestDoc.getPropertyValue(FIELD_CONFIGURATION_NAME); 789 } 790 791 if (!configurations.containsKey(configurationName)) { 792 throw new NuxeoException("Configuration " + configurationName + " is not registered"); 793 } 794 return configurations.get(configurationName); 795 } catch (NuxeoException e) { 796 log.info("Unable to get request parent document: " + e.getMessage()); 797 throw e; 798 } 799 } 800 801 @Override 802 public UserRegistrationConfiguration getConfiguration(String name) { 803 if (!configurations.containsKey(name)) { 804 throw new NuxeoException("Trying to get unknown user registration configuration."); 805 } 806 return configurations.get(name); 807 } 808 809 @Override 810 public RegistrationRules getRegistrationRules(String configurationName) { 811 RootDocumentGetter rdg = new RootDocumentGetter(configurationName); 812 rdg.runUnrestricted(); 813 return rdg.getDoc().getAdapter(RegistrationRules.class); 814 } 815 816 @Override 817 public void reviveRegistrationRequests(CoreSession session, List<DocumentModel> registrationDocs) { 818 for (DocumentModel registrationDoc : registrationDocs) { 819 reviveRegistrationRequest(session, registrationDoc, new HashMap<>()); 820 } 821 } 822 823 protected void reviveRegistrationRequest(CoreSession session, DocumentModel registrationDoc, 824 Map<String, Serializable> additionalInfos) { 825 UserRegistrationConfiguration configuration = getConfiguration(registrationDoc); 826 // Build validationBaseUrl with nuxeo.url property as request is not 827 // accessible. 828 if (!additionalInfos.containsKey("enterPasswordUrl")) { 829 additionalInfos.put("enterPasswordUrl", buildEnterPasswordUrl(configuration)); 830 } 831 sendEmail(additionalInfos, registrationDoc, configuration.getReviveEmailTemplate(), 832 configuration.getReviveEmailTitle()); 833 } 834 835 @Override 836 public void deleteRegistrationRequests(CoreSession session, List<DocumentModel> registrationDocs) { 837 for (DocumentModel registration : registrationDocs) { 838 UserRegistrationConfiguration configuration = getConfiguration(registration); 839 if (!registration.hasSchema(configuration.getUserInfoSchemaName())) { 840 throw new NuxeoException("Registration document do not contains needed schema"); 841 } 842 843 session.removeDocument(registration.getRef()); 844 } 845 } 846 847 @Override 848 public Set<String> getConfigurationsName() { 849 return configurations.keySet(); 850 } 851 852 @Override 853 public void checkRequestId(final String requestId) throws UserRegistrationException { 854 RequestIdValidator runner = new RequestIdValidator(requestId); 855 runner.runUnrestricted(); 856 } 857 858 @Override 859 public String getNameEventRegistrationSubmitted() { 860 return INVITATION_SUBMITTED_EVENT; 861 } 862 863 @Override 864 public String getNameEventRegistrationAccepted() { 865 return INVITATION_ACCEPTED_EVENT; 866 } 867 868 @Override 869 public String getNameEventRegistrationRejected() { 870 return INVITATION_REJECTED_EVENT; 871 } 872 873 @Override 874 public String getNameEventRegistrationValidated() { 875 return INVITATION_VALIDATED_EVENT; 876 } 877 878}