mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
[MNT-24807] repo event2 is exposing user password hash and salt (#3147)
* [MNT-24807] Implemented PropertyReplacer that replaces values of sensitive properties (e.g. passwords) during creation of NodeResource for event2
* [MNT-24807] Fix failing tests
* Revert "[MNT-24807] Fix failing tests"
This reverts commit c118f713f2
.
* [MNT-24807] Fix failing tests without reformat
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
* [MNT-24807] Introduced interface to keep convention
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
* [MNT-24807] Added ability to configure property filter and mapper for user
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
* [MNT-24807] Fixed npe and pmd issues
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
* [MNT-24807] Fixed more pmd comments, applied pre-commit formatting
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
* [MNT-24807] Renamed user configured properties to indicate what they do, added failsafe when userConfiguredReplacementText is not configured at all
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
* [MNT-24807] Added unit tests
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
* [MNT-24807] Additional config to disable property mapper entirely
* [MNT-24807] PMD again
* [MNT-24807] Updated year in licence for some files I missed
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
---------
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -42,6 +42,9 @@ import java.util.Set;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
import org.alfresco.repo.event.v1.model.ContentInfo;
|
import org.alfresco.repo.event.v1.model.ContentInfo;
|
||||||
@@ -50,6 +53,7 @@ import org.alfresco.repo.event.v1.model.UserInfo;
|
|||||||
import org.alfresco.repo.event2.filter.EventFilterRegistry;
|
import org.alfresco.repo.event2.filter.EventFilterRegistry;
|
||||||
import org.alfresco.repo.event2.filter.NodeAspectFilter;
|
import org.alfresco.repo.event2.filter.NodeAspectFilter;
|
||||||
import org.alfresco.repo.event2.filter.NodePropertyFilter;
|
import org.alfresco.repo.event2.filter.NodePropertyFilter;
|
||||||
|
import org.alfresco.repo.event2.mapper.PropertyMapper;
|
||||||
import org.alfresco.repo.node.MLPropertyInterceptor;
|
import org.alfresco.repo.node.MLPropertyInterceptor;
|
||||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||||
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
||||||
@@ -68,9 +72,6 @@ import org.alfresco.service.namespace.NamespaceService;
|
|||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.alfresco.util.PathUtil;
|
import org.alfresco.util.PathUtil;
|
||||||
import org.alfresco.util.PropertyCheck;
|
import org.alfresco.util.PropertyCheck;
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper for {@link NodeResource} objects.
|
* Helper for {@link NodeResource} objects.
|
||||||
@@ -81,14 +82,15 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
{
|
{
|
||||||
private static final Log LOGGER = LogFactory.getLog(NodeResourceHelper.class);
|
private static final Log LOGGER = LogFactory.getLog(NodeResourceHelper.class);
|
||||||
|
|
||||||
protected NodeService nodeService;
|
protected NodeService nodeService;
|
||||||
protected DictionaryService dictionaryService;
|
protected DictionaryService dictionaryService;
|
||||||
protected PersonService personService;
|
protected PersonService personService;
|
||||||
protected EventFilterRegistry eventFilterRegistry;
|
protected EventFilterRegistry eventFilterRegistry;
|
||||||
protected NamespaceService namespaceService;
|
protected NamespaceService namespaceService;
|
||||||
protected PermissionService permissionService;
|
protected PermissionService permissionService;
|
||||||
|
protected PropertyMapper propertyMapper;
|
||||||
|
|
||||||
private NodeAspectFilter nodeAspectFilter;
|
private NodeAspectFilter nodeAspectFilter;
|
||||||
private NodePropertyFilter nodePropertyFilter;
|
private NodePropertyFilter nodePropertyFilter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -100,6 +102,7 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
PropertyCheck.mandatory(this, "eventFilterRegistry", eventFilterRegistry);
|
PropertyCheck.mandatory(this, "eventFilterRegistry", eventFilterRegistry);
|
||||||
PropertyCheck.mandatory(this, "namespaceService", namespaceService);
|
PropertyCheck.mandatory(this, "namespaceService", namespaceService);
|
||||||
PropertyCheck.mandatory(this, "permissionService", permissionService);
|
PropertyCheck.mandatory(this, "permissionService", permissionService);
|
||||||
|
PropertyCheck.mandatory(this, "propertyMapper", propertyMapper);
|
||||||
|
|
||||||
this.nodeAspectFilter = eventFilterRegistry.getNodeAspectFilter();
|
this.nodeAspectFilter = eventFilterRegistry.getNodeAspectFilter();
|
||||||
this.nodePropertyFilter = eventFilterRegistry.getNodePropertyFilter();
|
this.nodePropertyFilter = eventFilterRegistry.getNodePropertyFilter();
|
||||||
@@ -124,7 +127,7 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
{
|
{
|
||||||
this.permissionService = permissionService;
|
this.permissionService = permissionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// To make IntelliJ stop complaining about unused method!
|
// To make IntelliJ stop complaining about unused method!
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public void setEventFilterRegistry(EventFilterRegistry eventFilterRegistry)
|
public void setEventFilterRegistry(EventFilterRegistry eventFilterRegistry)
|
||||||
@@ -137,6 +140,11 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
this.namespaceService = namespaceService;
|
this.namespaceService = namespaceService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPropertyMapper(PropertyMapper propertyMapper)
|
||||||
|
{
|
||||||
|
this.propertyMapper = propertyMapper;
|
||||||
|
}
|
||||||
|
|
||||||
public NodeResource.Builder createNodeResourceBuilder(NodeRef nodeRef)
|
public NodeResource.Builder createNodeResourceBuilder(NodeRef nodeRef)
|
||||||
{
|
{
|
||||||
final QName type = nodeService.getType(nodeRef);
|
final QName type = nodeService.getType(nodeRef);
|
||||||
@@ -148,22 +156,22 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
Map<String, UserInfo> mapUserCache = new HashMap<>(2);
|
Map<String, UserInfo> mapUserCache = new HashMap<>(2);
|
||||||
|
|
||||||
return NodeResource.builder()
|
return NodeResource.builder()
|
||||||
.setId(nodeRef.getId())
|
.setId(nodeRef.getId())
|
||||||
.setName((String) properties.get(ContentModel.PROP_NAME))
|
.setName((String) properties.get(ContentModel.PROP_NAME))
|
||||||
.setNodeType(getQNamePrefixString(type))
|
.setNodeType(getQNamePrefixString(type))
|
||||||
.setIsFile(isSubClass(type, ContentModel.TYPE_CONTENT))
|
.setIsFile(isSubClass(type, ContentModel.TYPE_CONTENT))
|
||||||
.setIsFolder(isSubClass(type, ContentModel.TYPE_FOLDER))
|
.setIsFolder(isSubClass(type, ContentModel.TYPE_FOLDER))
|
||||||
.setCreatedByUser(getUserInfo((String) properties.get(ContentModel.PROP_CREATOR), mapUserCache))
|
.setCreatedByUser(getUserInfo((String) properties.get(ContentModel.PROP_CREATOR), mapUserCache))
|
||||||
.setCreatedAt(getZonedDateTime((Date)properties.get(ContentModel.PROP_CREATED)))
|
.setCreatedAt(getZonedDateTime((Date) properties.get(ContentModel.PROP_CREATED)))
|
||||||
.setModifiedByUser(getUserInfo((String) properties.get(ContentModel.PROP_MODIFIER), mapUserCache))
|
.setModifiedByUser(getUserInfo((String) properties.get(ContentModel.PROP_MODIFIER), mapUserCache))
|
||||||
.setModifiedAt(getZonedDateTime((Date)properties.get(ContentModel.PROP_MODIFIED)))
|
.setModifiedAt(getZonedDateTime((Date) properties.get(ContentModel.PROP_MODIFIED)))
|
||||||
.setContent(getContentInfo(properties))
|
.setContent(getContentInfo(properties))
|
||||||
.setPrimaryAssocQName(getPrimaryAssocQName(nodeRef))
|
.setPrimaryAssocQName(getPrimaryAssocQName(nodeRef))
|
||||||
.setPrimaryHierarchy(PathUtil.getNodeIdsInReverse(path, false))
|
.setPrimaryHierarchy(PathUtil.getNodeIdsInReverse(path, false))
|
||||||
.setProperties(mapToNodeProperties(properties))
|
.setProperties(mapToNodeProperties(properties))
|
||||||
.setLocalizedProperties(mapToNodeLocalizedProperties(properties))
|
.setLocalizedProperties(mapToNodeLocalizedProperties(properties))
|
||||||
.setAspectNames(getMappedAspects(nodeRef))
|
.setAspectNames(getMappedAspects(nodeRef))
|
||||||
.setSecondaryParents(getSecondaryParents(nodeRef));
|
.setSecondaryParents(getSecondaryParents(nodeRef));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSubClass(QName className, QName ofClassQName)
|
private boolean isSubClass(QName className, QName ofClassQName)
|
||||||
@@ -171,17 +179,18 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
return dictionaryService.isSubClass(className, ofClassQName);
|
return dictionaryService.isSubClass(className, ofClassQName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getPrimaryAssocQName(NodeRef nodeRef)
|
private String getPrimaryAssocQName(NodeRef nodeRef)
|
||||||
{
|
{
|
||||||
String result = null;
|
String result = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ChildAssociationRef primaryParent = nodeService.getPrimaryParent(nodeRef);
|
ChildAssociationRef primaryParent = nodeService.getPrimaryParent(nodeRef);
|
||||||
if(primaryParent != null && primaryParent.getQName() != null)
|
if (primaryParent != null && primaryParent.getQName() != null)
|
||||||
{
|
{
|
||||||
result = primaryParent.getQName().getPrefixedQName(namespaceService).getPrefixString();
|
result = primaryParent.getQName().getPrefixedQName(namespaceService).getPrefixString();
|
||||||
}
|
}
|
||||||
} catch (NamespaceException namespaceException)
|
}
|
||||||
|
catch (NamespaceException namespaceException)
|
||||||
{
|
{
|
||||||
LOGGER.error("Cannot return a valid primary association QName: " + namespaceException.getMessage());
|
LOGGER.error("Cannot return a valid primary association QName: " + namespaceException.getMessage());
|
||||||
}
|
}
|
||||||
@@ -215,8 +224,8 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
{
|
{
|
||||||
v = ((MLText) v).getDefaultValue();
|
v = ((MLText) v).getDefaultValue();
|
||||||
}
|
}
|
||||||
|
Serializable mappedValue = propertyMapper.map(k, v);
|
||||||
filteredProps.put(getQNamePrefixString(k), v);
|
filteredProps.put(getQNamePrefixString(k), mappedValue);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -232,7 +241,10 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
{
|
{
|
||||||
final MLText mlTextValue = (MLText) v;
|
final MLText mlTextValue = (MLText) v;
|
||||||
final HashMap<String, String> localizedValues = new HashMap<>(mlTextValue.size());
|
final HashMap<String, String> localizedValues = new HashMap<>(mlTextValue.size());
|
||||||
mlTextValue.forEach((locale, text) -> localizedValues.put(locale.toString(), text));
|
mlTextValue.forEach((locale, text) -> {
|
||||||
|
Serializable mappedValue = propertyMapper.map(k, text);
|
||||||
|
localizedValues.put(locale.toString(), (String) mappedValue);
|
||||||
|
});
|
||||||
filteredProps.put(getQNamePrefixString(k), localizedValues);
|
filteredProps.put(getQNamePrefixString(k), localizedValues);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -259,7 +271,7 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
{
|
{
|
||||||
String sysUserName = AuthenticationUtil.getSystemUserName();
|
String sysUserName = AuthenticationUtil.getSystemUserName();
|
||||||
if (userName.equals(sysUserName) || (AuthenticationUtil.isMtEnabled()
|
if (userName.equals(sysUserName) || (AuthenticationUtil.isMtEnabled()
|
||||||
&& userName.startsWith(sysUserName + "@")))
|
&& userName.startsWith(sysUserName + "@")))
|
||||||
{
|
{
|
||||||
userInfo = new UserInfo(userName, userName, "");
|
userInfo = new UserInfo(userName, userName, "");
|
||||||
}
|
}
|
||||||
@@ -306,11 +318,11 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the QName in the format prefix:local, but in the exceptional case where there is no registered prefix
|
* Returns the QName in the format prefix:local, but in the exceptional case where there is no registered prefix returns it in the form {uri}local.
|
||||||
* returns it in the form {uri}local.
|
|
||||||
*
|
*
|
||||||
* @param k QName
|
* @param k
|
||||||
* @return a String representing the QName in the format prefix:local or {uri}local.
|
* QName
|
||||||
|
* @return a String representing the QName in the format prefix:local or {uri}local.
|
||||||
*/
|
*/
|
||||||
public String getQNamePrefixString(QName k)
|
public String getQNamePrefixString(QName k)
|
||||||
{
|
{
|
||||||
@@ -342,7 +354,7 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
|
|
||||||
public QName getNodeType(NodeRef nodeRef)
|
public QName getNodeType(NodeRef nodeRef)
|
||||||
{
|
{
|
||||||
return nodeService.getType(nodeRef);
|
return nodeService.getType(nodeRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Serializable getProperty(NodeRef nodeRef, QName qName)
|
public Serializable getProperty(NodeRef nodeRef, QName qName)
|
||||||
@@ -352,13 +364,14 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
|
|
||||||
public Map<QName, Serializable> getProperties(NodeRef nodeRef)
|
public Map<QName, Serializable> getProperties(NodeRef nodeRef)
|
||||||
{
|
{
|
||||||
//We need to have full MLText properties here. This is why we are marking the current thread as MLAware
|
// We need to have full MLText properties here. This is why we are marking the current thread as MLAware
|
||||||
final boolean toRestore = MLPropertyInterceptor.isMLAware();
|
final boolean toRestore = MLPropertyInterceptor.isMLAware();
|
||||||
MLPropertyInterceptor.setMLAware(true);
|
MLPropertyInterceptor.setMLAware(true);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return nodeService.getProperties(nodeRef);
|
return nodeService.getProperties(nodeRef);
|
||||||
} finally
|
}
|
||||||
|
finally
|
||||||
{
|
{
|
||||||
MLPropertyInterceptor.setMLAware(toRestore);
|
MLPropertyInterceptor.setMLAware(toRestore);
|
||||||
}
|
}
|
||||||
@@ -377,7 +390,7 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Map<String, Map<String, String>> getLocalizedPropertiesBefore(Map<String, Map<String, String>> locPropsBefore,
|
static Map<String, Map<String, String>> getLocalizedPropertiesBefore(Map<String, Map<String, String>> locPropsBefore,
|
||||||
Map<String, Map<String, String>> locPropsAfter)
|
Map<String, Map<String, String>> locPropsAfter)
|
||||||
{
|
{
|
||||||
final Map<String, Map<String, String>> result = new HashMap<>(locPropsBefore.size());
|
final Map<String, Map<String, String>> result = new HashMap<>(locPropsBefore.size());
|
||||||
|
|
||||||
@@ -410,7 +423,7 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
{
|
{
|
||||||
return mapToNodeAspects(nodeService.getAspects(nodeRef));
|
return mapToNodeAspects(nodeService.getAspects(nodeRef));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getPrimaryHierarchy(NodeRef nodeRef, boolean showLeaf)
|
public List<String> getPrimaryHierarchy(NodeRef nodeRef, boolean showLeaf)
|
||||||
{
|
{
|
||||||
final Path path = nodeService.getPath(nodeRef);
|
final Path path = nodeService.getPath(nodeRef);
|
||||||
@@ -420,16 +433,17 @@ public class NodeResourceHelper implements InitializingBean
|
|||||||
/**
|
/**
|
||||||
* Gathers node's secondary parents.
|
* Gathers node's secondary parents.
|
||||||
*
|
*
|
||||||
* @param nodeRef - node reference
|
* @param nodeRef
|
||||||
|
* - node reference
|
||||||
* @return a list of node's secondary parents.
|
* @return a list of node's secondary parents.
|
||||||
*/
|
*/
|
||||||
public List<String> getSecondaryParents(final NodeRef nodeRef)
|
public List<String> getSecondaryParents(final NodeRef nodeRef)
|
||||||
{
|
{
|
||||||
return nodeService.getParentAssocs(nodeRef).stream()
|
return nodeService.getParentAssocs(nodeRef).stream()
|
||||||
.filter(not(ChildAssociationRef::isPrimary))
|
.filter(not(ChildAssociationRef::isPrimary))
|
||||||
.map(ChildAssociationRef::getParentRef)
|
.map(ChildAssociationRef::getParentRef)
|
||||||
.map(NodeRef::getId)
|
.map(NodeRef::getId)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public PermissionService getPermissionService()
|
public PermissionService getPermissionService()
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -26,160 +26,50 @@
|
|||||||
package org.alfresco.repo.event2.filter;
|
package org.alfresco.repo.event2.filter;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
import org.alfresco.repo.event2.shared.CSVStringToListParser;
|
||||||
import org.alfresco.service.namespace.NamespaceException;
|
import org.alfresco.repo.event2.shared.QNameMatcher;
|
||||||
import org.alfresco.service.namespace.NamespaceService;
|
import org.alfresco.repo.event2.shared.TypeDefExpander;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract {@link EventFilter} implementation, containing common event filtering
|
* Abstract {@link EventFilter} implementation, containing common event filtering functionality for the {@link QName} type.
|
||||||
* functionality for the {@link QName} type.
|
|
||||||
*
|
*
|
||||||
* @author Jamal Kaabi-Mofrad
|
* @author Jamal Kaabi-Mofrad
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractNodeEventFilter implements EventFilter<QName>
|
public abstract class AbstractNodeEventFilter implements EventFilter<QName>
|
||||||
{
|
{
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractNodeEventFilter.class);
|
protected TypeDefExpander typeDefExpander;
|
||||||
|
|
||||||
private static final String MARKER_INCLUDE_SUBTYPES = "include_subtypes";
|
private QNameMatcher qNameMatcher;
|
||||||
private static final String WILDCARD = "*";
|
|
||||||
|
|
||||||
protected DictionaryService dictionaryService;
|
|
||||||
protected NamespaceService namespaceService;
|
|
||||||
|
|
||||||
private Set<QName> excludedTypes;
|
|
||||||
private Set<String> excludedNamespaceURI;
|
|
||||||
|
|
||||||
public AbstractNodeEventFilter()
|
|
||||||
{
|
|
||||||
this.excludedTypes = new HashSet<>();
|
|
||||||
this.excludedNamespaceURI = new HashSet<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void init()
|
public final void init()
|
||||||
{
|
{
|
||||||
preprocessExcludedTypes(getExcludedTypes());
|
qNameMatcher = new QNameMatcher(getExcludedTypes());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDictionaryService(DictionaryService dictionaryService)
|
public void setTypeDefExpander(TypeDefExpander typeDefExpander)
|
||||||
{
|
{
|
||||||
this.dictionaryService = dictionaryService;
|
this.typeDefExpander = typeDefExpander;
|
||||||
}
|
|
||||||
|
|
||||||
public void setNamespaceService(NamespaceService namespaceService)
|
|
||||||
{
|
|
||||||
this.namespaceService = namespaceService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isExcluded(QName qName)
|
public boolean isExcluded(QName qName)
|
||||||
{
|
{
|
||||||
if (qName != null)
|
return qNameMatcher.isMatching(qName);
|
||||||
{
|
|
||||||
return excludedTypes.contains(qName) || excludedNamespaceURI.contains(qName.getNamespaceURI());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Set<QName> getExcludedTypes();
|
protected abstract Set<QName> getExcludedTypes();
|
||||||
|
|
||||||
protected List<String> parseFilterList(String unparsedFilterList)
|
protected List<String> parseFilterList(String unparsedFilterList)
|
||||||
{
|
{
|
||||||
List<String> list = new LinkedList<>();
|
return CSVStringToListParser.parse(unparsedFilterList);
|
||||||
|
|
||||||
StringTokenizer st = new StringTokenizer(unparsedFilterList, ",");
|
|
||||||
while (st.hasMoreTokens())
|
|
||||||
{
|
|
||||||
String entry = st.nextToken().trim();
|
|
||||||
if (!entry.isEmpty())
|
|
||||||
{
|
|
||||||
if (!entry.equals("none") && !entry.contains("${"))
|
|
||||||
{
|
|
||||||
list.add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes the user-defined list of types into valid QNames. It
|
|
||||||
* validates them against the dictionary and also supports wildcards
|
|
||||||
*/
|
|
||||||
private void preprocessExcludedTypes(Set<QName> excluded)
|
|
||||||
{
|
|
||||||
excluded.forEach(qName -> {
|
|
||||||
if (WILDCARD.equals(qName.getLocalName()))
|
|
||||||
{
|
|
||||||
//excludedPrefixes.add(getPrefix(qName));
|
|
||||||
excludedNamespaceURI.add(qName.getNamespaceURI());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
excludedTypes.add(qName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (LOGGER.isDebugEnabled())
|
|
||||||
{
|
|
||||||
LOGGER.debug("Excluded namespace URIs:" + excludedNamespaceURI);
|
|
||||||
LOGGER.debug("Excluded types:" + excludedTypes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private QName getQName(String type)
|
|
||||||
{
|
|
||||||
return QName.createQName(type, namespaceService);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Collection<QName> expandTypeDef(String typeDef)
|
protected Collection<QName> expandTypeDef(String typeDef)
|
||||||
{
|
{
|
||||||
if ((typeDef == null) || typeDef.isEmpty() || typeDef.equals("none") || typeDef.contains("${"))
|
return typeDefExpander.expand(typeDef);
|
||||||
{
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeDef.indexOf(' ') < 0)
|
|
||||||
{
|
|
||||||
return Collections.singleton(getQName(typeDef));
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] typeDefParts = typeDef.split(" ");
|
|
||||||
if (typeDefParts.length != 2)
|
|
||||||
{
|
|
||||||
LOGGER.warn("Ignoring invalid blacklist type pattern: " + typeDef);
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeDefParts[1].equals(MARKER_INCLUDE_SUBTYPES))
|
|
||||||
{
|
|
||||||
if (typeDefParts[0].indexOf('*') >= 0)
|
|
||||||
{
|
|
||||||
LOGGER.warn("Ignoring invalid blacklist type pattern: " + typeDef);
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
QName baseType;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
baseType = getQName(typeDefParts[0]);
|
|
||||||
}
|
|
||||||
catch (NamespaceException ne)
|
|
||||||
{
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
return dictionaryService.getSubTypes(baseType, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.warn("Ignoring invalid blacklist type pattern: " + typeDef);
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.event2.filter;
|
package org.alfresco.repo.event2.filter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -43,34 +44,37 @@ public class NodePropertyFilter extends AbstractNodeEventFilter
|
|||||||
// These properties are included as top-level info,
|
// These properties are included as top-level info,
|
||||||
// so exclude them from the properties object
|
// so exclude them from the properties object
|
||||||
private static final Set<QName> EXCLUDED_TOP_LEVEL_PROPS = Set.of(ContentModel.PROP_NAME,
|
private static final Set<QName> EXCLUDED_TOP_LEVEL_PROPS = Set.of(ContentModel.PROP_NAME,
|
||||||
ContentModel.PROP_MODIFIER,
|
ContentModel.PROP_MODIFIER,
|
||||||
ContentModel.PROP_MODIFIED,
|
ContentModel.PROP_MODIFIED,
|
||||||
ContentModel.PROP_CREATOR,
|
ContentModel.PROP_CREATOR,
|
||||||
ContentModel.PROP_CREATED,
|
ContentModel.PROP_CREATED,
|
||||||
ContentModel.PROP_CONTENT);
|
ContentModel.PROP_CONTENT);
|
||||||
// These properties should not be excluded from the properties object
|
// These properties should not be excluded from the properties object
|
||||||
private static final Set<QName> ALLOWED_PROPERTIES = Set.of(ContentModel.PROP_CASCADE_TX,
|
private static final Set<QName> ALLOWED_PROPERTIES = Set.of(ContentModel.PROP_CASCADE_TX,
|
||||||
ContentModel.PROP_CASCADE_CRC);
|
ContentModel.PROP_CASCADE_CRC);
|
||||||
|
|
||||||
private final List<String> nodePropertiesBlackList;
|
private final List<String> nodePropertiesBlackList = new ArrayList<>();
|
||||||
|
|
||||||
public NodePropertyFilter()
|
public NodePropertyFilter(String userConfiguredProperties)
|
||||||
{
|
{
|
||||||
this.nodePropertiesBlackList = parseFilterList(FILTERED_PROPERTIES);
|
super();
|
||||||
|
nodePropertiesBlackList.addAll(parseFilterList(FILTERED_PROPERTIES));
|
||||||
|
nodePropertiesBlackList.addAll(parseFilterList(userConfiguredProperties));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<QName> getExcludedTypes()
|
public Set<QName> getExcludedTypes()
|
||||||
{
|
{
|
||||||
Set<QName> result = new HashSet<>(EXCLUDED_TOP_LEVEL_PROPS);
|
Set<QName> result = new HashSet<>(EXCLUDED_TOP_LEVEL_PROPS);
|
||||||
nodePropertiesBlackList.forEach(nodeProperty-> result.addAll(expandTypeDef(nodeProperty)));
|
nodePropertiesBlackList.forEach(nodeProperty -> result.addAll(expandTypeDef(nodeProperty)));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isExcluded(QName qName)
|
public boolean isExcluded(QName qName)
|
||||||
{
|
{
|
||||||
if(qName != null && ALLOWED_PROPERTIES.contains(qName)){
|
if (qName != null && ALLOWED_PROPERTIES.contains(qName))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return super.isExcluded(qName);
|
return super.isExcluded(qName);
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -31,6 +31,7 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,10 +42,13 @@ import org.alfresco.service.namespace.QName;
|
|||||||
public class NodeTypeFilter extends AbstractNodeEventFilter
|
public class NodeTypeFilter extends AbstractNodeEventFilter
|
||||||
{
|
{
|
||||||
private final List<String> nodeTypesBlackList;
|
private final List<String> nodeTypesBlackList;
|
||||||
|
private final DictionaryService dictionaryService;
|
||||||
|
|
||||||
public NodeTypeFilter(String filteredNodeTypes)
|
public NodeTypeFilter(String filteredNodeTypes, DictionaryService dictionaryService)
|
||||||
{
|
{
|
||||||
|
super();
|
||||||
this.nodeTypesBlackList = parseFilterList(filteredNodeTypes);
|
this.nodeTypesBlackList = parseFilterList(filteredNodeTypes);
|
||||||
|
this.dictionaryService = dictionaryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2025 - 2025 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.event2.mapper;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
|
public interface PropertyMapper
|
||||||
|
{
|
||||||
|
PropertyMapper NO_OP = (propertyQName, value) -> value;
|
||||||
|
|
||||||
|
Serializable map(QName propertyQName, Serializable value);
|
||||||
|
}
|
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2025 - 2025 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.event2.mapper;
|
||||||
|
|
||||||
|
import org.alfresco.repo.event2.shared.CSVStringToListParser;
|
||||||
|
import org.alfresco.repo.event2.shared.TypeDefExpander;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class PropertyMapperFactory
|
||||||
|
{
|
||||||
|
private final TypeDefExpander typeDefExpander;
|
||||||
|
|
||||||
|
public PropertyMapperFactory(TypeDefExpander typeDefExpander)
|
||||||
|
{
|
||||||
|
this.typeDefExpander = typeDefExpander;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertyMapper createPropertyMapper(String enabled, String userConfiguredSensitiveProperties, String userConfiguredReplacementText)
|
||||||
|
{
|
||||||
|
if ("false".equalsIgnoreCase(enabled))
|
||||||
|
{
|
||||||
|
return PropertyMapper.NO_OP;
|
||||||
|
}
|
||||||
|
Set<QName> sensitiveProperties = Optional.ofNullable(userConfiguredSensitiveProperties)
|
||||||
|
.filter(Predicate.not(String::isEmpty))
|
||||||
|
.map(CSVStringToListParser::parse)
|
||||||
|
.map(typeDefExpander::expand)
|
||||||
|
.orElse(Set.of());
|
||||||
|
return new ReplaceSensitivePropertyWithTextMapper(sensitiveProperties, userConfiguredReplacementText);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2025 - 2025 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.event2.mapper;
|
||||||
|
|
||||||
|
import org.alfresco.model.ContentModel;
|
||||||
|
import org.alfresco.repo.event2.shared.QNameMatcher;
|
||||||
|
import org.alfresco.repo.transfer.TransferModel;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class ReplaceSensitivePropertyWithTextMapper implements PropertyMapper
|
||||||
|
{
|
||||||
|
private static final Set<QName> DEFAULT_SENSITIVE_PROPERTIES = Set.of(
|
||||||
|
ContentModel.PROP_PASSWORD,
|
||||||
|
ContentModel.PROP_SALT,
|
||||||
|
ContentModel.PROP_PASSWORD_HASH,
|
||||||
|
TransferModel.PROP_PASSWORD
|
||||||
|
);
|
||||||
|
private static final String DEFAULT_REPLACEMENT_TEXT = "SENSITIVE_DATA_REMOVED";
|
||||||
|
|
||||||
|
private final QNameMatcher qNameMatcher;
|
||||||
|
private final String replacementText;
|
||||||
|
|
||||||
|
public ReplaceSensitivePropertyWithTextMapper(Set<QName> userConfiguredSensitiveProperties, String userConfiguredReplacementText)
|
||||||
|
{
|
||||||
|
Set<QName> sensitiveProperties = Optional.ofNullable(userConfiguredSensitiveProperties)
|
||||||
|
.filter(Predicate.not(Collection::isEmpty))
|
||||||
|
.orElse(DEFAULT_SENSITIVE_PROPERTIES);
|
||||||
|
qNameMatcher = new QNameMatcher(sensitiveProperties);
|
||||||
|
replacementText = Optional.ofNullable(userConfiguredReplacementText)
|
||||||
|
.filter(Predicate.not(String::isEmpty))
|
||||||
|
.filter(userInput -> !userInput.contains("${"))
|
||||||
|
.orElse(DEFAULT_REPLACEMENT_TEXT);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Serializable map(QName propertyQName, Serializable value)
|
||||||
|
{
|
||||||
|
if (qNameMatcher.isMatching(propertyQName))
|
||||||
|
{
|
||||||
|
return replacementText;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2025 - 2025 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.event2.shared;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
public final class CSVStringToListParser
|
||||||
|
{
|
||||||
|
private CSVStringToListParser()
|
||||||
|
{}
|
||||||
|
|
||||||
|
public static List<String> parse(String userInputCSV)
|
||||||
|
{
|
||||||
|
List<String> list = new LinkedList<>();
|
||||||
|
|
||||||
|
StringTokenizer st = new StringTokenizer(userInputCSV, ",");
|
||||||
|
while (st.hasMoreTokens())
|
||||||
|
{
|
||||||
|
String entry = st.nextToken().trim();
|
||||||
|
if (!entry.isEmpty() && !entry.equals("none") && !entry.contains("${"))
|
||||||
|
{
|
||||||
|
list.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2025 - 2025 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.event2.shared;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
|
public class QNameMatcher
|
||||||
|
{
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(QNameMatcher.class);
|
||||||
|
private static final String WILDCARD = "*";
|
||||||
|
|
||||||
|
private final Set<QName> matchingTypes;
|
||||||
|
private final Set<String> matchingNamespaceURIs;
|
||||||
|
|
||||||
|
public QNameMatcher(Set<QName> qNamesToMatch)
|
||||||
|
{
|
||||||
|
matchingTypes = new HashSet<>();
|
||||||
|
matchingNamespaceURIs = new HashSet<>();
|
||||||
|
|
||||||
|
qNamesToMatch.forEach(qName -> {
|
||||||
|
if (WILDCARD.equals(qName.getLocalName()))
|
||||||
|
{
|
||||||
|
matchingNamespaceURIs.add(qName.getNamespaceURI());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
matchingTypes.add(qName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (LOGGER.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOGGER.debug("Matching namespace URIs:" + matchingNamespaceURIs);
|
||||||
|
LOGGER.debug("Matching types:" + matchingTypes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMatching(QName qName)
|
||||||
|
{
|
||||||
|
if (qName != null)
|
||||||
|
{
|
||||||
|
return matchingTypes.contains(qName) || matchingNamespaceURIs.contains(qName.getNamespaceURI());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2025 - 2025 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.event2.shared;
|
||||||
|
|
||||||
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||||
|
import org.alfresco.service.namespace.NamespaceException;
|
||||||
|
import org.alfresco.service.namespace.NamespaceService;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class TypeDefExpander
|
||||||
|
{
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(TypeDefExpander.class);
|
||||||
|
private static final String MARKER_INCLUDE_SUBTYPES = "include_subtypes";
|
||||||
|
|
||||||
|
private final DictionaryService dictionaryService;
|
||||||
|
private final NamespaceService namespaceService;
|
||||||
|
|
||||||
|
public TypeDefExpander(DictionaryService dictionaryService, NamespaceService namespaceService)
|
||||||
|
{
|
||||||
|
this.dictionaryService = dictionaryService;
|
||||||
|
this.namespaceService = namespaceService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<QName> expand(Collection<String> types){
|
||||||
|
Set<QName> result = new HashSet<>();
|
||||||
|
types.forEach(type -> result.addAll(expand(type)));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<QName> expand(String typeDef)
|
||||||
|
{
|
||||||
|
if ((typeDef == null) || typeDef.isEmpty() || typeDef.equals("none") || typeDef.contains("${"))
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeDef.indexOf(' ') < 0)
|
||||||
|
{
|
||||||
|
return Collections.singleton(getQName(typeDef));
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] typeDefParts = typeDef.split(" ");
|
||||||
|
if (typeDefParts.length != 2)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Ignoring invalid type pattern: " + typeDef);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeDefParts[1].equals(MARKER_INCLUDE_SUBTYPES))
|
||||||
|
{
|
||||||
|
if (typeDefParts[0].indexOf('*') >= 0)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Ignoring invalid type pattern: " + typeDef);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
QName baseType;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
baseType = getQName(typeDefParts[0]);
|
||||||
|
}
|
||||||
|
catch (NamespaceException ne)
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return dictionaryService.getSubTypes(baseType, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.warn("Ignoring invalid type pattern: " + typeDef);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private QName getQName(String type)
|
||||||
|
{
|
||||||
|
return QName.createQName(type, namespaceService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -3,23 +3,30 @@
|
|||||||
|
|
||||||
<beans>
|
<beans>
|
||||||
|
|
||||||
|
<bean id="event2TypeDefExpander" class="org.alfresco.repo.event2.shared.TypeDefExpander">
|
||||||
|
<constructor-arg ref="dictionaryService"/>
|
||||||
|
<constructor-arg ref="namespaceService"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
<!-- Event2 Filters -->
|
<!-- Event2 Filters -->
|
||||||
<bean id="event2FilterRegistry" class="org.alfresco.repo.event2.filter.EventFilterRegistry"/>
|
<bean id="event2FilterRegistry" class="org.alfresco.repo.event2.filter.EventFilterRegistry"/>
|
||||||
|
|
||||||
<bean id="abstractNodeEventFilter" class="org.alfresco.repo.event2.filter.AbstractNodeEventFilter" abstract="true" init-method="init">
|
<bean id="abstractNodeEventFilter" class="org.alfresco.repo.event2.filter.AbstractNodeEventFilter" abstract="true" init-method="init">
|
||||||
<property name="dictionaryService" ref="dictionaryService"/>
|
<property name="typeDefExpander" ref="event2TypeDefExpander"/>
|
||||||
<property name="namespaceService" ref="namespaceService"/>
|
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="event2NodeTypeFilter" class="org.alfresco.repo.event2.filter.NodeTypeFilter" parent="abstractNodeEventFilter">
|
<bean id="event2NodeTypeFilter" class="org.alfresco.repo.event2.filter.NodeTypeFilter" parent="abstractNodeEventFilter">
|
||||||
<constructor-arg value="${repo.event2.filter.nodeTypes}"/>
|
<constructor-arg value="${repo.event2.filter.nodeTypes}"/>
|
||||||
|
<constructor-arg ref="dictionaryService"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="event2NodeAspectFilter" class="org.alfresco.repo.event2.filter.NodeAspectFilter" parent="abstractNodeEventFilter">
|
<bean id="event2NodeAspectFilter" class="org.alfresco.repo.event2.filter.NodeAspectFilter" parent="abstractNodeEventFilter">
|
||||||
<constructor-arg value="${repo.event2.filter.nodeAspects}"/>
|
<constructor-arg value="${repo.event2.filter.nodeAspects}"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="event2NodePropertyFilter" class="org.alfresco.repo.event2.filter.NodePropertyFilter" parent="abstractNodeEventFilter"/>
|
<bean id="event2NodePropertyFilter" class="org.alfresco.repo.event2.filter.NodePropertyFilter" parent="abstractNodeEventFilter">
|
||||||
|
<constructor-arg value="${repo.event2.filter.nodeProperties}"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
<bean id="event2UserFilter" class="org.alfresco.repo.event2.filter.EventUserFilter">
|
<bean id="event2UserFilter" class="org.alfresco.repo.event2.filter.EventUserFilter">
|
||||||
<constructor-arg value="${repo.event2.filter.users}"/>
|
<constructor-arg value="${repo.event2.filter.users}"/>
|
||||||
@@ -31,6 +38,16 @@
|
|||||||
</bean>
|
</bean>
|
||||||
<!-- End of Event2 Filters -->
|
<!-- End of Event2 Filters -->
|
||||||
|
|
||||||
|
<bean id="event2PropertyMapperFactory" class="org.alfresco.repo.event2.mapper.PropertyMapperFactory">
|
||||||
|
<constructor-arg ref="event2TypeDefExpander"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="event2PropertyMapper" factory-bean="event2PropertyMapperFactory" factory-method="createPropertyMapper">
|
||||||
|
<constructor-arg value="${repo.event2.mapper.enabled}"/>
|
||||||
|
<constructor-arg value="${repo.event2.mapper.overrideDefaultProperties}"/>
|
||||||
|
<constructor-arg value="${repo.event2.mapper.overrideReplacementText}"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
<bean id="baseEventGeneratorV2" abstract="true">
|
<bean id="baseEventGeneratorV2" abstract="true">
|
||||||
<property name="policyComponent" ref="policyComponent"/>
|
<property name="policyComponent" ref="policyComponent"/>
|
||||||
<property name="nodeService" ref="nodeService"/>
|
<property name="nodeService" ref="nodeService"/>
|
||||||
@@ -53,6 +70,7 @@
|
|||||||
<property name="eventFilterRegistry" ref="event2FilterRegistry"/>
|
<property name="eventFilterRegistry" ref="event2FilterRegistry"/>
|
||||||
<property name="namespaceService" ref="namespaceService"/>
|
<property name="namespaceService" ref="namespaceService"/>
|
||||||
<property name="permissionService" ref="permissionService"/>
|
<property name="permissionService" ref="permissionService"/>
|
||||||
|
<property name="propertyMapper" ref="event2PropertyMapper"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="baseEventSender" abstract="true">
|
<bean id="baseEventSender" abstract="true">
|
||||||
|
@@ -1234,6 +1234,10 @@ repo.event2.filter.childAssocTypes=rn:rendition
|
|||||||
# Comma separated list of users which should be excluded
|
# Comma separated list of users which should be excluded
|
||||||
# Note: username's case-sensitivity depends on the {user.name.caseSensitive} setting
|
# Note: username's case-sensitivity depends on the {user.name.caseSensitive} setting
|
||||||
repo.event2.filter.users=
|
repo.event2.filter.users=
|
||||||
|
repo.event2.filter.nodeProperties=
|
||||||
|
repo.event2.mapper.enabled=true
|
||||||
|
repo.event2.mapper.overrideDefaultProperties=
|
||||||
|
repo.event2.mapper.overrideReplacementText=
|
||||||
# Topic name
|
# Topic name
|
||||||
repo.event2.topic.endpoint=amqp:topic:alfresco.repo.event2
|
repo.event2.topic.endpoint=amqp:topic:alfresco.repo.event2
|
||||||
# Specifies the strategy for sending the events
|
# Specifies the strategy for sending the events
|
||||||
@@ -1393,4 +1397,4 @@ default.async.folder.items=1000
|
|||||||
# Default NodeSize Thread pool
|
# Default NodeSize Thread pool
|
||||||
default.nodeSize.corePoolSize=5
|
default.nodeSize.corePoolSize=5
|
||||||
default.nodeSize.maximumPoolSize=10
|
default.nodeSize.maximumPoolSize=10
|
||||||
default.nodeSize.workQueueSize=100
|
default.nodeSize.workQueueSize=100
|
||||||
|
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2025 - 2025 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.event2;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.alfresco.repo.event2.shared.CSVStringToListParser;
|
||||||
|
|
||||||
|
public class CSVStringToListParserUnitTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void shouldIgnoreEmptySpacesAndNoneValueAndTemplateStringsAndParseTheRest()
|
||||||
|
{
|
||||||
|
String userInputCSV = "a,,none,2,${test}, ,*";
|
||||||
|
|
||||||
|
List<String> result = CSVStringToListParser.parse(userInputCSV);
|
||||||
|
|
||||||
|
List<String> expected = List.of("a", "2", "*");
|
||||||
|
assertEquals(expected, result);
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2020 Alfresco Software Limited
|
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -35,6 +35,10 @@ import static org.mockito.Mockito.when;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
import org.alfresco.model.ForumModel;
|
import org.alfresco.model.ForumModel;
|
||||||
import org.alfresco.model.RenditionModel;
|
import org.alfresco.model.RenditionModel;
|
||||||
@@ -43,14 +47,12 @@ import org.alfresco.repo.event2.filter.EventUserFilter;
|
|||||||
import org.alfresco.repo.event2.filter.NodeAspectFilter;
|
import org.alfresco.repo.event2.filter.NodeAspectFilter;
|
||||||
import org.alfresco.repo.event2.filter.NodePropertyFilter;
|
import org.alfresco.repo.event2.filter.NodePropertyFilter;
|
||||||
import org.alfresco.repo.event2.filter.NodeTypeFilter;
|
import org.alfresco.repo.event2.filter.NodeTypeFilter;
|
||||||
|
import org.alfresco.repo.event2.shared.TypeDefExpander;
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||||
import org.alfresco.service.namespace.NamespaceException;
|
import org.alfresco.service.namespace.NamespaceException;
|
||||||
import org.alfresco.service.namespace.NamespaceService;
|
import org.alfresco.service.namespace.NamespaceService;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
import org.alfresco.util.OneToManyHashBiMap;
|
import org.alfresco.util.OneToManyHashBiMap;
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.stubbing.Answer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests event filters.
|
* Tests event filters.
|
||||||
@@ -78,32 +80,32 @@ public class EventFilterUnitTest
|
|||||||
|
|
||||||
namespaceService = new MockNamespaceServiceImpl();
|
namespaceService = new MockNamespaceServiceImpl();
|
||||||
namespaceService.registerNamespace(NamespaceService.SYSTEM_MODEL_PREFIX,
|
namespaceService.registerNamespace(NamespaceService.SYSTEM_MODEL_PREFIX,
|
||||||
NamespaceService.SYSTEM_MODEL_1_0_URI);
|
NamespaceService.SYSTEM_MODEL_1_0_URI);
|
||||||
namespaceService.registerNamespace(NamespaceService.CONTENT_MODEL_PREFIX,
|
namespaceService.registerNamespace(NamespaceService.CONTENT_MODEL_PREFIX,
|
||||||
NamespaceService.CONTENT_MODEL_1_0_URI);
|
NamespaceService.CONTENT_MODEL_1_0_URI);
|
||||||
namespaceService.registerNamespace(NamespaceService.FORUMS_MODEL_PREFIX,
|
namespaceService.registerNamespace(NamespaceService.FORUMS_MODEL_PREFIX,
|
||||||
NamespaceService.FORUMS_MODEL_1_0_URI);
|
NamespaceService.FORUMS_MODEL_1_0_URI);
|
||||||
namespaceService.registerNamespace(NamespaceService.RENDITION_MODEL_PREFIX,
|
namespaceService.registerNamespace(NamespaceService.RENDITION_MODEL_PREFIX,
|
||||||
NamespaceService.RENDITION_MODEL_1_0_URI);
|
NamespaceService.RENDITION_MODEL_1_0_URI);
|
||||||
|
namespaceService.registerNamespace(ContentModel.USER_MODEL_PREFIX,
|
||||||
|
ContentModel.USER_MODEL_URI);
|
||||||
|
|
||||||
propertyFilter = new NodePropertyFilter();
|
TypeDefExpander typeDefExpander = new TypeDefExpander(dictionaryService, namespaceService);
|
||||||
propertyFilter.setNamespaceService(namespaceService);
|
|
||||||
propertyFilter.setDictionaryService(dictionaryService);
|
propertyFilter = new NodePropertyFilter("usr:password");
|
||||||
|
propertyFilter.setTypeDefExpander(typeDefExpander);
|
||||||
propertyFilter.init();
|
propertyFilter.init();
|
||||||
|
|
||||||
typeFilter = new NodeTypeFilter("sys:*, fm:*, cm:thumbnail");
|
typeFilter = new NodeTypeFilter("sys:*, fm:*, cm:thumbnail", dictionaryService);
|
||||||
typeFilter.setNamespaceService(namespaceService);
|
typeFilter.setTypeDefExpander(typeDefExpander);
|
||||||
typeFilter.setDictionaryService(dictionaryService);
|
|
||||||
typeFilter.init();
|
typeFilter.init();
|
||||||
|
|
||||||
aspectFilter = new NodeAspectFilter("cm:workingcopy");
|
aspectFilter = new NodeAspectFilter("cm:workingcopy");
|
||||||
aspectFilter.setNamespaceService(namespaceService);
|
aspectFilter.setTypeDefExpander(typeDefExpander);
|
||||||
aspectFilter.setDictionaryService(dictionaryService);
|
|
||||||
aspectFilter.init();
|
aspectFilter.init();
|
||||||
|
|
||||||
childAssociationTypeFilter = new ChildAssociationTypeFilter("rn:rendition");
|
childAssociationTypeFilter = new ChildAssociationTypeFilter("rn:rendition");
|
||||||
childAssociationTypeFilter.setNamespaceService(namespaceService);
|
childAssociationTypeFilter.setTypeDefExpander(typeDefExpander);
|
||||||
childAssociationTypeFilter.setDictionaryService(dictionaryService);
|
|
||||||
childAssociationTypeFilter.init();
|
childAssociationTypeFilter.init();
|
||||||
|
|
||||||
caseInsensitive_userFilter = new EventUserFilter("System, john.doe, null", false);
|
caseInsensitive_userFilter = new EventUserFilter("System, john.doe, null", false);
|
||||||
@@ -114,10 +116,12 @@ public class EventFilterUnitTest
|
|||||||
public void nodePropertyFilter()
|
public void nodePropertyFilter()
|
||||||
{
|
{
|
||||||
assertTrue("System properties are excluded by default.",
|
assertTrue("System properties are excluded by default.",
|
||||||
propertyFilter.isExcluded(ContentModel.PROP_NODE_UUID));
|
propertyFilter.isExcluded(ContentModel.PROP_NODE_UUID));
|
||||||
|
|
||||||
assertTrue("System properties are excluded by default.",
|
assertTrue("System properties are excluded by default.",
|
||||||
propertyFilter.isExcluded(ContentModel.PROP_NODE_DBID));
|
propertyFilter.isExcluded(ContentModel.PROP_NODE_DBID));
|
||||||
|
|
||||||
|
assertTrue("User configured properties are excluded.", propertyFilter.isExcluded(ContentModel.PROP_PASSWORD));
|
||||||
|
|
||||||
assertFalse("Property cascadeTx is not excluded", propertyFilter.isExcluded(ContentModel.PROP_CASCADE_TX));
|
assertFalse("Property cascadeTx is not excluded", propertyFilter.isExcluded(ContentModel.PROP_CASCADE_TX));
|
||||||
assertFalse("Property cascadeCRC is not excluded", propertyFilter.isExcluded(ContentModel.PROP_CASCADE_CRC));
|
assertFalse("Property cascadeCRC is not excluded", propertyFilter.isExcluded(ContentModel.PROP_CASCADE_CRC));
|
||||||
@@ -129,16 +133,16 @@ public class EventFilterUnitTest
|
|||||||
public void nodeTypeFilter()
|
public void nodeTypeFilter()
|
||||||
{
|
{
|
||||||
assertTrue("Thumbnail node type should have been filtered.",
|
assertTrue("Thumbnail node type should have been filtered.",
|
||||||
typeFilter.isExcluded(ContentModel.TYPE_THUMBNAIL));
|
typeFilter.isExcluded(ContentModel.TYPE_THUMBNAIL));
|
||||||
|
|
||||||
assertTrue("System folder node types are excluded by default.",
|
assertTrue("System folder node types are excluded by default.",
|
||||||
typeFilter.isExcluded(ContentModel.TYPE_SYSTEM_FOLDER));
|
typeFilter.isExcluded(ContentModel.TYPE_SYSTEM_FOLDER));
|
||||||
|
|
||||||
assertTrue("System node type should have been filtered (sys:*).",
|
assertTrue("System node type should have been filtered (sys:*).",
|
||||||
typeFilter.isExcluded(QName.createQName("sys:testSomeSystemType", namespaceService)));
|
typeFilter.isExcluded(QName.createQName("sys:testSomeSystemType", namespaceService)));
|
||||||
|
|
||||||
assertTrue("Forum node type should have been filtered (fm:*).",
|
assertTrue("Forum node type should have been filtered (fm:*).",
|
||||||
typeFilter.isExcluded(ForumModel.TYPE_POST));
|
typeFilter.isExcluded(ForumModel.TYPE_POST));
|
||||||
|
|
||||||
assertFalse(typeFilter.isExcluded(ContentModel.TYPE_FOLDER));
|
assertFalse(typeFilter.isExcluded(ContentModel.TYPE_FOLDER));
|
||||||
}
|
}
|
||||||
@@ -147,7 +151,7 @@ public class EventFilterUnitTest
|
|||||||
public void nodeAspectFilter()
|
public void nodeAspectFilter()
|
||||||
{
|
{
|
||||||
assertTrue("Working copy aspect should have been filtered.",
|
assertTrue("Working copy aspect should have been filtered.",
|
||||||
aspectFilter.isExcluded(ContentModel.ASPECT_WORKING_COPY));
|
aspectFilter.isExcluded(ContentModel.ASPECT_WORKING_COPY));
|
||||||
|
|
||||||
assertFalse(aspectFilter.isExcluded(ContentModel.ASPECT_TITLED));
|
assertFalse(aspectFilter.isExcluded(ContentModel.ASPECT_TITLED));
|
||||||
}
|
}
|
||||||
@@ -160,41 +164,41 @@ public class EventFilterUnitTest
|
|||||||
|
|
||||||
assertFalse(childAssociationTypeFilter.isExcluded(ContentModel.ASSOC_CONTAINS));
|
assertFalse(childAssociationTypeFilter.isExcluded(ContentModel.ASSOC_CONTAINS));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void userFilter_case_insensitive()
|
public void userFilter_case_insensitive()
|
||||||
{
|
{
|
||||||
assertTrue("System user should have been filtered.",
|
assertTrue("System user should have been filtered.",
|
||||||
caseInsensitive_userFilter.isExcluded("System"));
|
caseInsensitive_userFilter.isExcluded("System"));
|
||||||
|
|
||||||
assertTrue("System user should have been filtered (case-insensitive).",
|
assertTrue("System user should have been filtered (case-insensitive).",
|
||||||
caseInsensitive_userFilter.isExcluded("SYSTEM"));
|
caseInsensitive_userFilter.isExcluded("SYSTEM"));
|
||||||
|
|
||||||
assertTrue("'null' user should have been filtered.",
|
assertTrue("'null' user should have been filtered.",
|
||||||
caseInsensitive_userFilter.isExcluded("null"));
|
caseInsensitive_userFilter.isExcluded("null"));
|
||||||
|
|
||||||
assertTrue("john.doe user should have been filtered.",
|
assertTrue("john.doe user should have been filtered.",
|
||||||
caseInsensitive_userFilter.isExcluded("John.Doe"));
|
caseInsensitive_userFilter.isExcluded("John.Doe"));
|
||||||
|
|
||||||
assertFalse("'jane.doe' user should not have been filtered.",
|
assertFalse("'jane.doe' user should not have been filtered.",
|
||||||
caseInsensitive_userFilter.isExcluded("jane.doe"));
|
caseInsensitive_userFilter.isExcluded("jane.doe"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void userFilter_case_sensitive()
|
public void userFilter_case_sensitive()
|
||||||
{
|
{
|
||||||
assertFalse("'system' user should not have been filtered.",
|
assertFalse("'system' user should not have been filtered.",
|
||||||
caseSensitive_userFilter.isExcluded("system"));
|
caseSensitive_userFilter.isExcluded("system"));
|
||||||
assertTrue("'System' user should have been filtered.",
|
assertTrue("'System' user should have been filtered.",
|
||||||
caseSensitive_userFilter.isExcluded("System"));
|
caseSensitive_userFilter.isExcluded("System"));
|
||||||
|
|
||||||
assertFalse("'John.Doe' user should not have been filtered.",
|
assertFalse("'John.Doe' user should not have been filtered.",
|
||||||
caseSensitive_userFilter.isExcluded("John.Doe"));
|
caseSensitive_userFilter.isExcluded("John.Doe"));
|
||||||
assertTrue("'john.doe' user should have been filtered.",
|
assertTrue("'john.doe' user should have been filtered.",
|
||||||
caseSensitive_userFilter.isExcluded("john.doe"));
|
caseSensitive_userFilter.isExcluded("john.doe"));
|
||||||
|
|
||||||
assertFalse("'jane.doe' user should not have been filtered.",
|
assertFalse("'jane.doe' user should not have been filtered.",
|
||||||
caseSensitive_userFilter.isExcluded("jane.doe"));
|
caseSensitive_userFilter.isExcluded("jane.doe"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2025 - 2025 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.event2;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.alfresco.model.ContentModel;
|
||||||
|
import org.alfresco.repo.event2.mapper.PropertyMapper;
|
||||||
|
import org.alfresco.repo.event2.mapper.ReplaceSensitivePropertyWithTextMapper;
|
||||||
|
import org.alfresco.repo.transfer.TransferModel;
|
||||||
|
|
||||||
|
public class PropertyMapperUnitTest
|
||||||
|
{
|
||||||
|
private static final String DEFAULT_REPLACEMENT_TEXT = "SENSITIVE_DATA_REMOVED";
|
||||||
|
private static final String USER_CONFIGURED_REPLACEMENT_TEXT = "HIDDEN_BY_SECURITY_CONFIG";
|
||||||
|
|
||||||
|
private final PropertyMapper defaultPropertyMapper = new ReplaceSensitivePropertyWithTextMapper(null,null);
|
||||||
|
private final PropertyMapper userConfiguredpropertyMapper = new ReplaceSensitivePropertyWithTextMapper(
|
||||||
|
Set.of(ContentModel.PROP_PASSWORD, TransferModel.PROP_PASSWORD),
|
||||||
|
USER_CONFIGURED_REPLACEMENT_TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReplacePropertyValueWhenItsOneOfTheDefaultSensitivePropertiesWhenUsingDefaultConfig()
|
||||||
|
{
|
||||||
|
assertEquals(DEFAULT_REPLACEMENT_TEXT, defaultPropertyMapper.map(ContentModel.PROP_PASSWORD, "test_pass"));
|
||||||
|
assertEquals(DEFAULT_REPLACEMENT_TEXT, defaultPropertyMapper.map(ContentModel.PROP_SALT, UUID.randomUUID().toString()));
|
||||||
|
assertEquals(DEFAULT_REPLACEMENT_TEXT, defaultPropertyMapper.map(ContentModel.PROP_PASSWORD_HASH, "r4nD0M_h4sH"));
|
||||||
|
assertEquals(DEFAULT_REPLACEMENT_TEXT, defaultPropertyMapper.map(TransferModel.PROP_PASSWORD, "pyramid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotReplacePropertyValueWhenItsNotOneOfTheDefaultSensitivePropertiesWhenUsingDefaultConfig()
|
||||||
|
{
|
||||||
|
assertEquals("Bob", defaultPropertyMapper.map(ContentModel.PROP_USERNAME, "Bob"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReplacePropertyValueWhenItsOneOfTheDefaultSensitivePropertiesWhenUsingUserConfig()
|
||||||
|
{
|
||||||
|
assertEquals(USER_CONFIGURED_REPLACEMENT_TEXT, userConfiguredpropertyMapper.map(ContentModel.PROP_PASSWORD, "test_pass"));
|
||||||
|
|
||||||
|
assertEquals(USER_CONFIGURED_REPLACEMENT_TEXT, userConfiguredpropertyMapper.map(TransferModel.PROP_PASSWORD, "pyramid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotReplacePropertyValueWhenItsNotOneOfTheDefaultSensitivePropertiesWhenUsingUserConfig()
|
||||||
|
{
|
||||||
|
String randomUuid = UUID.randomUUID().toString();
|
||||||
|
assertEquals(randomUuid, userConfiguredpropertyMapper.map(ContentModel.PROP_SALT, randomUuid));
|
||||||
|
assertEquals("r4nD0M_h4sH", userConfiguredpropertyMapper.map(ContentModel.PROP_PASSWORD_HASH, "r4nD0M_h4sH"));
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2025 - 2025 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.event2;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.alfresco.model.ContentModel;
|
||||||
|
import org.alfresco.repo.event2.shared.QNameMatcher;
|
||||||
|
import org.alfresco.repo.transfer.TransferModel;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
|
public class QNameMatcherUnitTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void shouldMatchOnlyQNamesFromUserModelURI()
|
||||||
|
{
|
||||||
|
QNameMatcher qNameMatcher = new QNameMatcher(Set.of(QName.createQName(ContentModel.USER_MODEL_URI, "*")));
|
||||||
|
|
||||||
|
assertTrue(qNameMatcher.isMatching(ContentModel.PROP_USER_USERNAME));
|
||||||
|
assertTrue(qNameMatcher.isMatching(ContentModel.TYPE_USER));
|
||||||
|
assertFalse(qNameMatcher.isMatching(ContentModel.PROP_TITLE));
|
||||||
|
assertFalse(qNameMatcher.isMatching(TransferModel.PROP_USERNAME));
|
||||||
|
assertFalse(qNameMatcher.isMatching(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldOnlyMatchSpecificQName()
|
||||||
|
{
|
||||||
|
QNameMatcher qNameMatcher = new QNameMatcher(Set.of(ContentModel.PROP_USER_USERNAME));
|
||||||
|
|
||||||
|
assertTrue(qNameMatcher.isMatching(ContentModel.PROP_USER_USERNAME));
|
||||||
|
assertFalse(qNameMatcher.isMatching(ContentModel.PROP_NAME));
|
||||||
|
assertFalse(qNameMatcher.isMatching(ContentModel.PROP_USERNAME));
|
||||||
|
assertFalse(qNameMatcher.isMatching(TransferModel.PROP_USERNAME));
|
||||||
|
assertFalse(qNameMatcher.isMatching(null));
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Repository
|
* Alfresco Repository
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -30,12 +30,15 @@ import org.junit.runners.Suite;
|
|||||||
import org.junit.runners.Suite.SuiteClasses;
|
import org.junit.runners.Suite.SuiteClasses;
|
||||||
|
|
||||||
@RunWith(Suite.class)
|
@RunWith(Suite.class)
|
||||||
@SuiteClasses({ EventFilterUnitTest.class,
|
@SuiteClasses({EventFilterUnitTest.class,
|
||||||
EventConsolidatorUnitTest.class,
|
EventConsolidatorUnitTest.class,
|
||||||
EventJSONSchemaUnitTest.class,
|
EventJSONSchemaUnitTest.class,
|
||||||
EnqueuingEventSenderUnitTest.class,
|
EnqueuingEventSenderUnitTest.class,
|
||||||
NodeResourceHelperUnitTest.class
|
NodeResourceHelperUnitTest.class,
|
||||||
|
PropertyMapperUnitTest.class,
|
||||||
|
QNameMatcherUnitTest.class,
|
||||||
|
CSVStringToListParserUnitTest.class,
|
||||||
|
TypeDefExpanderUnitTest.class
|
||||||
})
|
})
|
||||||
public class RepoEvent2UnitSuite
|
public class RepoEvent2UnitSuite
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2025 - 2025 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
*
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.event2;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.alfresco.model.ContentModel;
|
||||||
|
import org.alfresco.model.DataListModel;
|
||||||
|
import org.alfresco.repo.event2.shared.TypeDefExpander;
|
||||||
|
import org.alfresco.repo.transfer.TransferModel;
|
||||||
|
import org.alfresco.repo.workflow.WorkflowModel;
|
||||||
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||||
|
import org.alfresco.service.namespace.NamespaceService;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
|
public class TypeDefExpanderUnitTest
|
||||||
|
{
|
||||||
|
private DictionaryService dictionaryService;
|
||||||
|
private NamespaceService namespaceService;
|
||||||
|
private TypeDefExpander typeDefExpander;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp()
|
||||||
|
{
|
||||||
|
dictionaryService = mock(DictionaryService.class);
|
||||||
|
namespaceService = mock(NamespaceService.class);
|
||||||
|
typeDefExpander = new TypeDefExpander(dictionaryService, namespaceService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExpandWithValidType()
|
||||||
|
{
|
||||||
|
String input = "usr:username";
|
||||||
|
when(namespaceService.getNamespaceURI("usr")).thenReturn(ContentModel.USER_MODEL_URI);
|
||||||
|
|
||||||
|
Collection<QName> result = typeDefExpander.expand(input);
|
||||||
|
|
||||||
|
QName expected = ContentModel.PROP_USER_USERNAME;
|
||||||
|
assertEquals(expected, result.iterator().next());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExpandWithValidTypeIncludingSubtypes()
|
||||||
|
{
|
||||||
|
String input = "cm:content include_subtypes";
|
||||||
|
when(namespaceService.getNamespaceURI("cm")).thenReturn(NamespaceService.CONTENT_MODEL_1_0_URI);
|
||||||
|
Set<QName> subtypes = Set.of(TransferModel.TYPE_TRANSFER_RECORD, DataListModel.TYPE_EVENT, WorkflowModel.TYPE_TASK);
|
||||||
|
when(dictionaryService.getSubTypes(ContentModel.TYPE_CONTENT, true)).thenReturn(subtypes);
|
||||||
|
|
||||||
|
Collection<QName> result = typeDefExpander.expand(input);
|
||||||
|
|
||||||
|
assertEquals(subtypes, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExpandWithInvalidTypes()
|
||||||
|
{
|
||||||
|
Set<String> input = Stream.of(null, " ", "none", "${test.prop}").collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Collection<QName> result = typeDefExpander.expand(input);
|
||||||
|
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user