001/* 002 * (C) Copyright 2017 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 * Antoine Taillefer <ataillefer@nuxeo.com> 018 */ 019package org.nuxeo.drive.listener; 020 021import java.util.ArrayList; 022import java.util.List; 023import java.util.stream.Collectors; 024 025import org.apache.logging.log4j.LogManager; 026import org.apache.logging.log4j.Logger; 027import org.nuxeo.drive.service.NuxeoDriveEvents; 028import org.nuxeo.ecm.core.api.CoreInstance; 029import org.nuxeo.ecm.core.api.CoreSession; 030import org.nuxeo.ecm.core.api.DocumentModel; 031import org.nuxeo.ecm.core.api.DocumentModelList; 032import org.nuxeo.ecm.core.api.SystemPrincipal; 033import org.nuxeo.ecm.core.api.local.ClientLoginModule; 034import org.nuxeo.ecm.core.api.local.LoginStack; 035import org.nuxeo.ecm.core.api.repository.RepositoryManager; 036import org.nuxeo.ecm.core.event.Event; 037import org.nuxeo.ecm.core.event.EventBundle; 038import org.nuxeo.ecm.core.event.EventContext; 039import org.nuxeo.ecm.core.event.EventService; 040import org.nuxeo.ecm.core.event.PostCommitFilteringEventListener; 041import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 042import org.nuxeo.ecm.core.query.sql.NXQL; 043import org.nuxeo.ecm.platform.usermanager.UserManager; 044import org.nuxeo.ecm.platform.usermanager.UserManagerImpl; 045import org.nuxeo.runtime.api.Framework; 046 047/** 048 * Post-commit asynchronous listener that handles group change events fired by the {@link UserManager}. 049 * <p> 050 * For all the documents carrying an ACL impacted by a changed group or one of its ancestors it fires the 051 * {@link NuxeoDriveEvents#GROUP_UPDATED} event that is handled by the synchronous 052 * {@link NuxeoDriveFileSystemDeletionListener}. 053 * 054 * @since 9.2 055 */ 056public class NuxeoDriveGroupUpdateListener implements PostCommitFilteringEventListener { 057 058 protected static final Logger log = LogManager.getLogger(NuxeoDriveGroupUpdateListener.class); 059 060 @Override 061 public boolean acceptEvent(Event event) { 062 return event.getContext() != null && UserManagerImpl.USER_GROUP_CATEGORY.equals( 063 event.getContext().getProperty(DocumentEventContext.CATEGORY_PROPERTY_KEY)); 064 } 065 066 @Override 067 public void handleEvent(EventBundle events) { 068 for (Event event : events) { 069 EventContext context = event.getContext(); 070 if (context == null) { 071 continue; 072 } 073 String groupName = (String) context.getProperty(UserManagerImpl.ID_PROPERTY_KEY); 074 if (groupName == null) { 075 continue; 076 } 077 log.debug("NuxeoDriveGroupUpdateListener handling {} event for group {}", event::getName, () -> groupName); 078 List<String> groupNames = getAllGroupNames(groupName, context); 079 handleUpdatedGroups(groupNames); 080 } 081 } 082 083 /** 084 * Returns a list containing the names of the given group and all its ancestor groups. 085 */ 086 @SuppressWarnings("unchecked") 087 protected List<String> getAllGroupNames(String groupName, EventContext context) { 088 List<String> groupNames = new ArrayList<>(); 089 groupNames.add(groupName); 090 // Get ancestor groups from the event context or compute them if not provided 091 // and do it as system user in the local thread to access group directory 092 LoginStack loginStack = ClientLoginModule.getThreadLocalLogin(); 093 loginStack.push(new SystemPrincipal(null), null, null); 094 try { 095 List<String> ancestorGroups = (List<String>) context.getProperty( 096 UserManagerImpl.ANCESTOR_GROUPS_PROPERTY_KEY); 097 if (ancestorGroups != null) { 098 groupNames.addAll(ancestorGroups); 099 } else { 100 groupNames.addAll(Framework.getService(UserManager.class).getAncestorGroups(groupName)); 101 } 102 } finally { 103 loginStack.pop(); 104 } 105 return groupNames; 106 } 107 108 protected void handleUpdatedGroups(List<String> groupNames) { 109 RepositoryManager repositoryManager = Framework.getService(RepositoryManager.class); 110 for (String repositoryName : repositoryManager.getRepositoryNames()) { 111 CoreInstance.doPrivileged(repositoryName, (CoreSession session) -> { 112 DocumentModelList impactedDocuments = getImpactedDocuments(session, groupNames); 113 impactedDocuments.forEach(doc -> fireGroupUpdatedEvent(session, doc)); 114 }); 115 } 116 } 117 118 /** 119 * Returns the list of documents carrying an ACL impacted by one of the given group names. 120 */ 121 protected DocumentModelList getImpactedDocuments(CoreSession session, List<String> groupNames) { 122 String groups = groupNames.stream().map(NXQL::escapeString).collect(Collectors.joining(",")); 123 String query = "SELECT * FROM Document WHERE ecm:isTrashed = 0 AND ecm:isVersion = 0 AND ecm:acl/*/principal IN (" 124 + groups + ")"; 125 return session.query(query); 126 } 127 128 protected void fireGroupUpdatedEvent(CoreSession session, DocumentModel source) { 129 EventContext context = new DocumentEventContext(session, session.getPrincipal(), source); 130 Event event = context.newEvent(NuxeoDriveEvents.GROUP_UPDATED); 131 Framework.getService(EventService.class).fireEvent(event); 132 } 133 134}