001/* 002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. 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-2.1.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 * 014 * Contributors: 015 * Thomas Roger 016 */ 017 018package org.nuxeo.ecm.permissions; 019 020import static org.nuxeo.ecm.core.api.event.CoreEventConstants.CHANGED_ACL_NAME; 021import static org.nuxeo.ecm.core.api.event.CoreEventConstants.NEW_ACE; 022import static org.nuxeo.ecm.core.api.event.CoreEventConstants.NEW_ACP; 023import static org.nuxeo.ecm.core.api.event.CoreEventConstants.OLD_ACE; 024import static org.nuxeo.ecm.core.api.event.CoreEventConstants.OLD_ACP; 025import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_SECURITY_UPDATED; 026import static org.nuxeo.ecm.permissions.Constants.ACE_INFO_COMMENT; 027import static org.nuxeo.ecm.permissions.Constants.ACE_INFO_DIRECTORY; 028import static org.nuxeo.ecm.permissions.Constants.ACE_INFO_NOTIFIED; 029import static org.nuxeo.ecm.permissions.Constants.ACE_INFO_NOTIFY; 030import static org.nuxeo.ecm.permissions.Constants.ACE_KEY; 031import static org.nuxeo.ecm.permissions.Constants.ACL_NAME_KEY; 032import static org.nuxeo.ecm.permissions.Constants.COMMENT_KEY; 033import static org.nuxeo.ecm.permissions.Constants.NOTIFY_KEY; 034import static org.nuxeo.ecm.permissions.Constants.PERMISSION_NOTIFICATION_EVENT; 035import static org.nuxeo.ecm.permissions.PermissionHelper.computeDirectoryId; 036 037import java.util.ArrayList; 038import java.util.Collections; 039import java.util.List; 040import java.util.Map; 041 042import org.nuxeo.ecm.core.api.DocumentModel; 043import org.nuxeo.ecm.core.api.security.ACE; 044import org.nuxeo.ecm.core.api.security.ACL; 045import org.nuxeo.ecm.core.api.security.ACP; 046import org.nuxeo.ecm.core.event.Event; 047import org.nuxeo.ecm.core.event.EventContext; 048import org.nuxeo.ecm.core.event.EventListener; 049import org.nuxeo.ecm.core.event.EventService; 050import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 051import org.nuxeo.ecm.directory.Session; 052import org.nuxeo.ecm.directory.api.DirectoryService; 053import org.nuxeo.runtime.api.Framework; 054 055/** 056 * Listener filling the 'aceinfo' directory when an ACP is updated. 057 * 058 * @since 7.4 059 */ 060public class PermissionListener implements EventListener { 061 062 @Override 063 public void handleEvent(Event event) { 064 EventContext ctx = event.getContext(); 065 if (!(ctx instanceof DocumentEventContext)) { 066 return; 067 } 068 069 if (DOCUMENT_SECURITY_UPDATED.equals(event.getName())) { 070 updateDirectory((DocumentEventContext) ctx); 071 } 072 } 073 074 protected void updateDirectory(DocumentEventContext docCtx) { 075 ACE oldACE = (ACE) docCtx.getProperty(OLD_ACE); 076 ACE newACE = (ACE) docCtx.getProperty(NEW_ACE); 077 String changedACLName = (String) docCtx.getProperty(CHANGED_ACL_NAME); 078 if (oldACE != null && newACE != null && changedACLName != null) { 079 handleReplaceACE(docCtx, changedACLName, oldACE, newACE); 080 } else { 081 ACP oldACP = (ACP) docCtx.getProperty(OLD_ACP); 082 ACP newACP = (ACP) docCtx.getProperty(NEW_ACP); 083 if (oldACP != null && newACP != null) { 084 handleUpdateACP(docCtx, oldACP, newACP); 085 } 086 } 087 } 088 089 protected void handleUpdateACP(DocumentEventContext docCtx, ACP oldACP, ACP newACP) { 090 DocumentModel doc = docCtx.getSourceDocument(); 091 092 List<ACLDiff> aclDiffs = extractACLDiffs(oldACP, newACP); 093 DirectoryService directoryService = Framework.getLocalService(DirectoryService.class); 094 for (ACLDiff diff : aclDiffs) { 095 try (Session session = directoryService.open(ACE_INFO_DIRECTORY)) { 096 for (ACE ace : diff.removedACEs) { 097 String id = computeDirectoryId(doc, diff.aclName, ace.getId()); 098 session.deleteEntry(id); 099 } 100 101 for (ACE ace : diff.addedACEs) { 102 String id = computeDirectoryId(doc, diff.aclName, ace.getId()); 103 // remove it if it exists 104 if (session.hasEntry(id)) { 105 session.deleteEntry(id); 106 } 107 108 Boolean notify = (Boolean) ace.getContextData(NOTIFY_KEY); 109 String comment = (String) ace.getContextData(Constants.COMMENT_KEY); 110 notify = notify != null ? notify : false; 111 Map<String, Object> m = PermissionHelper.createDirectoryEntry(doc, diff.aclName, ace, notify, false, 112 comment); 113 session.createEntry(m); 114 115 if (notify && ace.isGranted() && ace.isEffective()) { 116 firePermissionNotificationEvent(docCtx, diff.aclName, ace); 117 } 118 } 119 } 120 } 121 } 122 123 protected void handleReplaceACE(DocumentEventContext docCtx, String changedACLName, ACE oldACE, ACE newACE) { 124 DocumentModel doc = docCtx.getSourceDocument(); 125 126 DirectoryService directoryService = Framework.getLocalService(DirectoryService.class); 127 try (Session session = directoryService.open(ACE_INFO_DIRECTORY)) { 128 Boolean notify = (Boolean) newACE.getContextData(NOTIFY_KEY); 129 String comment = (String) newACE.getContextData(COMMENT_KEY); 130 boolean notified = false; 131 132 String oldId = computeDirectoryId(doc, changedACLName, oldACE.getId()); 133 DocumentModel oldEntry = session.getEntry(oldId); 134 if (oldEntry != null) { 135 boolean oldNotified = (boolean) oldEntry.getPropertyValue(ACE_INFO_NOTIFIED); 136 boolean oldNotify = (boolean) oldEntry.getPropertyValue(ACE_INFO_NOTIFY); 137 String oldComment = (String) oldEntry.getPropertyValue(ACE_INFO_COMMENT); 138 139 // put back notified to false if notify has changed 140 notified = !(notify != null && notify != oldNotify) && oldNotified; 141 // only use old notify and comment if not updated 142 if (notify == null) { 143 notify = oldNotify; 144 } 145 if (comment == null) { 146 comment = oldComment; 147 } 148 149 // remove the old entry 150 session.deleteEntry(oldId); 151 } 152 153 // add the new entry 154 notify = notify != null ? notify : false; 155 Map<String, Object> m = PermissionHelper.createDirectoryEntry(doc, changedACLName, newACE, notify, notified, 156 comment); 157 session.createEntry(m); 158 159 if (notify && newACE.isGranted() && newACE.isEffective() && !notified) { 160 firePermissionNotificationEvent(docCtx, changedACLName, newACE); 161 } 162 } 163 } 164 165 protected List<ACLDiff> extractACLDiffs(ACP oldACP, ACP newACP) { 166 List<ACLDiff> aclDiffs = new ArrayList<>(); 167 168 List<String> oldACLNames = toACLNames(oldACP); 169 List<String> newACLNames = toACLNames(newACP); 170 List<String> addedACLNames = toACLNames(newACP); 171 List<String> removedACLNames = toACLNames(oldACP); 172 173 addedACLNames.removeAll(oldACLNames); 174 removedACLNames.removeAll(newACLNames); 175 176 for (String name : addedACLNames) { 177 aclDiffs.add(new ACLDiff(name, new ArrayList<>(newACP.getACL(name)), null)); 178 } 179 180 for (String name : removedACLNames) { 181 aclDiffs.add(new ACLDiff(name, null, new ArrayList<>(oldACP.getACL(name)))); 182 } 183 184 for (ACL newACL : newACP.getACLs()) { 185 ACL oldACL = oldACP.getACL(newACL.getName()); 186 if (oldACL != null) { 187 List<ACE> addedACEs = new ArrayList<>(newACL); 188 List<ACE> removedACEs = new ArrayList<>(oldACL); 189 190 addedACEs.removeAll(oldACL); 191 removedACEs.removeAll(newACL); 192 aclDiffs.add(new ACLDiff(newACL.getName(), addedACEs, removedACEs)); 193 } 194 } 195 return aclDiffs; 196 } 197 198 protected List<String> toACLNames(ACP acp) { 199 List<String> aclNames = new ArrayList<>(); 200 for (ACL acl : acp.getACLs()) { 201 aclNames.add(acl.getName()); 202 } 203 return aclNames; 204 } 205 206 protected void firePermissionNotificationEvent(DocumentEventContext docCtx, String aclName, ACE ace) { 207 docCtx.setProperty(ACE_KEY, ace); 208 docCtx.setProperty(ACL_NAME_KEY, aclName); 209 EventService eventService = Framework.getService(EventService.class); 210 eventService.fireEvent(PERMISSION_NOTIFICATION_EVENT, docCtx); 211 212 } 213 214 private static class ACLDiff { 215 public final String aclName; 216 217 public final List<ACE> addedACEs; 218 219 public final List<ACE> removedACEs; 220 221 private ACLDiff(String aclName, List<ACE> addedACEs, List<ACE> removedACEs) { 222 this.aclName = aclName; 223 this.addedACEs = addedACEs != null ? addedACEs : Collections.emptyList(); 224 this.removedACEs = removedACEs != null ? removedACEs : Collections.emptyList(); 225 } 226 } 227}