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 javax.security.auth.login.LoginContext; 039import javax.security.auth.login.LoginException; 040 041import org.nuxeo.ecm.core.api.DocumentModel; 042import org.nuxeo.ecm.core.api.NuxeoException; 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 ACP oldACP = (ACP) docCtx.getProperty(OLD_ACP); 076 ACP newACP = (ACP) docCtx.getProperty(NEW_ACP); 077 if (oldACP != null && newACP != null) { 078 handleUpdateACP(docCtx, oldACP, newACP); 079 } 080 } 081 082 protected void doAsSystemUser(Runnable runnable) { 083 LoginContext loginContext; 084 try { 085 loginContext = Framework.login(); 086 } catch (LoginException e) { 087 throw new NuxeoException(e); 088 } 089 090 try { 091 runnable.run(); 092 } finally { 093 try { 094 // Login context may be null in tests 095 if (loginContext != null) { 096 loginContext.logout(); 097 } 098 } catch (LoginException e) { 099 throw new NuxeoException("Cannot log out system user", e); 100 } 101 } 102 } 103 104 protected void handleUpdateACP(DocumentEventContext docCtx, ACP oldACP, ACP newACP) { 105 doAsSystemUser(() -> { 106 DocumentModel doc = docCtx.getSourceDocument(); 107 List<ACLDiff> aclDiffs = extractACLDiffs(oldACP, newACP); 108 DirectoryService directoryService = Framework.getLocalService(DirectoryService.class); 109 for (ACLDiff diff : aclDiffs) { 110 try (Session session = directoryService.open(ACE_INFO_DIRECTORY)) { 111 for (ACE ace : diff.removedACEs) { 112 String id = computeDirectoryId(doc, diff.aclName, ace.getId()); 113 session.deleteEntry(id); 114 115 removeToken(doc, ace); 116 } 117 118 for (ACE ace : diff.addedACEs) { 119 String id = computeDirectoryId(doc, diff.aclName, ace.getId()); 120 // remove it if it exists 121 if (session.hasEntry(id)) { 122 session.deleteEntry(id); 123 } 124 125 Boolean notify = (Boolean) ace.getContextData(NOTIFY_KEY); 126 String comment = (String) ace.getContextData(Constants.COMMENT_KEY); 127 notify = notify != null ? notify : false; 128 Map<String, Object> m = PermissionHelper.createDirectoryEntry(doc, diff.aclName, ace, notify, 129 comment); 130 session.createEntry(m); 131 132 addToken(doc, ace); 133 134 if (notify && ace.isGranted() && ace.isEffective()) { 135 firePermissionNotificationEvent(docCtx, diff.aclName, ace); 136 } 137 } 138 } 139 } 140 }); 141 } 142 143 /** 144 * @deprecated since 8.1. Not used anymore. 145 */ 146 @Deprecated 147 protected void handleReplaceACE(DocumentEventContext docCtx, String changedACLName, ACE oldACE, ACE newACE) { 148 doAsSystemUser(() -> { 149 DocumentModel doc = docCtx.getSourceDocument(); 150 151 DirectoryService directoryService = Framework.getLocalService(DirectoryService.class); 152 try (Session session = directoryService.open(ACE_INFO_DIRECTORY)) { 153 Boolean notify = (Boolean) newACE.getContextData(NOTIFY_KEY); 154 String comment = (String) newACE.getContextData(COMMENT_KEY); 155 156 String oldId = computeDirectoryId(doc, changedACLName, oldACE.getId()); 157 DocumentModel oldEntry = session.getEntry(oldId); 158 if (oldEntry != null) { 159 // remove the old entry 160 session.deleteEntry(oldId); 161 } 162 163 // add the new entry 164 notify = notify != null ? notify : false; 165 Map<String, Object> m = PermissionHelper.createDirectoryEntry(doc, changedACLName, newACE, notify, 166 comment); 167 session.createEntry(m); 168 169 if (notify && newACE.isGranted() && newACE.isEffective()) { 170 firePermissionNotificationEvent(docCtx, changedACLName, newACE); 171 } 172 } 173 }); 174 } 175 176 protected List<ACLDiff> extractACLDiffs(ACP oldACP, ACP newACP) { 177 List<ACLDiff> aclDiffs = new ArrayList<>(); 178 179 List<String> oldACLNames = toACLNames(oldACP); 180 List<String> newACLNames = toACLNames(newACP); 181 List<String> addedACLNames = toACLNames(newACP); 182 List<String> removedACLNames = toACLNames(oldACP); 183 184 addedACLNames.removeAll(oldACLNames); 185 removedACLNames.removeAll(newACLNames); 186 187 for (String name : addedACLNames) { 188 aclDiffs.add(new ACLDiff(name, new ArrayList<>(newACP.getACL(name)), null)); 189 } 190 191 for (String name : removedACLNames) { 192 aclDiffs.add(new ACLDiff(name, null, new ArrayList<>(oldACP.getACL(name)))); 193 } 194 195 for (ACL newACL : newACP.getACLs()) { 196 ACL oldACL = oldACP.getACL(newACL.getName()); 197 if (oldACL != null) { 198 List<ACE> addedACEs = new ArrayList<>(newACL); 199 List<ACE> removedACEs = new ArrayList<>(oldACL); 200 201 addedACEs.removeAll(oldACL); 202 removedACEs.removeAll(newACL); 203 aclDiffs.add(new ACLDiff(newACL.getName(), addedACEs, removedACEs)); 204 } 205 } 206 return aclDiffs; 207 } 208 209 protected List<String> toACLNames(ACP acp) { 210 List<String> aclNames = new ArrayList<>(); 211 for (ACL acl : acp.getACLs()) { 212 aclNames.add(acl.getName()); 213 } 214 return aclNames; 215 } 216 217 protected void firePermissionNotificationEvent(DocumentEventContext docCtx, String aclName, ACE ace) { 218 docCtx.setProperty(ACE_KEY, ace); 219 docCtx.setProperty(ACL_NAME_KEY, aclName); 220 EventService eventService = Framework.getService(EventService.class); 221 eventService.fireEvent(PERMISSION_NOTIFICATION_EVENT, docCtx); 222 } 223 224 protected void addToken(DocumentModel doc, ACE ace) { 225 if (!ace.isArchived()) { 226 TransientUserPermissionHelper.acquireToken(ace.getUsername(), doc, ace.getPermission()); 227 } 228 } 229 230 protected void removeToken(DocumentModel doc, ACE deletedAce) { 231 TransientUserPermissionHelper.revokeToken(deletedAce.getUsername(), doc); 232 } 233 234 private static class ACLDiff { 235 public final String aclName; 236 237 public final List<ACE> addedACEs; 238 239 public final List<ACE> removedACEs; 240 241 private ACLDiff(String aclName, List<ACE> addedACEs, List<ACE> removedACEs) { 242 this.aclName = aclName; 243 this.addedACEs = addedACEs != null ? addedACEs : Collections.emptyList(); 244 this.removedACEs = removedACEs != null ? removedACEs : Collections.emptyList(); 245 } 246 } 247}