001/* 002 * (C) Copyright 2015-2018 Nuxeo (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; 030import java.util.Locale; 031 032import org.apache.commons.text.StringEscapeUtils; 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.common.utils.i18n.I18NUtils; 036import org.nuxeo.ecm.automation.AutomationService; 037import org.nuxeo.ecm.automation.OperationChain; 038import org.nuxeo.ecm.automation.OperationContext; 039import org.nuxeo.ecm.automation.OperationException; 040import org.nuxeo.ecm.automation.core.operations.notification.SendMail; 041import org.nuxeo.ecm.automation.core.scripting.Expression; 042import org.nuxeo.ecm.automation.core.scripting.Scripting; 043import org.nuxeo.ecm.automation.core.util.StringList; 044import org.nuxeo.ecm.automation.features.PlatformFunctions; 045import org.nuxeo.ecm.core.api.CoreSession; 046import org.nuxeo.ecm.core.api.DocumentModel; 047import org.nuxeo.ecm.core.api.NuxeoGroup; 048import org.nuxeo.ecm.core.api.NuxeoPrincipal; 049import org.nuxeo.ecm.core.api.security.ACE; 050import org.nuxeo.ecm.core.event.Event; 051import org.nuxeo.ecm.core.event.EventBundle; 052import org.nuxeo.ecm.core.event.EventContext; 053import org.nuxeo.ecm.core.event.PostCommitFilteringEventListener; 054import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 055import org.nuxeo.ecm.directory.Session; 056import org.nuxeo.ecm.directory.api.DirectoryService; 057import org.nuxeo.ecm.platform.ec.notification.service.NotificationService; 058import org.nuxeo.ecm.platform.ec.notification.service.NotificationServiceHelper; 059import org.nuxeo.ecm.platform.usermanager.UserManager; 060import org.nuxeo.ecm.tokenauth.service.TokenAuthenticationService; 061import org.nuxeo.runtime.api.Framework; 062 063/** 064 * Listener sending an email notification for a granted ACE. 065 * <p> 066 * This listener checks only if the ACE is granted. It assumes that other checks (such as the ACE becomes effective) 067 * have been done before. 068 * 069 * @since 7.4 070 */ 071public class PermissionGrantedNotificationListener implements PostCommitFilteringEventListener { 072 073 public static final String LABEL_SUBJECT_NEW_PERMISSION = "label.subject.new.permission"; 074 075 private static final Log log = LogFactory.getLog(PermissionGrantedNotificationListener.class); 076 077 public static final String SUBJECT_FORMAT = "%s %s"; 078 079 @Override 080 public void handleEvent(EventBundle events) { 081 for (Event event : events) { 082 handleEvent(event); 083 } 084 } 085 086 protected void handleEvent(Event event) { 087 EventContext eventCtx = event.getContext(); 088 if (!(eventCtx instanceof DocumentEventContext)) { 089 return; 090 } 091 092 DocumentEventContext docCtx = (DocumentEventContext) eventCtx; 093 CoreSession coreSession = docCtx.getCoreSession(); 094 DocumentModel doc = docCtx.getSourceDocument(); 095 if (doc == null || !coreSession.exists(doc.getRef())) { 096 return; 097 } 098 099 ACE ace = (ACE) docCtx.getProperty(ACE_KEY); 100 String aclName = (String) docCtx.getProperty(ACL_NAME_KEY); 101 if (ace == null || ace.isDenied() || aclName == null) { 102 return; 103 } 104 105 String username = ace.getUsername(); 106 StringList to = getRecipients(username); 107 if (to == null) { 108 // no recipient 109 return; 110 } 111 112 Expression from = Scripting.newExpression("Env[\"mail.from\"]"); 113 NotificationService notificationService = NotificationServiceHelper.getNotificationService(); 114 String subject = String.format(SUBJECT_FORMAT, notificationService.getEMailSubjectPrefix(), 115 I18NUtils.getMessageString("messages", LABEL_SUBJECT_NEW_PERMISSION, new Object[] { doc.getTitle() }, 116 Locale.ENGLISH)); 117 118 try (OperationContext ctx = new OperationContext(coreSession)) { 119 ctx.setInput(doc); 120 ctx.put("ace", ace); 121 122 Framework.doPrivileged(() -> { 123 DirectoryService directoryService = Framework.getService(DirectoryService.class); 124 try (Session session = directoryService.open(ACE_INFO_DIRECTORY)) { 125 String id = PermissionHelper.computeDirectoryId(doc, aclName, ace.getId()); 126 DocumentModel entry = session.getEntry(id); 127 if (entry != null) { 128 String comment = (String) entry.getPropertyValue(ACE_INFO_COMMENT); 129 if (comment != null) { 130 comment = StringEscapeUtils.escapeHtml4(comment); 131 comment = comment.replaceAll("\n", "<br/>"); 132 ctx.put("comment", comment); 133 } 134 } 135 } 136 }); 137 138 String aceCreator = ace.getCreator(); 139 if (aceCreator != null) { 140 UserManager userManager = Framework.getService(UserManager.class); 141 NuxeoPrincipal creator = userManager.getPrincipal(aceCreator); 142 if (creator != null) { 143 ctx.put("aceCreator", String.format("%s (%s)", principalFullName(creator), creator.getName())); 144 } 145 } 146 147 if (NuxeoPrincipal.isTransientUsername(username)) { 148 TokenAuthenticationService tokenAuthenticationService = Framework.getService( 149 TokenAuthenticationService.class); 150 String token = tokenAuthenticationService.getToken(username, doc.getRepositoryName(), doc.getId()); 151 if (token != null) { 152 ctx.put("token", token); 153 } 154 } 155 156 OperationChain chain = new OperationChain("SendMail"); 157 158 // if there is more than one recipient use "bcc:" instead of "to:" for avoiding email sharing 159 final boolean useBcc = to.size() > 1; 160 chain.add(SendMail.ID) 161 .set("from", from) 162 .set("to", useBcc ? null : to) 163 .set("bcc", useBcc ? to : null) 164 .set("HTML", true) 165 .set("subject", subject) 166 .set("message", ACE_GRANTED_TEMPLATE); 167 Framework.getService(AutomationService.class).run(ctx, chain); 168 } catch (OperationException e) { 169 log.warn("Unable to notify user", e); 170 log.debug(e, e); 171 } 172 } 173 174 // copied from org.nuxeo.ecm.platform.ui.web.tag.fn.Functions which lives in nuxeo-platform-ui-web 175 public static String principalFullName(NuxeoPrincipal principal) { 176 String first = principal.getFirstName(); 177 String last = principal.getLastName(); 178 return userDisplayName(principal.getName(), first, last); 179 } 180 181 public static String userDisplayName(String id, String first, String last) { 182 if (first == null || first.length() == 0) { 183 if (last == null || last.length() == 0) { 184 return id; 185 } else { 186 return last; 187 } 188 } else { 189 if (last == null || last.length() == 0) { 190 return first; 191 } else { 192 return first + ' ' + last; 193 } 194 } 195 } 196 197 protected StringList getRecipients(String username) { 198 UserManager userManager = Framework.getService(UserManager.class); 199 NuxeoPrincipal principal = userManager.getPrincipal(username); 200 StringList to = null; 201 if (principal != null) { 202 to = new StringList(Collections.singletonList(principal.getEmail())); 203 } else { 204 NuxeoGroup group = userManager.getGroup(username); 205 if (group != null) { 206 PlatformFunctions platformFunctions = new PlatformFunctions(); 207 to = platformFunctions.getEmailsFromGroup(group.getName()); 208 } 209 } 210 return to; 211 } 212 213 @Override 214 public boolean acceptEvent(Event event) { 215 String eventName = event.getName(); 216 return PERMISSION_NOTIFICATION_EVENT.equals(eventName); 217 } 218}