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.common.utils.IdUtils;
052import org.nuxeo.ecm.core.api.CoreSession;
053import org.nuxeo.ecm.core.api.DocumentModel;
054import org.nuxeo.ecm.core.api.DocumentModelList;
055import org.nuxeo.ecm.core.api.DocumentRef;
056import org.nuxeo.ecm.core.api.IdRef;
057import org.nuxeo.ecm.core.api.NuxeoException;
058import org.nuxeo.ecm.core.api.NuxeoPrincipal;
059import org.nuxeo.ecm.core.api.PathRef;
060import org.nuxeo.ecm.core.api.PropertyException;
061import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
062import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
063import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
064import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService;
065import org.nuxeo.ecm.core.api.repository.RepositoryManager;
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
079public class UserInvitationComponent extends DefaultComponent implements UserInvitationService {
080
081    protected static Log log = LogFactory.getLog(UserInvitationService.class);
082
083    public static final String NUXEO_URL_KEY = "nuxeo.url";
084
085    protected String repoName = null;
086
087    protected String testRendering = null;
088
089    protected RenderingHelper rh = new RenderingHelper();
090
091    protected Map<String, UserRegistrationConfiguration> configurations = new HashMap<String, UserRegistrationConfiguration>();
092
093    private static final String INVITATION_SUBMITTED_EVENT = "invitationSubmitted";
094
095    private static final String INVITATION_ACCEPTED_EVENT = "invitationAccepted";
096
097    private static final String INVITATION_REJECTED_EVENT = "invitationRejected";
098
099    private static final String INVITATION_VALIDATED_EVENT = "invitationValidated";
100
101    public String getTestedRendering() {
102        return testRendering;
103    }
104
105    protected String getTargetRepositoryName() {
106        if (repoName == null) {
107            RepositoryManager rm = Framework.getService(RepositoryManager.class);
108            repoName = rm.getDefaultRepositoryName();
109        }
110        return repoName;
111    }
112
113    protected boolean userAlreadyExists(UserRegistrationInfo userRegistrationInfo) {
114        DocumentModel user = Framework.getLocalService(UserManager.class).getUserModel(userRegistrationInfo.getLogin());
115        return user != null;
116    }
117
118    protected String getJavaMailJndiName() {
119        return Framework.getProperty("jndi.java.mail", "java:/Mail");
120    }
121
122    @Override
123    public DocumentModel getUserRegistrationModel(String configurationName) {
124        // Test if the configuration is defined
125        if (StringUtils.isEmpty(configurationName)) {
126            configurationName = DEFAULT_CONFIGURATION_NAME;
127        }
128        // Get the DocumentModel for the doctype defined in the configuration
129        UserRegistrationModelCreator creator = new UserRegistrationModelCreator(configurationName);
130        creator.runUnrestricted();
131        return creator.getUserRegistrationModel();
132    }
133
134    @Override
135    public DocumentModel getRegistrationRulesDocument(CoreSession session, String configurationName)
136            {
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.getLocalService(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        protected String uuid;
256
257        protected Map<String, Serializable> additionnalInfo;
258
259        public RegistrationApprover(String registrationUuid, Map<String, Serializable> additionnalInfo) {
260            super(getTargetRepositoryName());
261            uuid = registrationUuid;
262            this.additionnalInfo = additionnalInfo;
263        }
264
265        @Override
266        public void run() {
267
268            DocumentModel doc = session.getDocument(new IdRef(uuid));
269            String validationMethod = (String) doc.getPropertyValue("registration:validationMethod");
270
271            // test Validation Method
272            if (StringUtils.equals(EMAIL.toString(), validationMethod)) {
273                sendValidationEmail(additionnalInfo, doc);
274            }
275
276            doc.setPropertyValue("registration:accepted", true);
277            if (doc.getAllowedStateTransitions().contains("approve")) {
278                doc.followTransition("approve");
279            }
280            doc = session.saveDocument(doc);
281            session.save();
282
283            sendEvent(session, doc, getNameEventRegistrationAccepted());
284        }
285    }
286
287    protected class RegistrationRejector extends UnrestrictedSessionRunner {
288
289        protected String uuid;
290
291        protected Map<String, Serializable> additionnalInfo;
292
293        public RegistrationRejector(String registrationUuid, Map<String, Serializable> additionnalInfo) {
294            super(getTargetRepositoryName());
295            uuid = registrationUuid;
296            this.additionnalInfo = additionnalInfo;
297        }
298
299        @Override
300        public void run() {
301
302            DocumentModel doc = session.getDocument(new IdRef(uuid));
303
304            doc.setPropertyValue("registration:accepted", false);
305            if (doc.getAllowedStateTransitions().contains("reject")) {
306                doc.followTransition("reject");
307            }
308            doc = session.saveDocument(doc);
309            session.save();
310
311            sendEvent(session, doc, getNameEventRegistrationRejected());
312        }
313    }
314
315    protected class RegistrationAcceptator extends UnrestrictedSessionRunner {
316
317        protected String uuid;
318
319        protected Map<String, Serializable> registrationData = new HashMap<String, Serializable>();
320
321        protected Map<String, Serializable> additionnalInfo;
322
323        public RegistrationAcceptator(String uuid, Map<String, Serializable> additionnalInfo) {
324            super(getTargetRepositoryName());
325            this.uuid = uuid;
326            this.additionnalInfo = additionnalInfo;
327        }
328
329        public Map<String, Serializable> getRegistrationData() {
330            return registrationData;
331        }
332
333        @Override
334        public void run() {
335            DocumentRef idRef = new IdRef(uuid);
336
337            DocumentModel registrationDoc = session.getDocument(idRef);
338
339            // additionnal infos
340            for (String key : additionnalInfo.keySet()) {
341                try {
342                    if (DefaultInvitationUserFactory.PASSWORD_KEY.equals(key)) {
343                        // add the password as a transient context data
344                        registrationDoc.putContextData(DefaultInvitationUserFactory.PASSWORD_KEY,
345                                additionnalInfo.get(key));
346                    } else {
347                        registrationDoc.setPropertyValue(key, additionnalInfo.get(key));
348                    }
349                } catch (PropertyException e) {
350                    // skip silently
351                }
352            }
353
354            if (registrationDoc.getLifeCyclePolicy().equals("registrationRequest")) {
355                if (registrationDoc.getCurrentLifeCycleState().equals("approved")) {
356                    registrationDoc.followTransition("accept");
357                } else {
358                    if (registrationDoc.getCurrentLifeCycleState().equals("accepted")) {
359                        throw new AlreadyProcessedRegistrationException(
360                                "Registration request has already been processed.");
361                    } else {
362                        throw new UserRegistrationException("Registration request has not been accepted yet.");
363                    }
364                }
365            }
366
367            session.saveDocument(registrationDoc);
368            session.save();
369            EventContext evContext = sendEvent(session, registrationDoc, getNameEventRegistrationValidated());
370
371            ((DocumentModelImpl) registrationDoc).detach(sessionIsAlreadyUnrestricted);
372            registrationData.put(REGISTRATION_DATA_DOC, registrationDoc);
373            registrationData.put(REGISTRATION_DATA_USER, evContext.getProperty("registeredUser"));
374        }
375
376    }
377
378    protected class RequestIdValidator extends UnrestrictedSessionRunner {
379
380        protected String uuid;
381
382        public RequestIdValidator(String uuid) {
383            super(getTargetRepositoryName());
384            this.uuid = uuid;
385        }
386
387        @Override
388        public void run() {
389            DocumentRef idRef = new IdRef(uuid);
390            // Check if the id matches an existing document
391            if (!session.exists(idRef)) {
392                throw new UserRegistrationException("There is no existing registration request with id " + uuid);
393            }
394
395            // Check if the request has not been already validated
396            DocumentModel registrationDoc = session.getDocument(idRef);
397            if (registrationDoc.getCurrentLifeCycleState().equals("accepted")) {
398                throw new AlreadyProcessedRegistrationException("Registration request has already been processed.");
399            }
400        }
401    }
402
403    protected EventContext sendEvent(CoreSession session, DocumentModel source, String evName)
404            throws UserRegistrationException {
405        try {
406            EventService evService = Framework.getService(EventService.class);
407            EventContext evContext = new DocumentEventContext(session, session.getPrincipal(), source);
408
409            Event event = evContext.newEvent(evName);
410
411            evService.fireEvent(event);
412
413            return evContext;
414        } catch (UserRegistrationException ue) {
415            log.warn("Error during event processing", ue);
416            throw ue;
417        }
418
419    }
420
421    protected void sendValidationEmail(Map<String, Serializable> additionnalInfo, DocumentModel registrationDoc)
422            {
423        UserRegistrationConfiguration configuration = getConfiguration(registrationDoc);
424        sendEmail(additionnalInfo, registrationDoc, configuration.getValidationEmailTemplate(),
425                configuration.getValidationEmailTitle());
426    }
427
428    protected void sendEmail(Map<String, Serializable> additionnalInfo, DocumentModel registrationDoc,
429            String emailTemplatePath, String emailTitle) {
430        UserRegistrationConfiguration configuration = getConfiguration(registrationDoc);
431
432        String emailAdress = (String) registrationDoc.getPropertyValue(configuration.getUserInfoEmailField());
433
434        Map<String, Serializable> input = new HashMap<String, Serializable>();
435        Map<String, Serializable> userinfo = new HashMap<String, Serializable>();
436        userinfo.put("firstName", registrationDoc.getPropertyValue(configuration.getUserInfoFirstnameField()));
437        userinfo.put("lastName", registrationDoc.getPropertyValue(configuration.getUserInfoLastnameField()));
438        userinfo.put("login", registrationDoc.getPropertyValue(configuration.getUserInfoUsernameField()));
439        userinfo.put("id", registrationDoc.getId());
440
441        String documentTitle = "";
442
443        if (registrationDoc.hasSchema("docinfo")) {
444            documentTitle = (String) registrationDoc.getPropertyValue("docinfo:documentTitle");
445        }
446        input.put("documentTitle", documentTitle);
447        input.put("configurationName", configuration.getName());
448        input.put("comment", registrationDoc.getPropertyValue("registration:comment"));
449        input.put(UserInvitationService.REGISTRATION_CONFIGURATION_NAME, configuration.getName());
450        input.put("userinfo", (Serializable) userinfo);
451        input.put("info", (Serializable) additionnalInfo);
452        input.put("userAlreadyExists", checkUserFromRegistrationExistence(registrationDoc));
453        input.put("productName", Framework.getProperty("org.nuxeo.ecm.product.name"));
454        StringWriter writer = new StringWriter();
455
456        try {
457            rh.getRenderingEngine().render(emailTemplatePath, input, writer);
458        } catch (RenderingException e) {
459            throw new NuxeoException("Error during rendering email", e);
460        }
461
462        // render custom email subject
463        emailTitle = renderSubjectTemplate(emailTitle, input);
464
465        String body = writer.getBuffer().toString();
466        String copyTo = (String) registrationDoc.getPropertyValue("registration:copyTo");
467        if (!isTestModeSet()) {
468            try {
469                generateMail(emailAdress, copyTo, emailTitle, body);
470            } catch (NamingException | MessagingException e) {
471                throw new NuxeoException("Error while sending mail: ", e);
472            }
473        } else {
474            testRendering = body;
475        }
476    }
477
478    private String renderSubjectTemplate(String emailTitle, Map<String, Serializable> input) {
479        Configuration stringCfg = rh.getEngineConfiguration();
480        Writer out;
481        try {
482            Template templ = new Template("subjectTemplate", new StringReader(emailTitle), stringCfg);
483            out = new StringWriter();
484            templ.process(input, out);
485            out.flush();
486        } catch (IOException | TemplateException e) {
487            throw new NuxeoException("Error while rendering email subject: ", e);
488        }
489        return out.toString();
490    }
491
492    protected static boolean isTestModeSet() {
493        return Framework.isTestModeSet() || !isBlank(Framework.getProperty("org.nuxeo.ecm.tester.name"));
494    }
495
496    protected boolean checkUserFromRegistrationExistence(DocumentModel registrationDoc) {
497        UserRegistrationConfiguration configuration = getConfiguration(registrationDoc);
498        return null != Framework.getLocalService(UserManager.class).getPrincipal(
499                (String) registrationDoc.getPropertyValue(configuration.getUserInfoUsernameField()));
500    }
501
502    protected void generateMail(String destination, String copy, String title, String content) throws NamingException,
503            MessagingException {
504
505        InitialContext ic = new InitialContext();
506        Session session = (Session) ic.lookup(getJavaMailJndiName());
507
508        MimeMessage msg = new MimeMessage(session);
509        msg.setFrom(new InternetAddress(session.getProperty("mail.from")));
510        msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(destination, false));
511        if (!isBlank(copy)) {
512            msg.addRecipient(Message.RecipientType.CC, new InternetAddress(copy, false));
513        }
514
515        msg.setSubject(title, "UTF-8");
516        msg.setSentDate(new Date());
517        msg.setContent(content, "text/html; charset=utf-8");
518
519        Transport.send(msg);
520    }
521
522    @Override
523    public String submitRegistrationRequest(DocumentModel userRegistrationModel,
524            Map<String, Serializable> additionnalInfo, ValidationMethod validationMethod, boolean autoAccept)
525            {
526        return submitRegistrationRequest(DEFAULT_CONFIGURATION_NAME, userRegistrationModel, additionnalInfo,
527                validationMethod, autoAccept);
528    }
529
530    @Override
531    public DocumentModelList getRegistrationsForUser(final String docId, final String username,
532            final String configurationName) {
533        final DocumentModelList registrationDocs = new DocumentModelListImpl();
534        new UnrestrictedSessionRunner(getTargetRepositoryName()) {
535            @Override
536            public void run() {
537                String query = "SELECT * FROM Document WHERE ecm:currentLifeCycleState != 'validated' AND"
538                        + " ecm:mixinType = '" + getConfiguration(configurationName).getRequestDocType()
539                        + "' AND docinfo:documentId = '%s' AND"
540                        + getConfiguration(configurationName).getUserInfoUsernameField()
541                        + " = '%s' AND ecm:isCheckedInVersion = 0";
542                query = String.format(query, docId, username);
543                registrationDocs.addAll(session.query(query));
544            }
545        }.runUnrestricted();
546        return registrationDocs;
547    }
548
549    protected static boolean isEmailExist(UserRegistrationConfiguration configuration, DocumentModel userRegistration) {
550        String email = (String) userRegistration.getPropertyValue(configuration.getUserInfoEmailField());
551        if (isBlank(email)) {
552            return false;
553        }
554
555        Map<String, Serializable> filter = new HashMap<>(1);
556        filter.put(UserConfig.EMAIL_COLUMN, email);
557
558        DocumentModelList users = Framework.getLocalService(UserManager.class).searchUsers(filter, null);
559        return !users.isEmpty();
560    }
561
562    @Override
563    public String submitRegistrationRequest(String configurationName, DocumentModel userRegistrationModel,
564            Map<String, Serializable> additionnalInfo, ValidationMethod validationMethod, boolean autoAccept)
565            {
566        RegistrationCreator creator = new RegistrationCreator(configurationName, userRegistrationModel,
567                additionnalInfo, validationMethod);
568        creator.runUnrestricted();
569        String registrationUuid = creator.getRegistrationUuid();
570
571        UserRegistrationConfiguration currentConfig = getConfiguration(configurationName);
572        boolean userAlreadyExists = null != Framework.getLocalService(UserManager.class).getPrincipal(
573                (String) userRegistrationModel.getPropertyValue(currentConfig.getUserInfoUsernameField()));
574
575        if (!userAlreadyExists && isEmailExist(currentConfig, userRegistrationModel)) {
576            log.info("Trying to submit a registration from an existing email with a different username.");
577            throw new UserAlreadyExistsException();
578        }
579
580        // Directly accept registration if the configuration allow it and the
581        // user already exists
582        RegistrationRules registrationRules = getRegistrationRules(configurationName);
583        boolean byPassAdminValidation = autoAccept;
584        byPassAdminValidation |= userAlreadyExists && registrationRules.allowDirectValidationForExistingUser();
585        byPassAdminValidation |= registrationRules.allowDirectValidationForExistingUser()
586                && registrationRules.allowDirectValidationForNonExistingUser();
587        if (byPassAdminValidation) {
588            // Build validationBaseUrl with nuxeo.url property as request is not
589            // accessible.
590            if (!additionnalInfo.containsKey("enterPasswordUrl")) {
591                additionnalInfo.put("enterPasswordUrl", buildEnterPasswordUrl(currentConfig));
592            }
593            acceptRegistrationRequest(registrationUuid, additionnalInfo);
594        }
595        return registrationUuid;
596    }
597
598    protected String buildEnterPasswordUrl(UserRegistrationConfiguration configuration) {
599        String baseUrl = Framework.getProperty(NUXEO_URL_KEY);
600
601        baseUrl = isBlank(baseUrl) ? "/" : baseUrl;
602        if (!baseUrl.endsWith("/")) {
603            baseUrl += "/";
604        }
605        return baseUrl.concat(configuration.getEnterPasswordUrl());
606    }
607
608    @Override
609    public void acceptRegistrationRequest(String requestId, Map<String, Serializable> additionnalInfo)
610            throws UserRegistrationException {
611        RegistrationApprover acceptor = new RegistrationApprover(requestId, additionnalInfo);
612        acceptor.runUnrestricted();
613
614    }
615
616    @Override
617    public void rejectRegistrationRequest(String requestId, Map<String, Serializable> additionnalInfo)
618            throws UserRegistrationException {
619
620        RegistrationRejector rejector = new RegistrationRejector(requestId, additionnalInfo);
621        rejector.runUnrestricted();
622
623    }
624
625    @Override
626    public Map<String, Serializable> validateRegistration(String requestId, Map<String, Serializable> additionnalInfo)
627            throws UserRegistrationException {
628        RegistrationAcceptator validator = new RegistrationAcceptator(requestId, additionnalInfo);
629        validator.runUnrestricted();
630        return validator.getRegistrationData();
631    }
632
633    @Override
634    public Map<String, Serializable> validateRegistrationAndSendEmail(String requestId,
635            Map<String, Serializable> additionnalInfo) throws UserRegistrationException {
636
637        Map<String, Serializable> registrationInfo = validateRegistration(requestId, additionnalInfo);
638
639        Map<String, Serializable> input = new HashMap<String, Serializable>();
640        input.putAll(registrationInfo);
641        input.put("info", (Serializable) additionnalInfo);
642        StringWriter writer = new StringWriter();
643
644        UserRegistrationConfiguration configuration = getConfiguration((DocumentModel) registrationInfo.get(REGISTRATION_DATA_DOC));
645        try {
646            rh.getRenderingEngine().render(configuration.getSuccessEmailTemplate(), input, writer);
647        } catch (RenderingException e) {
648            throw new NuxeoException("Error during rendering email", e);
649        }
650
651        String emailAdress = ((NuxeoPrincipalImpl) registrationInfo.get("registeredUser")).getEmail();
652        String body = writer.getBuffer().toString();
653        String title = configuration.getValidationEmailTitle();
654        if (!Framework.isTestModeSet()) {
655            try {
656                generateMail(emailAdress, null, title, body);
657            } catch (NamingException | MessagingException e) {
658                throw new NuxeoException("Error while sending mail : ", e);
659            }
660        } else {
661            testRendering = body;
662        }
663
664        return registrationInfo;
665    }
666
667    @Override
668    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
669        if ("configuration".equals(extensionPoint)) {
670            UserRegistrationConfiguration newConfig = (UserRegistrationConfiguration) contribution;
671
672            if (configurations.containsKey(newConfig.getName())) {
673                if (newConfig.isMerge()) {
674                    configurations.get(newConfig.getName()).mergeWith(newConfig);
675                } else if (newConfig.isRemove()) {
676                    configurations.remove(newConfig.getName());
677                } else {
678                    log.warn("Trying to register an existing userRegistration configuration without removing or merging it, in: "
679                            + contributor.getName());
680                }
681            } else {
682                configurations.put(newConfig.getName(), newConfig);
683            }
684        }
685    }
686
687    protected InvitationUserFactory getRegistrationUserFactory(UserRegistrationConfiguration configuration) {
688        InvitationUserFactory factory = null;
689        Class<? extends InvitationUserFactory> factoryClass = configuration.getRegistrationUserFactory();
690        if (factoryClass != null) {
691            try {
692                factory = factoryClass.newInstance();
693            } catch (InstantiationException e) {
694                log.warn("Failed to instanciate RegistrationUserFactory", e);
695            } catch (IllegalAccessException e) {
696                log.warn("Failed to instanciate RegistrationUserFactory", e);
697            }
698        }
699        if (factory == null) {
700            factory = new DefaultInvitationUserFactory();
701        }
702        return factory;
703    }
704
705    @Override
706    public NuxeoPrincipal createUser(CoreSession session, DocumentModel registrationDoc) throws
707            UserRegistrationException {
708        UserRegistrationConfiguration configuration = getConfiguration(registrationDoc);
709        return getRegistrationUserFactory(configuration).doCreateUser(session, registrationDoc, configuration);
710    }
711
712    protected class RootDocumentGetter extends UnrestrictedSessionRunner {
713
714        protected DocumentModel doc;
715
716        protected String configurationName;
717
718        protected RootDocumentGetter(String configurationName) {
719            super(getTargetRepositoryName());
720            this.configurationName = configurationName;
721        }
722
723        @Override
724        public void run() {
725            doc = getOrCreateRootDocument(session, configurationName);
726            ((DocumentModelImpl) doc).detach(true);
727        }
728
729        public DocumentModel getDoc() {
730            return doc;
731        }
732    }
733
734    @Override
735    public UserRegistrationConfiguration getConfiguration() {
736        return getConfiguration(DEFAULT_CONFIGURATION_NAME);
737    }
738
739    @Override
740    public UserRegistrationConfiguration getConfiguration(DocumentModel requestDoc) {
741        try {
742            DocumentModel parent = requestDoc.getCoreSession().getDocument(requestDoc.getParentRef());
743            String configurationName = DEFAULT_CONFIGURATION_NAME;
744            if (parent.hasFacet(FACET_REGISTRATION_CONFIGURATION)) {
745                configurationName = (String) parent.getPropertyValue(FIELD_CONFIGURATION_NAME);
746            } else if (requestDoc.hasFacet(FACET_REGISTRATION_CONFIGURATION)) {
747                configurationName = (String) requestDoc.getPropertyValue(FIELD_CONFIGURATION_NAME);
748            }
749
750            if (!configurations.containsKey(configurationName)) {
751                throw new NuxeoException("Configuration " + configurationName + " is not registered");
752            }
753            return configurations.get(configurationName);
754        } catch (NuxeoException e) {
755            log.info("Unable to get request parent document: " + e.getMessage());
756            throw e;
757        }
758    }
759
760    @Override
761    public UserRegistrationConfiguration getConfiguration(String name) {
762        if (!configurations.containsKey(name)) {
763            throw new NuxeoException("Trying to get unknown user registration configuration.");
764        }
765        return configurations.get(name);
766    }
767
768    @Override
769    public RegistrationRules getRegistrationRules(String configurationName) {
770        RootDocumentGetter rdg = new RootDocumentGetter(configurationName);
771        rdg.runUnrestricted();
772        return rdg.getDoc().getAdapter(RegistrationRules.class);
773    }
774
775    @Override
776    public void reviveRegistrationRequests(CoreSession session, List<DocumentModel> registrationDocs)
777            {
778        for (DocumentModel registrationDoc : registrationDocs) {
779            reviveRegistrationRequest(session, registrationDoc, new HashMap<String, Serializable>());
780        }
781    }
782
783    protected void reviveRegistrationRequest(CoreSession session, DocumentModel registrationDoc,
784            Map<String, Serializable> additionalInfos) {
785        UserRegistrationConfiguration configuration = getConfiguration(registrationDoc);
786        // Build validationBaseUrl with nuxeo.url property as request is not
787        // accessible.
788        if (!additionalInfos.containsKey("enterPasswordUrl")) {
789            additionalInfos.put("enterPasswordUrl", buildEnterPasswordUrl(configuration));
790        }
791        sendEmail(additionalInfos, registrationDoc, configuration.getReviveEmailTemplate(),
792                configuration.getReviveEmailTitle());
793    }
794
795    @Override
796    public void deleteRegistrationRequests(CoreSession session, List<DocumentModel> registrationDocs)
797            {
798        for (DocumentModel registration : registrationDocs) {
799            UserRegistrationConfiguration configuration = getConfiguration(registration);
800            if (!registration.hasSchema(configuration.getUserInfoSchemaName())) {
801                throw new NuxeoException("Registration document do not contains needed schema");
802            }
803
804            session.removeDocument(registration.getRef());
805        }
806    }
807
808    @Override
809    public Set<String> getConfigurationsName() {
810        return configurations.keySet();
811    }
812
813    @Override
814    public void checkRequestId(final String requestId) throws UserRegistrationException {
815        RequestIdValidator runner = new RequestIdValidator(requestId);
816        runner.runUnrestricted();
817    }
818
819    @Override
820    public String getNameEventRegistrationSubmitted() {
821        return INVITATION_SUBMITTED_EVENT;
822    }
823
824    @Override
825    public String getNameEventRegistrationAccepted() {
826        return INVITATION_ACCEPTED_EVENT;
827    }
828
829    @Override
830    public String getNameEventRegistrationRejected() {
831        return INVITATION_REJECTED_EVENT;
832    }
833
834    @Override
835    public String getNameEventRegistrationValidated() {
836        return INVITATION_VALIDATED_EVENT;
837    }
838
839}