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