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 freemarker.template.Configuration;
048import freemarker.template.Template;
049import freemarker.template.TemplateException;
050import org.apache.commons.lang.StringUtils;
051import org.apache.commons.logging.Log;
052import org.apache.commons.logging.LogFactory;
053import org.nuxeo.ecm.core.api.CoreSession;
054import org.nuxeo.ecm.core.api.DocumentModel;
055import org.nuxeo.ecm.core.api.DocumentModelList;
056import org.nuxeo.ecm.core.api.DocumentRef;
057import org.nuxeo.ecm.core.api.IdRef;
058import org.nuxeo.ecm.core.api.NuxeoException;
059import org.nuxeo.ecm.core.api.NuxeoPrincipal;
060import org.nuxeo.ecm.core.api.PathRef;
061import org.nuxeo.ecm.core.api.PropertyException;
062import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
063import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
064import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
065import org.nuxeo.ecm.core.api.pathsegment.PathSegmentService;
066import org.nuxeo.ecm.core.api.repository.RepositoryManager;
067import org.nuxeo.ecm.core.api.security.ACE;
068import org.nuxeo.ecm.core.api.security.ACL;
069import org.nuxeo.ecm.core.api.security.ACP;
070import org.nuxeo.ecm.core.event.Event;
071import org.nuxeo.ecm.core.event.EventContext;
072import org.nuxeo.ecm.core.event.EventService;
073import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
074import org.nuxeo.ecm.platform.rendering.api.RenderingException;
075import org.nuxeo.ecm.platform.usermanager.NuxeoPrincipalImpl;
076import org.nuxeo.ecm.platform.usermanager.UserConfig;
077import org.nuxeo.ecm.platform.usermanager.UserManager;
078import org.nuxeo.ecm.platform.usermanager.exceptions.UserAlreadyExistsException;
079import org.nuxeo.runtime.api.Framework;
080import org.nuxeo.runtime.model.ComponentInstance;
081import org.nuxeo.runtime.model.DefaultComponent;
082
083public class UserInvitationComponent extends DefaultComponent implements UserInvitationService {
084
085    protected static Log log = LogFactory.getLog(UserInvitationService.class);
086
087    public static final String NUXEO_URL_KEY = "nuxeo.url";
088
089    protected String repoName = null;
090
091    protected String testRendering = null;
092
093    protected RenderingHelper rh = new RenderingHelper();
094
095    protected Map<String, UserRegistrationConfiguration> configurations = new HashMap<String, UserRegistrationConfiguration>();
096
097    private static final String INVITATION_SUBMITTED_EVENT = "invitationSubmitted";
098
099    private static final String INVITATION_ACCEPTED_EVENT = "invitationAccepted";
100
101    private static final String INVITATION_REJECTED_EVENT = "invitationRejected";
102
103    private static final String INVITATION_VALIDATED_EVENT = "invitationValidated";
104
105    public String getTestedRendering() {
106        return testRendering;
107    }
108
109    protected String getTargetRepositoryName() {
110        if (repoName == null) {
111            RepositoryManager rm = Framework.getService(RepositoryManager.class);
112            repoName = rm.getDefaultRepositoryName();
113        }
114        return repoName;
115    }
116
117    protected boolean userAlreadyExists(UserRegistrationInfo userRegistrationInfo) {
118        DocumentModel user = Framework.getLocalService(UserManager.class).getUserModel(userRegistrationInfo.getLogin());
119        return user != null;
120    }
121
122    protected String getJavaMailJndiName() {
123        return Framework.getProperty("jndi.java.mail", "java:/Mail");
124    }
125
126    @Override
127    public DocumentModel getUserRegistrationModel(String configurationName) {
128        // Test if the configuration is defined
129        if (StringUtils.isEmpty(configurationName)) {
130            configurationName = DEFAULT_CONFIGURATION_NAME;
131        }
132        // Get the DocumentModel for the doctype defined in the configuration
133        UserRegistrationModelCreator creator = new UserRegistrationModelCreator(configurationName);
134        creator.runUnrestricted();
135        return creator.getUserRegistrationModel();
136    }
137
138    @Override
139    public DocumentModel getRegistrationRulesDocument(CoreSession session, String configurationName)
140            {
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.getLocalService(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.getLocalService(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                targetPrincipal = userManager.getPrincipal((String) doc.getPropertyValue("userinfo:email"));
281            }
282            if (targetPrincipal != null) {
283                DocumentModel target = session.getDocument(new IdRef(
284                        (String) doc.getPropertyValue("docinfo:documentId")));
285                ACP acp = target.getACP();
286                Map<String, Serializable> contextData = new HashMap<>();
287                contextData.put("notify", true);
288                contextData.put("comment", doc.getPropertyValue("registration:comment"));
289                acp.addACE(ACL.LOCAL_ACL,
290                        ACE.builder(targetPrincipal.getName(), (String) doc.getPropertyValue("docinfo:permission"))
291                           .creator((String) doc.getPropertyValue("docinfo:creator"))
292                           .contextData(contextData)
293                           .build());
294                target.setACP(acp, true);
295                // test Validation Method
296            } else if (StringUtils.equals(EMAIL.toString(), validationMethod)) {
297                sendValidationEmail(additionnalInfo, doc);
298            }
299
300            doc.setPropertyValue("registration:accepted", true);
301            if (doc.getAllowedStateTransitions().contains("approve")) {
302                doc.followTransition("approve");
303            }
304            doc = session.saveDocument(doc);
305            session.save();
306
307            sendEvent(session, doc, getNameEventRegistrationAccepted());
308        }
309    }
310
311    protected class RegistrationRejector extends UnrestrictedSessionRunner {
312
313        protected String uuid;
314
315        protected Map<String, Serializable> additionnalInfo;
316
317        public RegistrationRejector(String registrationUuid, Map<String, Serializable> additionnalInfo) {
318            super(getTargetRepositoryName());
319            uuid = registrationUuid;
320            this.additionnalInfo = additionnalInfo;
321        }
322
323        @Override
324        public void run() {
325
326            DocumentModel doc = session.getDocument(new IdRef(uuid));
327
328            doc.setPropertyValue("registration:accepted", false);
329            if (doc.getAllowedStateTransitions().contains("reject")) {
330                doc.followTransition("reject");
331            }
332            doc = session.saveDocument(doc);
333            session.save();
334
335            sendEvent(session, doc, getNameEventRegistrationRejected());
336        }
337    }
338
339    protected class RegistrationAcceptator extends UnrestrictedSessionRunner {
340
341        protected String uuid;
342
343        protected Map<String, Serializable> registrationData = new HashMap<String, Serializable>();
344
345        protected Map<String, Serializable> additionnalInfo;
346
347        public RegistrationAcceptator(String uuid, Map<String, Serializable> additionnalInfo) {
348            super(getTargetRepositoryName());
349            this.uuid = uuid;
350            this.additionnalInfo = additionnalInfo;
351        }
352
353        public Map<String, Serializable> getRegistrationData() {
354            return registrationData;
355        }
356
357        @Override
358        public void run() {
359            DocumentRef idRef = new IdRef(uuid);
360
361            DocumentModel registrationDoc = session.getDocument(idRef);
362
363            // additionnal infos
364            for (String key : additionnalInfo.keySet()) {
365                try {
366                    if (DefaultInvitationUserFactory.PASSWORD_KEY.equals(key)) {
367                        // add the password as a transient context data
368                        registrationDoc.putContextData(DefaultInvitationUserFactory.PASSWORD_KEY,
369                                additionnalInfo.get(key));
370                    } else {
371                        registrationDoc.setPropertyValue(key, additionnalInfo.get(key));
372                    }
373                } catch (PropertyException e) {
374                    // skip silently
375                }
376            }
377
378            if (registrationDoc.getLifeCyclePolicy().equals("registrationRequest")) {
379                if (registrationDoc.getCurrentLifeCycleState().equals("approved")) {
380                    registrationDoc.followTransition("accept");
381                } else {
382                    if (registrationDoc.getCurrentLifeCycleState().equals("accepted")) {
383                        throw new AlreadyProcessedRegistrationException(
384                                "Registration request has already been processed");
385                    } else {
386                        throw new UserRegistrationException("Registration request has not been accepted yet");
387                    }
388                }
389            }
390
391            session.saveDocument(registrationDoc);
392            session.save();
393            EventContext evContext = sendEvent(session, registrationDoc, getNameEventRegistrationValidated());
394
395            ((DocumentModelImpl) registrationDoc).detach(sessionIsAlreadyUnrestricted);
396            registrationData.put(REGISTRATION_DATA_DOC, registrationDoc);
397            registrationData.put(REGISTRATION_DATA_USER, evContext.getProperty("registeredUser"));
398        }
399
400    }
401
402    protected class RequestIdValidator extends UnrestrictedSessionRunner {
403
404        protected String uuid;
405
406        public RequestIdValidator(String uuid) {
407            super(getTargetRepositoryName());
408            this.uuid = uuid;
409        }
410
411        @Override
412        public void run() {
413            DocumentRef idRef = new IdRef(uuid);
414            // Check if the id matches an existing document
415            if (!session.exists(idRef)) {
416                throw new UserRegistrationException("There is no existing registration request with id " + uuid);
417            }
418
419            // Check if the request has not been already validated
420            DocumentModel registrationDoc = session.getDocument(idRef);
421            if (registrationDoc.getCurrentLifeCycleState().equals("accepted")) {
422                throw new AlreadyProcessedRegistrationException("Registration request has already been processed");
423            }
424        }
425    }
426
427    protected EventContext sendEvent(CoreSession session, DocumentModel source, String evName)
428            throws UserRegistrationException {
429        try {
430            EventService evService = Framework.getService(EventService.class);
431            EventContext evContext = new DocumentEventContext(session, session.getPrincipal(), source);
432
433            Event event = evContext.newEvent(evName);
434
435            evService.fireEvent(event);
436
437            return evContext;
438        } catch (UserRegistrationException ue) {
439            log.warn("Error during event processing", ue);
440            throw ue;
441        }
442
443    }
444
445    protected void sendValidationEmail(Map<String, Serializable> additionnalInfo, DocumentModel registrationDoc)
446            {
447        UserRegistrationConfiguration configuration = getConfiguration(registrationDoc);
448        sendEmail(additionnalInfo, registrationDoc, configuration.getValidationEmailTemplate(),
449                configuration.getValidationEmailTitle());
450    }
451
452    protected void sendEmail(Map<String, Serializable> additionnalInfo, DocumentModel registrationDoc,
453            String emailTemplatePath, String emailTitle) {
454        UserRegistrationConfiguration configuration = getConfiguration(registrationDoc);
455
456        String emailAdress = (String) registrationDoc.getPropertyValue(configuration.getUserInfoEmailField());
457
458        Map<String, Serializable> input = new HashMap<String, Serializable>();
459        Map<String, Serializable> userinfo = new HashMap<String, Serializable>();
460        userinfo.put("firstName", registrationDoc.getPropertyValue(configuration.getUserInfoFirstnameField()));
461        userinfo.put("lastName", registrationDoc.getPropertyValue(configuration.getUserInfoLastnameField()));
462        userinfo.put("login", registrationDoc.getPropertyValue(configuration.getUserInfoUsernameField()));
463        userinfo.put("id", registrationDoc.getId());
464
465        String documentTitle = "";
466
467        if (registrationDoc.hasSchema("docinfo")) {
468            documentTitle = (String) registrationDoc.getPropertyValue("docinfo:documentTitle");
469        }
470        input.put("documentTitle", documentTitle);
471        input.put("configurationName", configuration.getName());
472        input.put("comment", registrationDoc.getPropertyValue("registration:comment"));
473        input.put(UserInvitationService.REGISTRATION_CONFIGURATION_NAME, configuration.getName());
474        input.put("userinfo", (Serializable) userinfo);
475        input.put("info", (Serializable) additionnalInfo);
476        input.put("userAlreadyExists", checkUserFromRegistrationExistence(registrationDoc));
477        input.put("productName", Framework.getProperty("org.nuxeo.ecm.product.name"));
478        StringWriter writer = new StringWriter();
479
480        try {
481            rh.getRenderingEngine().render(emailTemplatePath, input, writer);
482        } catch (RenderingException e) {
483            throw new NuxeoException("Error during rendering email", e);
484        }
485
486        // render custom email subject
487        emailTitle = renderSubjectTemplate(emailTitle, input);
488
489        String body = writer.getBuffer().toString();
490        String copyTo = (String) registrationDoc.getPropertyValue("registration:copyTo");
491        if (!isTestModeSet()) {
492            try {
493                generateMail(emailAdress, copyTo, emailTitle, body);
494            } catch (NamingException | MessagingException e) {
495                throw new NuxeoException("Error while sending mail: ", e);
496            }
497        } else {
498            testRendering = body;
499        }
500    }
501
502    private String renderSubjectTemplate(String emailTitle, Map<String, Serializable> input) {
503        Configuration stringCfg = rh.getEngineConfiguration();
504        Writer out;
505        try {
506            Template templ = new Template("subjectTemplate", new StringReader(emailTitle), stringCfg);
507            out = new StringWriter();
508            templ.process(input, out);
509            out.flush();
510        } catch (IOException | TemplateException e) {
511            throw new NuxeoException("Error while rendering email subject: ", e);
512        }
513        return out.toString();
514    }
515
516    protected static boolean isTestModeSet() {
517        return Framework.isTestModeSet() || !isBlank(Framework.getProperty("org.nuxeo.ecm.tester.name"));
518    }
519
520    protected boolean checkUserFromRegistrationExistence(DocumentModel registrationDoc) {
521        UserRegistrationConfiguration configuration = getConfiguration(registrationDoc);
522        return null != Framework.getLocalService(UserManager.class).getPrincipal(
523                (String) registrationDoc.getPropertyValue(configuration.getUserInfoUsernameField()));
524    }
525
526    protected void generateMail(String destination, String copy, String title, String content) throws NamingException,
527            MessagingException {
528
529        InitialContext ic = new InitialContext();
530        Session session = (Session) ic.lookup(getJavaMailJndiName());
531
532        MimeMessage msg = new MimeMessage(session);
533        msg.setFrom(new InternetAddress(session.getProperty("mail.from")));
534        msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(destination, false));
535        if (!isBlank(copy)) {
536            msg.addRecipient(Message.RecipientType.CC, new InternetAddress(copy, false));
537        }
538
539        msg.setSubject(title, "UTF-8");
540        msg.setSentDate(new Date());
541        msg.setContent(content, "text/html; charset=utf-8");
542
543        Transport.send(msg);
544    }
545
546    @Override
547    public String submitRegistrationRequest(DocumentModel userRegistrationModel,
548            Map<String, Serializable> additionnalInfo, ValidationMethod validationMethod, boolean autoAccept)
549            {
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.getLocalService(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            {
590        RegistrationCreator creator = new RegistrationCreator(configurationName, userRegistrationModel,
591                additionnalInfo, validationMethod);
592        creator.runUnrestricted();
593        String registrationUuid = creator.getRegistrationUuid();
594
595        UserRegistrationConfiguration currentConfig = getConfiguration(configurationName);
596        boolean userAlreadyExists = null != Framework.getLocalService(UserManager.class).getPrincipal(
597                (String) userRegistrationModel.getPropertyValue(currentConfig.getUserInfoUsernameField()));
598
599        if (!userAlreadyExists && isEmailExist(currentConfig, userRegistrationModel)) {
600            log.info("Trying to submit a registration from an existing email with a different username.");
601            throw new UserAlreadyExistsException();
602        }
603
604        // Directly accept registration if the configuration allow it and the
605        // user already exists
606        RegistrationRules registrationRules = getRegistrationRules(configurationName);
607        boolean byPassAdminValidation = autoAccept;
608        byPassAdminValidation |= userAlreadyExists && registrationRules.allowDirectValidationForExistingUser();
609        byPassAdminValidation |= registrationRules.allowDirectValidationForExistingUser()
610                && registrationRules.allowDirectValidationForNonExistingUser();
611        if (byPassAdminValidation) {
612            // Build validationBaseUrl with nuxeo.url property as request is not
613            // accessible.
614            if (!additionnalInfo.containsKey("enterPasswordUrl")) {
615                additionnalInfo.put("enterPasswordUrl", buildEnterPasswordUrl(currentConfig));
616            }
617            acceptRegistrationRequest(registrationUuid, additionnalInfo);
618        }
619        return registrationUuid;
620    }
621
622    protected String buildEnterPasswordUrl(UserRegistrationConfiguration configuration) {
623        String baseUrl = Framework.getProperty(NUXEO_URL_KEY);
624
625        baseUrl = isBlank(baseUrl) ? "/" : baseUrl;
626        if (!baseUrl.endsWith("/")) {
627            baseUrl += "/";
628        }
629        return baseUrl.concat(configuration.getEnterPasswordUrl());
630    }
631
632    @Override
633    public void acceptRegistrationRequest(String requestId, Map<String, Serializable> additionnalInfo)
634            throws UserRegistrationException {
635        RegistrationApprover acceptor = new RegistrationApprover(requestId, additionnalInfo);
636        acceptor.runUnrestricted();
637
638    }
639
640    @Override
641    public void rejectRegistrationRequest(String requestId, Map<String, Serializable> additionnalInfo)
642            throws UserRegistrationException {
643
644        RegistrationRejector rejector = new RegistrationRejector(requestId, additionnalInfo);
645        rejector.runUnrestricted();
646
647    }
648
649    @Override
650    public Map<String, Serializable> validateRegistration(String requestId, Map<String, Serializable> additionnalInfo)
651            throws UserRegistrationException {
652        RegistrationAcceptator validator = new RegistrationAcceptator(requestId, additionnalInfo);
653        validator.runUnrestricted();
654        return validator.getRegistrationData();
655    }
656
657    @Override
658    public Map<String, Serializable> validateRegistrationAndSendEmail(String requestId,
659            Map<String, Serializable> additionnalInfo) throws UserRegistrationException {
660
661        Map<String, Serializable> registrationInfo = validateRegistration(requestId, additionnalInfo);
662
663        Map<String, Serializable> input = new HashMap<String, Serializable>();
664        input.putAll(registrationInfo);
665        input.put("info", (Serializable) additionnalInfo);
666        StringWriter writer = new StringWriter();
667
668        UserRegistrationConfiguration configuration = getConfiguration((DocumentModel) registrationInfo.get(REGISTRATION_DATA_DOC));
669        try {
670            rh.getRenderingEngine().render(configuration.getSuccessEmailTemplate(), input, writer);
671        } catch (RenderingException e) {
672            throw new NuxeoException("Error during rendering email", e);
673        }
674
675        String emailAdress = ((NuxeoPrincipalImpl) registrationInfo.get("registeredUser")).getEmail();
676        String body = writer.getBuffer().toString();
677        String title = configuration.getValidationEmailTitle();
678        if (!Framework.isTestModeSet()) {
679            try {
680                generateMail(emailAdress, null, title, body);
681            } catch (NamingException | MessagingException e) {
682                throw new NuxeoException("Error while sending mail : ", e);
683            }
684        } else {
685            testRendering = body;
686        }
687
688        return registrationInfo;
689    }
690
691    @Override
692    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
693        if ("configuration".equals(extensionPoint)) {
694            UserRegistrationConfiguration newConfig = (UserRegistrationConfiguration) contribution;
695
696            if (configurations.containsKey(newConfig.getName())) {
697                if (newConfig.isMerge()) {
698                    configurations.get(newConfig.getName()).mergeWith(newConfig);
699                } else if (newConfig.isRemove()) {
700                    configurations.remove(newConfig.getName());
701                } else {
702                    log.warn("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) throws
731            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            {
802        for (DocumentModel registrationDoc : registrationDocs) {
803            reviveRegistrationRequest(session, registrationDoc, new HashMap<String, Serializable>());
804        }
805    }
806
807    protected void reviveRegistrationRequest(CoreSession session, DocumentModel registrationDoc,
808            Map<String, Serializable> additionalInfos) {
809        UserRegistrationConfiguration configuration = getConfiguration(registrationDoc);
810        // Build validationBaseUrl with nuxeo.url property as request is not
811        // accessible.
812        if (!additionalInfos.containsKey("enterPasswordUrl")) {
813            additionalInfos.put("enterPasswordUrl", buildEnterPasswordUrl(configuration));
814        }
815        sendEmail(additionalInfos, registrationDoc, configuration.getReviveEmailTemplate(),
816                configuration.getReviveEmailTitle());
817    }
818
819    @Override
820    public void deleteRegistrationRequests(CoreSession session, List<DocumentModel> registrationDocs)
821            {
822        for (DocumentModel registration : registrationDocs) {
823            UserRegistrationConfiguration configuration = getConfiguration(registration);
824            if (!registration.hasSchema(configuration.getUserInfoSchemaName())) {
825                throw new NuxeoException("Registration document do not contains needed schema");
826            }
827
828            session.removeDocument(registration.getRef());
829        }
830    }
831
832    @Override
833    public Set<String> getConfigurationsName() {
834        return configurations.keySet();
835    }
836
837    @Override
838    public void checkRequestId(final String requestId) throws UserRegistrationException {
839        RequestIdValidator runner = new RequestIdValidator(requestId);
840        runner.runUnrestricted();
841    }
842
843    @Override
844    public String getNameEventRegistrationSubmitted() {
845        return INVITATION_SUBMITTED_EVENT;
846    }
847
848    @Override
849    public String getNameEventRegistrationAccepted() {
850        return INVITATION_ACCEPTED_EVENT;
851    }
852
853    @Override
854    public String getNameEventRegistrationRejected() {
855        return INVITATION_REJECTED_EVENT;
856    }
857
858    @Override
859    public String getNameEventRegistrationValidated() {
860        return INVITATION_VALIDATED_EVENT;
861    }
862
863}