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            request.setPropertyValue("docinfo:creator", documentManager.getPrincipal().getName());
195            documentManager.saveDocument(request);
196
197            userRegistrationService.acceptRegistrationRequest(request.getId(), additionalInfo);
198
199            // EventManager.raiseEventsOnDocumentChange(request);
200            Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
201        } catch (NuxeoException e) {
202            facesMessages.add(ERROR, e.getMessage());
203        }
204    }
205
206    public void rejectRegistrationRequest(DocumentModel request) {
207        try {
208            Map<String, Serializable> additionalInfo = new HashMap<String, Serializable>();
209            additionalInfo.put("validationBaseURL", getValidationBaseUrl());
210            userRegistrationService.rejectRegistrationRequest(request.getId(), additionalInfo);
211            // EventManager.raiseEventsOnDocumentChange(request);
212            Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
213        } catch (NuxeoException e) {
214            facesMessages.add(ERROR, e.getMessage());
215        }
216    }
217
218    public void submitUserRegistration(String configurationName) {
219        try {
220            docinfo.setDocumentId(navigationContext.getCurrentDocument().getId());
221            docinfo.setDocumentTitle(navigationContext.getCurrentDocument().getTitle());
222            doSubmitUserRegistration(configurationName);
223            resetPojos();
224            Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
225        } catch (NuxeoException e) {
226            facesMessages.add(ERROR, e.getMessage());
227        }
228    }
229
230    public void submitMultipleUserRegistration(String configurationName) throws AddressException {
231        if (StringUtils.isBlank(multipleEmails)) {
232            facesMessages.add(ERROR, resourcesAccessor.getMessages().get("label.registration.multiple.empty"));
233            return;
234        }
235        docinfo.setDocumentId(navigationContext.getCurrentDocument().getId());
236
237        InternetAddress[] emails = splitAddresses(multipleEmails);
238        for (InternetAddress email : emails) {
239            userinfo.setLogin(email.getAddress());
240            userinfo.setEmail(email.getAddress());
241
242            log.debug("Request email: " + email + " with multiple invitation.");
243            doSubmitUserRegistration(configurationName);
244        }
245        resetPojos();
246        Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
247    }
248
249    protected InternetAddress[] splitAddresses(String emails) throws AddressException {
250        return StringUtils.isNotBlank(emails) ? InternetAddress.parse(emails.replace(MULTIPLE_EMAILS_SEPARATOR, ","),
251                false) : new InternetAddress[] {};
252    }
253
254    public void validateMultipleUser(FacesContext context, UIComponent component, Object value) {
255        if (value instanceof String) {
256            try {
257                splitAddresses((String) value);
258                return;
259            } catch (AddressException e) {
260                // Nothing to do, error is handled after
261            }
262        }
263
264        FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, ComponentUtils.translate(context,
265                "label.request.error.multiple.emails"), null);
266
267        // also add global message
268        context.addMessage(null, message);
269        throw new ValidatorException(message);
270    }
271
272    public boolean getCanValidate() {
273        boolean canDelete = !documentsListsManager.isWorkingListEmpty(REQUEST_DOCUMENT_LIST);
274        for (DocumentModel doc : documentsListsManager.getWorkingList(REQUEST_DOCUMENT_LIST)) {
275            canDelete &= isDocumentValidable(doc);
276        }
277        return canDelete;
278    }
279
280    protected boolean isDocumentValidable(DocumentModel doc) {
281        return "accepted".equals(doc.getCurrentLifeCycleState());
282    }
283
284    public boolean getCanDelete() {
285        boolean canDelete = !documentsListsManager.isWorkingListEmpty(REQUEST_DOCUMENT_LIST);
286        for (DocumentModel doc : documentsListsManager.getWorkingList(REQUEST_DOCUMENT_LIST)) {
287            canDelete &= isDocumentDeletable(doc);
288        }
289        return canDelete;
290    }
291
292    protected boolean isDocumentDeletable(DocumentModel doc) {
293        return !"validated".equals(doc.getCurrentLifeCycleState());
294    }
295
296    public boolean getCanRevive() {
297        boolean canRevive = !documentsListsManager.isWorkingListEmpty(REQUEST_DOCUMENT_LIST);
298        for (DocumentModel doc : documentsListsManager.getWorkingList(REQUEST_DOCUMENT_LIST)) {
299            canRevive &= isDocumentRevivable(doc);
300        }
301        return canRevive;
302    }
303
304    protected boolean isDocumentRevivable(DocumentModel doc) {
305        return "accepted".equals(doc.getCurrentLifeCycleState());
306    }
307
308    public void validateUserRegistration() {
309        if (!documentsListsManager.isWorkingListEmpty(REQUEST_DOCUMENT_LIST)) {
310            try {
311                for (DocumentModel registration : documentsListsManager.getWorkingList(REQUEST_DOCUMENT_LIST)) {
312                    userRegistrationService.validateRegistration(registration.getId(),
313                            new HashMap<String, Serializable>());
314                }
315                Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
316                facesMessages.add(INFO, resourcesAccessor.getMessages().get("label.validate.request"));
317                documentsListsManager.resetWorkingList(REQUEST_DOCUMENT_LIST);
318            } catch (NuxeoException e) {
319                log.warn("Unable to validate registration: " + e.getMessage());
320                log.info(e);
321                facesMessages.add(ERROR, resourcesAccessor.getMessages().get("label.unable.validate.request"));
322            }
323        }
324    }
325
326    public void reviveUserRegistration() {
327        if (!documentsListsManager.isWorkingListEmpty(REQUEST_DOCUMENT_LIST)) {
328            try {
329                userRegistrationService.reviveRegistrationRequests(documentManager,
330                        documentsListsManager.getWorkingList(REQUEST_DOCUMENT_LIST));
331                Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
332                facesMessages.add(INFO, resourcesAccessor.getMessages().get("label.revive.request"));
333                documentsListsManager.resetWorkingList(REQUEST_DOCUMENT_LIST);
334            } catch (NuxeoException e) {
335                log.warn("Unable to revive user: " + e.getMessage());
336                log.info(e);
337                facesMessages.add(ERROR, resourcesAccessor.getMessages().get("label.unable.revive.request"));
338            }
339        }
340    }
341
342    public void deleteUserRegistration() {
343        if (!documentsListsManager.isWorkingListEmpty(REQUEST_DOCUMENT_LIST)) {
344            try {
345                userRegistrationService.deleteRegistrationRequests(documentManager,
346                        documentsListsManager.getWorkingList(REQUEST_DOCUMENT_LIST));
347                Events.instance().raiseEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
348                facesMessages.add(INFO, resourcesAccessor.getMessages().get("label.delete.request"));
349                documentsListsManager.resetWorkingList(REQUEST_DOCUMENT_LIST);
350            } catch (NuxeoException e) {
351                log.warn("Unable to delete user request:" + e.getMessage());
352                log.info(e);
353                facesMessages.add(ERROR, resourcesAccessor.getMessages().get("label.unable.delete.request"));
354            }
355
356        }
357    }
358
359    protected void doSubmitUserRegistration(String configurationName) {
360        if (StringUtils.isBlank(configurationName)) {
361            configurationName = CONFIGURATION_NAME;
362        }
363
364        try {
365            userRegistrationService.submitRegistrationRequest(configurationName, userinfo, docinfo,
366                    getAdditionalsParameters(), EMAIL, false, documentManager.getPrincipal().getName());
367
368            facesMessages.add(INFO, resourcesAccessor.getMessages().get("label.user.invited.success"));
369        } catch (NuxeoException e) {
370            log.info("Unable to register user: " + e.getMessage());
371            log.debug(e, e);
372            facesMessages.add(ERROR, resourcesAccessor.getMessages().get("label.unable.invite"), userinfo.getLogin());
373            facesMessages.add(ERROR, e.getMessage());
374        }
375    }
376
377    protected Map<String, Serializable> getAdditionalsParameters() {
378        Map<String, Serializable> additionalsInfo = new HashMap<String, Serializable>();
379        try {
380            additionalsInfo.put("docinfo:documentTitle", navigationContext.getCurrentDocument().getTitle());
381            if (copyOwner) {
382                additionalsInfo.put("registration:copyTo", ((NuxeoPrincipal) documentManager.getPrincipal()).getEmail());
383            }
384            additionalsInfo.put("registration:comment", comment);
385        } catch (NuxeoException e) {
386            // log it silently as it will break anything
387            log.debug(e, e);
388        }
389        return additionalsInfo;
390    }
391
392    @Observer({ EventNames.DOCUMENT_CHANGED })
393    public void resetPojos() {
394        userinfo = new UserRegistrationInfo();
395        docinfo = new DocumentRegistrationInfo();
396        multipleEmails = "";
397        copyOwner = false;
398        comment = "";
399    }
400
401    @Observer({ REQUESTS_DOCUMENT_LIST_CHANGED })
402    public void refreshContentViewCache() {
403        contentViewActions.refreshOnSeamEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
404        contentViewActions.resetPageProviderOnSeamEvent(REQUESTS_DOCUMENT_LIST_CHANGED);
405    }
406
407    public void validateUsernameEmail(FacesContext context, UIComponent component, Object value) {
408        String email = (String) ((UIInput) component.findComponent(WIDGET_COMPONENT_EMAIL_ID)).getLocalValue();
409        NuxeoPrincipal principal = userManager.getPrincipal((String) value);
410        if (principal == null || email == null) {
411            return;
412        }
413        if (!email.equals(principal.getEmail())) {
414            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, ComponentUtils.translate(context,
415                    "label.unicity.usernamepwd"), null);
416            context.addMessage(null, message);
417            throw new ValidatorException(message);
418        }
419    }
420}