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