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