001/*
002 * (C) Copyright 2015 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 *
016 * Contributors:
017 *     Thomas Roger
018 */
019
020package org.nuxeo.ecm.permissions;
021
022import static org.nuxeo.ecm.permissions.Constants.ACE_GRANTED_TEMPLATE;
023import static org.nuxeo.ecm.permissions.Constants.ACE_INFO_COMMENT;
024import static org.nuxeo.ecm.permissions.Constants.ACE_INFO_DIRECTORY;
025import static org.nuxeo.ecm.permissions.Constants.ACE_KEY;
026import static org.nuxeo.ecm.permissions.Constants.ACL_NAME_KEY;
027import static org.nuxeo.ecm.permissions.Constants.PERMISSION_NOTIFICATION_EVENT;
028
029import java.util.Collections;
030
031import org.apache.commons.lang.StringEscapeUtils;
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.nuxeo.ecm.automation.AutomationService;
035import org.nuxeo.ecm.automation.OperationChain;
036import org.nuxeo.ecm.automation.OperationContext;
037import org.nuxeo.ecm.automation.OperationException;
038import org.nuxeo.ecm.automation.core.operations.notification.SendMail;
039import org.nuxeo.ecm.automation.core.scripting.Expression;
040import org.nuxeo.ecm.automation.core.scripting.Scripting;
041import org.nuxeo.ecm.automation.core.util.StringList;
042import org.nuxeo.ecm.automation.features.PlatformFunctions;
043import org.nuxeo.ecm.core.api.CoreSession;
044import org.nuxeo.ecm.core.api.DocumentModel;
045import org.nuxeo.ecm.core.api.NuxeoGroup;
046import org.nuxeo.ecm.core.api.NuxeoPrincipal;
047import org.nuxeo.ecm.core.api.security.ACE;
048import org.nuxeo.ecm.core.event.Event;
049import org.nuxeo.ecm.core.event.EventBundle;
050import org.nuxeo.ecm.core.event.EventContext;
051import org.nuxeo.ecm.core.event.PostCommitFilteringEventListener;
052import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
053import org.nuxeo.ecm.directory.Session;
054import org.nuxeo.ecm.directory.api.DirectoryService;
055import org.nuxeo.ecm.platform.ec.notification.service.NotificationService;
056import org.nuxeo.ecm.platform.ec.notification.service.NotificationServiceHelper;
057import org.nuxeo.ecm.platform.ui.web.tag.fn.Functions;
058import org.nuxeo.ecm.platform.usermanager.UserManager;
059import org.nuxeo.ecm.tokenauth.service.TokenAuthenticationService;
060import org.nuxeo.runtime.api.Framework;
061
062/**
063 * Listener sending an email notification for a granted ACE.
064 * <p>
065 * This listener checks only if the ACE is granted. It assumes that other checks (such as the ACE becomes effective)
066 * have been done before.
067 *
068 * @since 7.4
069 */
070public class PermissionGrantedNotificationListener implements PostCommitFilteringEventListener {
071
072    private static final Log log = LogFactory.getLog(PermissionGrantedNotificationListener.class);
073
074    public static final String SUBJECT_FORMAT = "%s %s %s";
075
076    @Override
077    public void handleEvent(EventBundle events) {
078        for (Event event : events) {
079            handleEvent(event);
080        }
081    }
082
083    protected void handleEvent(Event event) {
084        EventContext eventCtx = event.getContext();
085        if (!(eventCtx instanceof DocumentEventContext)) {
086            return;
087        }
088
089        DocumentEventContext docCtx = (DocumentEventContext) eventCtx;
090        CoreSession coreSession = docCtx.getCoreSession();
091        DocumentModel doc = docCtx.getSourceDocument();
092        if (doc == null || !coreSession.exists(doc.getRef())) {
093            return;
094        }
095
096        ACE ace = (ACE) docCtx.getProperty(ACE_KEY);
097        String aclName = (String) docCtx.getProperty(ACL_NAME_KEY);
098        if (ace == null || ace.isDenied() || aclName == null) {
099            return;
100        }
101
102        String username = ace.getUsername();
103        StringList to = getRecipients(username);
104        if (to == null) {
105            // no recipient
106            return;
107        }
108
109        Expression from = Scripting.newExpression("Env[\"mail.from\"]");
110        NotificationService notificationService = NotificationServiceHelper.getNotificationService();
111        String subject = String.format(SUBJECT_FORMAT, notificationService.getEMailSubjectPrefix(),
112                "New permission on", doc.getTitle());
113
114        DirectoryService directoryService = Framework.getService(DirectoryService.class);
115        try (Session session = directoryService.open(ACE_INFO_DIRECTORY)) {
116            String id = PermissionHelper.computeDirectoryId(doc, aclName, ace.getId());
117            DocumentModel entry = session.getEntry(id);
118
119            OperationContext ctx = new OperationContext(coreSession);
120            ctx.setInput(doc);
121            ctx.put("ace", ace);
122            if (entry != null) {
123                String comment = (String) entry.getPropertyValue(ACE_INFO_COMMENT);
124                if (comment != null) {
125                    comment = StringEscapeUtils.escapeHtml(comment);
126                    comment = comment.replaceAll("\n", "<br/>");
127                    ctx.put("comment", comment);
128                }
129            }
130            String aceCreator = ace.getCreator();
131            if (aceCreator != null) {
132                UserManager userManager = Framework.getService(UserManager.class);
133                NuxeoPrincipal creator = userManager.getPrincipal(aceCreator);
134                if (creator != null) {
135                    ctx.put("aceCreator",
136                            String.format("%s (%s)", Functions.principalFullName(creator), creator.getName()));
137                }
138            }
139
140            if (NuxeoPrincipal.isTransientUsername(username)) {
141                TokenAuthenticationService tokenAuthenticationService = Framework.getService(TokenAuthenticationService.class);
142                String token = tokenAuthenticationService.getToken(username, doc.getRepositoryName(), doc.getId());
143                if (token != null) {
144                    ctx.put("token", token);
145                }
146            }
147
148            OperationChain chain = new OperationChain("SendMail");
149            chain.add(SendMail.ID)
150                 .set("from", from)
151                 .set("to", to)
152                 .set("HTML", true)
153                 .set("subject", subject)
154                 .set("message", ACE_GRANTED_TEMPLATE);
155            Framework.getService(AutomationService.class).run(ctx, chain);
156        } catch (OperationException e) {
157            log.warn("Unable to notify user", e);
158            log.debug(e, e);
159        }
160    }
161
162    protected StringList getRecipients(String username) {
163        UserManager userManager = Framework.getService(UserManager.class);
164        NuxeoPrincipal principal = userManager.getPrincipal(username);
165        StringList to = null;
166        if (principal != null) {
167            to = new StringList(Collections.singletonList(principal.getEmail()));
168        } else {
169            NuxeoGroup group = userManager.getGroup(username);
170            if (group != null) {
171                PlatformFunctions platformFunctions = new PlatformFunctions();
172                to = platformFunctions.getEmailsFromGroup(group.getName());
173            }
174        }
175        return to;
176    }
177
178    @Override
179    public boolean acceptEvent(Event event) {
180        String eventName = event.getName();
181        return PERMISSION_NOTIFICATION_EVENT.equals(eventName);
182    }
183}