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.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 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 Log log = LogFactory.getLog(NuxeoDriveFileSystemDeletionListener.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 if (log.isDebugEnabled()) { 078 log.debug(String.format("NuxeoDriveGroupUpdateListener handling %s event for group %s", event.getName(), 079 groupName)); 080 } 081 List<String> groupNames = getAllGroupNames(groupName, context); 082 handleUpdatedGroups(groupNames); 083 } 084 } 085 086 /** 087 * Returns a list containing the names of the given group and all its ancestor groups. 088 */ 089 @SuppressWarnings("unchecked") 090 protected List<String> getAllGroupNames(String groupName, EventContext context) { 091 List<String> groupNames = new ArrayList<>(); 092 groupNames.add(groupName); 093 // Get ancestor groups from the event context or compute them if not provided 094 // and do it as system user in the local thread to access group directory 095 LoginStack loginStack = ClientLoginModule.getThreadLocalLogin(); 096 loginStack.push(new SystemPrincipal(null), null, null); 097 try { 098 List<String> ancestorGroups = (List<String>) context.getProperty( 099 UserManagerImpl.ANCESTOR_GROUPS_PROPERTY_KEY); 100 if (ancestorGroups != null) { 101 groupNames.addAll(ancestorGroups); 102 } else { 103 groupNames.addAll(Framework.getService(UserManager.class).getAncestorGroups(groupName)); 104 } 105 } finally { 106 loginStack.pop(); 107 } 108 return groupNames; 109 } 110 111 protected void handleUpdatedGroups(List<String> groupNames) { 112 RepositoryManager repositoryManager = Framework.getService(RepositoryManager.class); 113 for (String repositoryName : repositoryManager.getRepositoryNames()) { 114 CoreInstance.doPrivileged(repositoryName, (CoreSession session) -> { 115 DocumentModelList impactedDocuments = getImpactedDocuments(session, groupNames); 116 impactedDocuments.forEach(doc -> fireGroupUpdatedEvent(session, doc)); 117 }); 118 } 119 } 120 121 /** 122 * Returns the list of documents carrying an ACL impacted by one of the given group names. 123 */ 124 protected DocumentModelList getImpactedDocuments(CoreSession session, List<String> groupNames) { 125 String groups = groupNames.stream().map(NXQL::escapeString).collect(Collectors.joining(",")); 126 String query = "SELECT * FROM Document WHERE ecm:currentLifeCycleState != 'deleted' AND ecm:isVersion = 0 AND ecm:acl/*/principal IN (" 127 + groups + ")"; 128 return session.query(query); 129 } 130 131 protected void fireGroupUpdatedEvent(CoreSession session, DocumentModel source) { 132 EventContext context = new DocumentEventContext(session, session.getPrincipal(), source); 133 Event event = context.newEvent(NuxeoDriveEvents.GROUP_UPDATED); 134 Framework.getService(EventService.class).fireEvent(event); 135 } 136 137}