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