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}