[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:
Cezary Witkowski
2025-01-17 11:16:31 +01:00
committed by GitHub
parent ea36e77775
commit 1592f7fa1d
18 changed files with 868 additions and 234 deletions

View File

@@ -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.
@@ -87,6 +88,7 @@ public class NodeResourceHelper implements InitializingBean
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;
@@ -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();
@@ -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);
@@ -181,7 +189,8 @@ public class NodeResourceHelper implements InitializingBean
{ {
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);
} }
}); });
@@ -306,10 +318,10 @@ 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
* QName
* @return a String representing the QName in the format prefix:local or {uri}local. * @return a String representing the QName in the format prefix:local or {uri}local.
*/ */
public String getQNamePrefixString(QName k) public String getQNamePrefixString(QName k)
@@ -358,7 +370,8 @@ public class NodeResourceHelper implements InitializingBean
try try
{ {
return nodeService.getProperties(nodeRef); return nodeService.getProperties(nodeRef);
} finally }
finally
{ {
MLPropertyInterceptor.setMLAware(toRestore); MLPropertyInterceptor.setMLAware(toRestore);
} }
@@ -420,7 +433,8 @@ 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)

View File

@@ -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();
} }
} }

View File

@@ -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;
@@ -52,11 +53,13 @@ public class NodePropertyFilter extends AbstractNodeEventFilter
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
@@ -70,7 +73,8 @@ public class NodePropertyFilter extends AbstractNodeEventFilter
@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);

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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">

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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.
@@ -85,25 +87,25 @@ public class EventFilterUnitTest
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);
@@ -119,6 +121,8 @@ public class EventFilterUnitTest
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));

View File

@@ -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"));
}
}

View File

@@ -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));
}
}

View File

@@ -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
@@ -34,8 +34,11 @@ import org.junit.runners.Suite.SuiteClasses;
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
{ {}
}

View File

@@ -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());
}
}