001/*
002 * (C) Copyright 2011 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.registration.actions;
020
021import static org.jboss.seam.international.StatusMessage.Severity.ERROR;
022import static org.jboss.seam.international.StatusMessage.Severity.INFO;
023import static org.nuxeo.ecm.user.invite.UserInvitationService.ValidationMethod.EMAIL;
024import static org.nuxeo.ecm.user.registration.UserRegistrationService.CONFIGURATION_NAME;
025
026import java.io.Serializable;
027import java.util.HashMap;
028import java.util.Map;
029
030import javax.faces.application.FacesMessage;
031import javax.faces.component.UIComponent;
032import javax.faces.component.UIInput;
033import javax.faces.context.FacesContext;
034import javax.faces.validator.ValidatorException;
035import javax.mail.internet.AddressException;
036import javax.mail.internet.InternetAddress;
037
038import org.apache.commons.lang3.StringUtils;
039import org.apache.commons.logging.Log;
040import org.apache.commons.logging.LogFactory;
041import org.jboss.seam.ScopeType;
042import org.jboss.seam.annotations.In;
043import org.jboss.seam.annotations.Name;
044import org.jboss.seam.annotations.Observer;
045import org.jboss.seam.annotations.Scope;
046import org.jboss.seam.core.Events;
047import org.jboss.seam.faces.FacesMessages;
048import org.nuxeo.ecm.core.api.CoreSession;
049import org.nuxeo.ecm.core.api.DocumentModel;
050import org.nuxeo.ecm.core.api.DocumentModelList;
051import org.nuxeo.ecm.core.api.DocumentRef;
052import org.nuxeo.ecm.core.api.IdRef;
053import org.nuxeo.ecm.core.api.NuxeoException;
054import org.nuxeo.ecm.core.api.NuxeoPrincipal;
055import org.nuxeo.ecm.platform.contentview.seam.ContentViewActions;
056import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
057import org.nuxeo.ecm.platform.ui.web.tag.fn.DocumentModelFunctions;
058import org.nuxeo.ecm.platform.ui.web.util.BaseURL;
059import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils;
060import org.nuxeo.ecm.platform.usermanager.UserManager;
061import org.nuxeo.ecm.user.invite.UserInvitationComponent;
062import org.nuxeo.ecm.user.invite.UserRegistrationInfo;
063import org.nuxeo.ecm.user.registration.DocumentRegistrationInfo;
064import org.nuxeo.ecm.user.registration.UserRegistrationService;
065import org.nuxeo.ecm.webapp.documentsLists.DocumentsListsManager;
066import org.nuxeo.ecm.webapp.helpers.EventNames;
067import org.nuxeo.ecm.webapp.helpers.ResourcesAccessor;
068
069@Name("userRegistrationActions")
070@Scope(ScopeType.CONVERSATION)
071public class UserRegistrationActions implements Serializable {
072
073    private static final long serialVersionUID = 53468164827894L;
074
075    public static final String WIDGET_COMPONENT_EMAIL_ID = "nxw_user_request_email";
076
077    private static Log log = LogFactory.getLog(UserRegistrationActions.class);
078
079    public static final String MULTIPLE_EMAILS_SEPARATOR = ";";
080
081    public static final String REQUEST_DOCUMENT_LIST = "CURRENT_USER_REQUESTS";
082
083    public static final String REQUESTS_DOCUMENT_LIST_CHANGED = "requestDocumentsChanged";
084
085    protected UserRegistrationInfo userinfo = new UserRegistrationInfo();
086
087    protected DocumentRegistrationInfo docinfo = new DocumentRegistrationInfo();
088
089    protected String multipleEmails;
090
091    protected String comment;
092
093    protected boolean copyOwner = false;
094
095    @In(create = true)
096    protected transient NavigationContext navigationContext;
097
098    @In(create = true, required = false)
099    protected transient CoreSession documentManager;
100
101    @In(create = true, required = false)
102    protected transient FacesMessages facesMessages;
103
104    @In(create = true)
105    protected transient ResourcesAccessor resourcesAccessor;
106
107    @In(create = true)
108    protected transient DocumentsListsManager documentsListsManager;
109
110    @In(create = true)
111    protected transient ContentViewActions contentViewActions;
112
113    @In(create = true)
114    protected transient UserRegistrationService userRegistrationService;
115
116    @In(create = true)
117    protected transient UserManager userManager;
118
119    public UserRegistrationInfo getUserinfo() {
120        return userinfo;
121    }
122
123    public DocumentRegistrationInfo getDocinfo() {
124        return docinfo;
125    }
126
127    public String getComment() {
128        return comment;
129    }
130
131    public void setComment(String comment) {
132        this.comment = comment;
133    }
134
135    public boolean isCopyOwner() {
136        return copyOwner;
137    }
138
139    public void setCopyOwner(boolean copyOwner) {
140        this.copyOwner = copyOwner;
141    }
142
143    // Tweak to use same widgets between listing and forms
144    public UserRegistrationActions getData() {
145        return this;
146    }
147
148    public String getDocType() {
149        return getDocType(CONFIGURATION_NAME);
150    }
151
152    public String getDocType(String name) {
153        return userRegistrationService.getConfiguration(name).getRequestDocType();
154    }
155
156    public String getValidationBaseUrl(String name) {
157        return BaseURL.getBaseURL() + userRegistrationService.getConfiguration(name).getValidationRelUrl();
158    }
159
160    public String getEnterPasswordUrl(String name) {
161        return BaseURL.getBaseURL() + userRegistrationService.getConfiguration(name).getEnterPasswordUrl();
162    }
163
164    public String getInvitationLayout(String name) {
165        return userRegistrationService.getConfiguration(name).getInvitationLayout();
166    }
167
168    public String getListingLocalContentView(String name) {
169        return userRegistrationService.getConfiguration(name).getListingLocalContentView();
170    }
171
172    public String getMultipleEmails() {
173        return multipleEmails;
174    }
175
176    public void setMultipleEmails(String multipleEmails) {
177        this.multipleEmails = multipleEmails;
178    }
179
180    public String getValidationBaseUrl() {
181        return getValidationBaseUrl(CONFIGURATION_NAME);
182    }
183
184    public String getEnterPasswordUrl() {
185        return getEnterPasswordUrl(CONFIGURATION_NAME);
186    }
187
188    public void acceptRegistrationRequest(DocumentModel request) {
189        try {
190            Map<String, Serializable> additionalInfo = new HashMap<String, Serializable>();
191            additionalInfo.put("enterPasswordUrl", getEnterPasswordUrl());
192            // Determine the document url to add it into the email
193            String docId = (String) request.getPropertyValue(DocumentRegistrationInfo.DOCUMENT_ID_FIELD);
194            DocumentRef docRef = new IdRef(docId);
195            DocumentModel doc = documentManager.getDocument(docRef);
196            String docUrl = DocumentModelFunctions.documentUrl("id", doc, null, null, false, BaseURL.getBaseURL());
197            additionalInfo.put("docUrl", docUrl);
198            request.setPropertyValue("docinfo:creator", documentManager.getPrincipal().getName());
199            documentManager.saveDocument(request);
200
201            userRegistrationService.acceptRegistrationRequest(request.getId(), additionalInfo);
202
203            // EventManager.raiseEventsOnDocumentChange(request);
204            Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
205        } catch (NuxeoException e) {
206            facesMessages.add(ERROR, e.getMessage());
207        }
208    }
209
210    public void rejectRegistrationRequest(DocumentModel request) {
211        try {
212            Map<String, Serializable> additionalInfo = new HashMap<String, Serializable>();
213            additionalInfo.put("validationBaseURL", getValidationBaseUrl());
214            userRegistrationService.rejectRegistrationRequest(request.getId(), additionalInfo);
215            // EventManager.raiseEventsOnDocumentChange(request);
216            Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
217        } catch (NuxeoException e) {
218            facesMessages.add(ERROR, e.getMessage());
219        }
220    }
221
222    public void submitUserRegistration(String configurationName) {
223        try {
224            docinfo.setDocumentId(navigationContext.getCurrentDocument().getId());
225            docinfo.setDocumentTitle(navigationContext.getCurrentDocument().getTitle());
226            doSubmitUserRegistration(configurationName);
227            resetPojos();
228            Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
229        } catch (NuxeoException e) {
230            facesMessages.add(ERROR, e.getMessage());
231        }
232    }
233
234    public void submitMultipleUserRegistration(String configurationName) throws AddressException {
235        if (StringUtils.isBlank(multipleEmails)) {
236            facesMessages.add(ERROR, resourcesAccessor.getMessages().get("label.registration.multiple.empty"));
237            return;
238        }
239        docinfo.setDocumentId(navigationContext.getCurrentDocument().getId());
240
241        InternetAddress[] emails = splitAddresses(multipleEmails);
242        for (InternetAddress email : emails) {
243            userinfo.setLogin(email.getAddress());
244            userinfo.setEmail(email.getAddress());
245
246            log.debug("Request email: " + email + " with multiple invitation.");
247            doSubmitUserRegistration(configurationName);
248        }
249        resetPojos();
250        Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
251    }
252
253    protected InternetAddress[] splitAddresses(String emails) throws AddressException {
254        return StringUtils.isNotBlank(emails) ? InternetAddress.parse(emails.replace(MULTIPLE_EMAILS_SEPARATOR, ","),
255                false) : new InternetAddress[] {};
256    }
257
258    public void validateMultipleUser(FacesContext context, UIComponent component, Object value) {
259        if (value instanceof String) {
260            try {
261                splitAddresses((String) value);
262                return;
263            } catch (AddressException e) {
264                // Nothing to do, error is handled after
265            }
266        }
267
268        FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, ComponentUtils.translate(context,
269                "label.request.error.multiple.emails"), null);
270
271        // also add global message
272        context.addMessage(null, message);
273        throw new ValidatorException(message);
274    }
275
276    public boolean getCanValidate() {
277        boolean canDelete = !documentsListsManager.isWorkingListEmpty(REQUEST_DOCUMENT_LIST);
278        for (DocumentModel doc : documentsListsManager.getWorkingList(REQUEST_DOCUMENT_LIST)) {
279            canDelete &= isDocumentValidable(doc);
280        }
281        return canDelete;
282    }
283
284    protected boolean isDocumentValidable(DocumentModel doc) {
285        return "approved".equals(doc.getCurrentLifeCycleState());
286    }
287
288    public boolean getCanDelete() {
289        boolean canDelete = !documentsListsManager.isWorkingListEmpty(REQUEST_DOCUMENT_LIST);
290        for (DocumentModel doc : documentsListsManager.getWorkingList(REQUEST_DOCUMENT_LIST)) {
291            canDelete &= isDocumentDeletable(doc);
292        }
293        return canDelete;
294    }
295
296    protected boolean isDocumentDeletable(DocumentModel doc) {
297        return !"validated".equals(doc.getCurrentLifeCycleState());
298    }
299
300    public boolean getCanRevive() {
301        boolean canRevive = !documentsListsManager.isWorkingListEmpty(REQUEST_DOCUMENT_LIST);
302        for (DocumentModel doc : documentsListsManager.getWorkingList(REQUEST_DOCUMENT_LIST)) {
303            canRevive &= isDocumentRevivable(doc);
304        }
305        return canRevive;
306    }
307
308    protected boolean isDocumentRevivable(DocumentModel doc) {
309        return "approved".equals(doc.getCurrentLifeCycleState());
310    }
311
312    public void validateUserRegistration() {
313        if (!documentsListsManager.isWorkingListEmpty(REQUEST_DOCUMENT_LIST)) {
314            try {
315                for (DocumentModel registration : documentsListsManager.getWorkingList(REQUEST_DOCUMENT_LIST)) {
316                    userRegistrationService.validateRegistration(registration.getId(),
317                            new HashMap<String, Serializable>());
318                }
319                Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
320                facesMessages.add(INFO, resourcesAccessor.getMessages().get("label.validate.request"));
321                documentsListsManager.resetWorkingList(REQUEST_DOCUMENT_LIST);
322            } catch (NuxeoException e) {
323                log.warn("Unable to validate registration: " + e.getMessage());
324                log.info(e);
325                facesMessages.add(ERROR, resourcesAccessor.getMessages().get("label.unable.validate.request"));
326            }
327        }
328    }
329
330    public void reviveUserRegistration() {
331        if (!documentsListsManager.isWorkingListEmpty(REQUEST_DOCUMENT_LIST)) {
332            try {
333                userRegistrationService.reviveRegistrationRequests(documentManager,
334                        documentsListsManager.getWorkingList(REQUEST_DOCUMENT_LIST));
335                Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
336                facesMessages.add(INFO, resourcesAccessor.getMessages().get("label.revive.request"));
337                documentsListsManager.resetWorkingList(REQUEST_DOCUMENT_LIST);
338            } catch (NuxeoException e) {
339                log.warn("Unable to revive user: " + e.getMessage());
340                log.info(e);
341                facesMessages.add(ERROR, resourcesAccessor.getMessages().get("label.unable.revive.request"));
342            }
343        }
344    }
345
346    public void deleteUserRegistration() {
347        if (!documentsListsManager.isWorkingListEmpty(REQUEST_DOCUMENT_LIST)) {
348            try {
349                userRegistrationService.deleteRegistrationRequests(documentManager,
350                        documentsListsManager.getWorkingList(REQUEST_DOCUMENT_LIST));
351                Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
352                facesMessages.add(INFO, resourcesAccessor.getMessages().get("label.delete.request"));
353                documentsListsManager.resetWorkingList(REQUEST_DOCUMENT_LIST);
354            } catch (NuxeoException e) {
355                log.warn("Unable to delete user request:" + e.getMessage());
356                log.info(e);
357                facesMessages.add(ERROR, resourcesAccessor.getMessages().get("label.unable.delete.request"));
358            }
359
360        }
361    }
362
363    protected void doSubmitUserRegistration(String configurationName) {
364        if (StringUtils.isBlank(configurationName)) {
365            configurationName = CONFIGURATION_NAME;
366        }
367
368        try {
369            userRegistrationService.submitRegistrationRequest(configurationName, userinfo, docinfo,
370                    getAdditionalsParameters(), EMAIL, false, documentManager.getPrincipal().getName());
371
372            facesMessages.add(INFO, resourcesAccessor.getMessages().get("label.user.invited.success"));
373        } catch (NuxeoException e) {
374            log.info("Unable to register user: " + e.getMessage());
375            log.debug(e, e);
376            facesMessages.add(ERROR, resourcesAccessor.getMessages().get("label.unable.invite"), userinfo.getLogin());
377            facesMessages.add(ERROR, e.getMessage());
378        }
379    }
380
381    protected Map<String, Serializable> getAdditionalsParameters() {
382        Map<String, Serializable> additionalsInfo = new HashMap<String, Serializable>();
383        try {
384            additionalsInfo.put(UserInvitationComponent.PARAM_ORIGINATING_USER , documentManager.getPrincipal().getName());
385            additionalsInfo.put("docinfo:documentTitle", navigationContext.getCurrentDocument().getTitle());
386            if (copyOwner) {
387                additionalsInfo.put("registration:copyTo", ((NuxeoPrincipal) documentManager.getPrincipal()).getEmail());
388            }
389            additionalsInfo.put("registration:comment", comment);
390        } catch (NuxeoException e) {
391            // log it silently as it will break anything
392            log.debug(e, e);
393        }
394        return additionalsInfo;
395    }
396
397    @Observer({ EventNames.DOCUMENT_CHANGED })
398    public void resetPojos() {
399        userinfo = new UserRegistrationInfo();
400        docinfo = new DocumentRegistrationInfo();
401        multipleEmails = "";
402        copyOwner = false;
403        comment = "";
404    }
405
406    @Observer({ REQUESTS_DOCUMENT_LIST_CHANGED })
407    public void refreshContentViewCache() {
408        contentViewActions.refreshOnSeamEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
409        contentViewActions.resetPageProviderOnSeamEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
410    }
411
412    public void validateUsernameEmail(FacesContext context, UIComponent component, Object value) {
413        String email = (String) ((UIInput) component.findComponent(WIDGET_COMPONENT_EMAIL_ID)).getLocalValue();
414        if (email != null) {
415            if (value == null) {
416                value = email;
417            }
418            NuxeoPrincipal principal = userManager.getPrincipal((String) value);
419            if (principal != null) {
420                if (!email.equals(principal.getEmail())) {
421                    FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, ComponentUtils.translate(
422                            context, "label.unicity.usernamepwd"), null);
423                    context.addMessage(null, message);
424                    throw new ValidatorException(message);
425                }
426                return;
427            }
428            Map<String, Serializable> filter = new HashMap<>();
429            filter.put("email", email);
430            DocumentModelList users = userManager.searchUsers(filter, filter.keySet());
431            if (users.size() != 0) {
432                FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, ComponentUtils.translate(context,
433                        "label.unicity.sameemail", users.get(0).getPropertyValue("user:username")), null);
434                context.addMessage(null, message);
435                throw new ValidatorException(message);
436            }
437        }
438    }
439}