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