/*
* (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Thomas Roger
*/
package org.nuxeo.ecm.permissions;
import static org.nuxeo.ecm.core.api.event.CoreEventConstants.NEW_ACP;
import static org.nuxeo.ecm.core.api.event.CoreEventConstants.OLD_ACP;
import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_SECURITY_UPDATED;
import static org.nuxeo.ecm.permissions.Constants.ACE_INFO_DIRECTORY;
import static org.nuxeo.ecm.permissions.Constants.ACE_KEY;
import static org.nuxeo.ecm.permissions.Constants.ACL_NAME_KEY;
import static org.nuxeo.ecm.permissions.Constants.COMMENT_KEY;
import static org.nuxeo.ecm.permissions.Constants.NOTIFY_KEY;
import static org.nuxeo.ecm.permissions.Constants.PERMISSION_NOTIFICATION_EVENT;
import static org.nuxeo.ecm.permissions.PermissionHelper.computeDirectoryId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.NuxeoException;
import org.nuxeo.ecm.core.api.security.ACE;
import org.nuxeo.ecm.core.api.security.ACL;
import org.nuxeo.ecm.core.api.security.ACP;
import org.nuxeo.ecm.core.event.Event;
import org.nuxeo.ecm.core.event.EventContext;
import org.nuxeo.ecm.core.event.EventListener;
import org.nuxeo.ecm.core.event.EventService;
import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
import org.nuxeo.ecm.directory.Session;
import org.nuxeo.ecm.directory.api.DirectoryService;
import org.nuxeo.runtime.api.Framework;
/**
* Listener filling the 'aceinfo' directory when an ACP is updated.
*
* @since 7.4
*/
public class PermissionListener implements EventListener {
@Override
public void handleEvent(Event event) {
EventContext ctx = event.getContext();
if (!(ctx instanceof DocumentEventContext)) {
return;
}
if (DOCUMENT_SECURITY_UPDATED.equals(event.getName())) {
updateDirectory((DocumentEventContext) ctx);
}
}
protected void updateDirectory(DocumentEventContext docCtx) {
ACP oldACP = (ACP) docCtx.getProperty(OLD_ACP);
ACP newACP = (ACP) docCtx.getProperty(NEW_ACP);
if (oldACP != null && newACP != null) {
handleUpdateACP(docCtx, oldACP, newACP);
}
}
protected void handleUpdateACP(DocumentEventContext docCtx, ACP oldACP, ACP newACP) {
Framework.doPrivileged(() -> {
DocumentModel doc = docCtx.getSourceDocument();
List<ACLDiff> aclDiffs = extractACLDiffs(oldACP, newACP);
DirectoryService directoryService = Framework.getLocalService(DirectoryService.class);
for (ACLDiff diff : aclDiffs) {
try (Session session = directoryService.open(ACE_INFO_DIRECTORY)) {
for (ACE ace : diff.removedACEs) {
String id = computeDirectoryId(doc, diff.aclName, ace.getId());
session.deleteEntry(id);
removeToken(doc, ace);
}
for (ACE ace : diff.addedACEs) {
String id = computeDirectoryId(doc, diff.aclName, ace.getId());
// remove it if it exists
if (session.hasEntry(id)) {
session.deleteEntry(id);
}
Boolean notify = (Boolean) ace.getContextData(NOTIFY_KEY);
String comment = (String) ace.getContextData(Constants.COMMENT_KEY);
notify = notify != null ? notify : false;
Map<String, Object> m = PermissionHelper.createDirectoryEntry(doc, diff.aclName, ace, notify,
comment);
session.createEntry(m);
addToken(doc, ace);
if (notify && ace.isGranted() && ace.isEffective()) {
firePermissionNotificationEvent(docCtx, diff.aclName, ace);
}
}
}
}
});
}
/**
* @deprecated since 8.1. Not used anymore.
*/
@Deprecated
protected void handleReplaceACE(DocumentEventContext docCtx, String changedACLName, ACE oldACE, ACE newACE) {
Framework.doPrivileged(() -> {
DocumentModel doc = docCtx.getSourceDocument();
DirectoryService directoryService = Framework.getLocalService(DirectoryService.class);
try (Session session = directoryService.open(ACE_INFO_DIRECTORY)) {
Boolean notify = (Boolean) newACE.getContextData(NOTIFY_KEY);
String comment = (String) newACE.getContextData(COMMENT_KEY);
String oldId = computeDirectoryId(doc, changedACLName, oldACE.getId());
DocumentModel oldEntry = session.getEntry(oldId);
if (oldEntry != null) {
// remove the old entry
session.deleteEntry(oldId);
}
// add the new entry
notify = notify != null ? notify : false;
Map<String, Object> m = PermissionHelper.createDirectoryEntry(doc, changedACLName, newACE, notify,
comment);
session.createEntry(m);
if (notify && newACE.isGranted() && newACE.isEffective()) {
firePermissionNotificationEvent(docCtx, changedACLName, newACE);
}
}
});
}
protected List<ACLDiff> extractACLDiffs(ACP oldACP, ACP newACP) {
List<ACLDiff> aclDiffs = new ArrayList<>();
List<String> oldACLNames = toACLNames(oldACP);
List<String> newACLNames = toACLNames(newACP);
List<String> addedACLNames = toACLNames(newACP);
List<String> removedACLNames = toACLNames(oldACP);
addedACLNames.removeAll(oldACLNames);
removedACLNames.removeAll(newACLNames);
for (String name : addedACLNames) {
aclDiffs.add(new ACLDiff(name, new ArrayList<>(newACP.getACL(name)), null));
}
for (String name : removedACLNames) {
aclDiffs.add(new ACLDiff(name, null, new ArrayList<>(oldACP.getACL(name))));
}
for (ACL newACL : newACP.getACLs()) {
ACL oldACL = oldACP.getACL(newACL.getName());
if (oldACL != null) {
List<ACE> addedACEs = new ArrayList<>(newACL);
List<ACE> removedACEs = new ArrayList<>(oldACL);
addedACEs.removeAll(oldACL);
removedACEs.removeAll(newACL);
aclDiffs.add(new ACLDiff(newACL.getName(), addedACEs, removedACEs));
}
}
return aclDiffs;
}
protected List<String> toACLNames(ACP acp) {
List<String> aclNames = new ArrayList<>();
for (ACL acl : acp.getACLs()) {
aclNames.add(acl.getName());
}
return aclNames;
}
protected void firePermissionNotificationEvent(DocumentEventContext docCtx, String aclName, ACE ace) {
docCtx.setProperty(ACE_KEY, ace);
docCtx.setProperty(ACL_NAME_KEY, aclName);
EventService eventService = Framework.getService(EventService.class);
eventService.fireEvent(PERMISSION_NOTIFICATION_EVENT, docCtx);
}
protected void addToken(DocumentModel doc, ACE ace) {
if (!ace.isArchived()) {
TransientUserPermissionHelper.acquireToken(ace.getUsername(), doc, ace.getPermission());
}
}
protected void removeToken(DocumentModel doc, ACE deletedAce) {
TransientUserPermissionHelper.revokeToken(deletedAce.getUsername(), doc);
}
private static class ACLDiff {
public final String aclName;
public final List<ACE> addedACEs;
public final List<ACE> removedACEs;
private ACLDiff(String aclName, List<ACE> addedACEs, List<ACE> removedACEs) {
this.aclName = aclName;
this.addedACEs = addedACEs != null ? addedACEs : Collections.emptyList();
this.removedACEs = removedACEs != null ? removedACEs : Collections.emptyList();
}
}
}