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