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