diff --git a/.secrets.baseline b/.secrets.baseline index c2e187e741..8f15b752ef 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -1242,7 +1242,7 @@ "filename": "repository/src/main/resources/alfresco/repository.properties", "hashed_secret": "1459a56410378e4d3ab470eff570e5eae1742762", "is_verified": false, - "line_number": 312, + "line_number": 314, "is_secret": false }, { @@ -1250,7 +1250,7 @@ "filename": "repository/src/main/resources/alfresco/repository.properties", "hashed_secret": "84551ae5442affc9f1a2d3b4c86ae8b24860149d", "is_verified": false, - "line_number": 771, + "line_number": 773, "is_secret": false } ], @@ -1845,5 +1845,5 @@ } ] }, - "generated_at": "2025-06-09T16:43:14Z" + "generated_at": "2025-07-23T08:25:11Z" } diff --git a/pom.xml b/pom.xml index eff065fc59..60b4e1e901 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ 5.2.1 4.2.1 7.1 - 1.0.5 + 1.0.9 1.9.22.1 6.2.8 diff --git a/repository/src/main/java/org/alfresco/repo/audit/AuditComponent.java b/repository/src/main/java/org/alfresco/repo/audit/AuditComponent.java index 89730940d5..b000c2166d 100644 --- a/repository/src/main/java/org/alfresco/repo/audit/AuditComponent.java +++ b/repository/src/main/java/org/alfresco/repo/audit/AuditComponent.java @@ -70,6 +70,13 @@ public interface AuditComponent */ public void setUserAuditFilter(UserAuditFilter userAuditFilter); + /** + * @param auditRecordReporter + * AuditRecordReporter + * @since 25.3 + */ + public void setAuditRecordReporter(AuditRecordReporter auditRecordReporter); + /** * Get all registered audit applications, whether active or not. * diff --git a/repository/src/main/java/org/alfresco/repo/audit/AuditComponentImpl.java b/repository/src/main/java/org/alfresco/repo/audit/AuditComponentImpl.java index 5ef5e12fa1..a45ce8587f 100644 --- a/repository/src/main/java/org/alfresco/repo/audit/AuditComponentImpl.java +++ b/repository/src/main/java/org/alfresco/repo/audit/AuditComponentImpl.java @@ -48,7 +48,6 @@ import org.alfresco.repo.audit.model.AuditModelRegistryImpl; import org.alfresco.repo.domain.audit.AuditDAO; import org.alfresco.repo.domain.propval.PropertyValueDAO; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper; @@ -73,8 +72,8 @@ public class AuditComponentImpl implements AuditComponent { private static final String INBOUND_LOGGER = "org.alfresco.repo.audit.inbound"; - private static Log logger = LogFactory.getLog(AuditComponentImpl.class); - private static Log loggerInbound = LogFactory.getLog(INBOUND_LOGGER); + private static final Log logger = LogFactory.getLog(AuditComponentImpl.class); + private static final Log loggerInbound = LogFactory.getLog(INBOUND_LOGGER); private AuditModelRegistryImpl auditModelRegistry; private PropertyValueDAO propertyValueDAO; @@ -82,6 +81,7 @@ public class AuditComponentImpl implements AuditComponent private TransactionService transactionService; private AuditFilter auditFilter; private UserAuditFilter userAuditFilter; + private AuditRecordReporter auditRecordReporter; /** * Default constructor @@ -140,6 +140,11 @@ public class AuditComponentImpl implements AuditComponent this.userAuditFilter = userAuditFilter; } + public void setAuditRecordReporter(AuditRecordReporter auditRecordReporter) + { + this.auditRecordReporter = auditRecordReporter; + } + /** * {@inheritDoc} * @@ -215,7 +220,7 @@ public class AuditComponentImpl implements AuditComponent public int deleteAuditEntries(List auditEntryIds) { // Shortcut, if necessary - if (auditEntryIds.size() == 0) + if (auditEntryIds.isEmpty()) { return 0; } @@ -234,7 +239,7 @@ public class AuditComponentImpl implements AuditComponent { Long disabledPathsId = application.getDisabledPathsId(); Set disabledPaths = (Set) propertyValueDAO.getPropertyById(disabledPathsId); - return new HashSet(disabledPaths); + return new HashSet<>(disabledPaths); } catch (Throwable e) { @@ -254,6 +259,16 @@ public class AuditComponentImpl implements AuditComponent return auditModelRegistry.isAuditEnabled(); } + public boolean isAuditingToDatabaseEnabled() + { + return auditModelRegistry.isAuditingToDatabaseEnabled(); + } + + public boolean isAuditingToAuditStorageEnabled() + { + return auditModelRegistry.isAuditingToAuditStorageEnabled(); + } + /** * {@inheritDoc} * @@ -309,7 +324,7 @@ public class AuditComponentImpl implements AuditComponent { PathMapper pathMapper = auditModelRegistry.getAuditPathMapper(); Set mappedPaths = pathMapper.getMappedPathsWithPartialMatch(path); - return loggerInbound.isDebugEnabled() || mappedPaths.size() > 0; + return loggerInbound.isDebugEnabled() || !mappedPaths.isEmpty(); } /** @@ -346,7 +361,7 @@ public class AuditComponentImpl implements AuditComponent // Check if there are any entries that match or supercede the given path String disablingPath = null; - ; + for (String disabledPath : disabledPaths) { if (path.startsWith(disabledPath)) @@ -573,7 +588,7 @@ public class AuditComponentImpl implements AuditComponent } // Build the key paths using the session root path - Map pathedValues = new HashMap(values.size() * 2); + Map pathedValues = new HashMap<>(values.size() * 2); for (Map.Entry entry : values.entrySet()) { String pathElement = entry.getKey(); @@ -596,12 +611,7 @@ public class AuditComponentImpl implements AuditComponent case TXN_NONE: case TXN_READ_ONLY: // New transaction - RetryingTransactionCallback> callback = new RetryingTransactionCallback>() { - public Map execute() throws Throwable - { - return recordAuditValuesImpl(mappedValues); - } - }; + RetryingTransactionCallback> callback = () -> recordAuditValuesImpl(mappedValues); RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); txnHelper.setForceWritable(true); return txnHelper.doInTransaction(callback, false, true); @@ -618,21 +628,16 @@ public class AuditComponentImpl implements AuditComponent public Map recordAuditValuesImpl(Map mappedValues) { // Group the values by root path - Map> mappedValuesByRootKey = new HashMap>(); + Map> mappedValuesByRootKey = new HashMap<>(); for (Map.Entry entry : mappedValues.entrySet()) { String path = entry.getKey(); String rootKey = AuditApplication.getRootKey(path); - Map rootKeyMappedValues = mappedValuesByRootKey.get(rootKey); - if (rootKeyMappedValues == null) - { - rootKeyMappedValues = new HashMap(7); - mappedValuesByRootKey.put(rootKey, rootKeyMappedValues); - } + Map rootKeyMappedValues = mappedValuesByRootKey.computeIfAbsent(rootKey, k -> new HashMap<>(7)); rootKeyMappedValues.put(path, entry.getValue()); } - Map allAuditedValues = new HashMap(mappedValues.size() * 2 + 1); + Map allAuditedValues = new HashMap<>(mappedValues.size() * 2 + 1); // Now audit for each of the root keys for (Map.Entry> entry : mappedValuesByRootKey.entrySet()) { @@ -694,7 +699,7 @@ public class AuditComponentImpl implements AuditComponent } // Check if there is anything to audit - if (values.size() == 0) + if (values.isEmpty()) { if (logger.isDebugEnabled()) { @@ -727,12 +732,7 @@ public class AuditComponentImpl implements AuditComponent Map auditData = generateData(generators); // Now extract values - Map extractedData = AuthenticationUtil.runAs(new RunAsWork>() { - public Map doWork() throws Exception - { - return extractData(application, values); - } - }, AuthenticationUtil.getSystemUserName()); + Map extractedData = AuthenticationUtil.runAs(() -> extractData(application, values), AuthenticationUtil.getSystemUserName()); // Combine extracted and generated values (extracted data takes precedence) auditData.putAll(extractedData); @@ -743,8 +743,8 @@ public class AuditComponentImpl implements AuditComponent { String root = value.getKey(); int index = root.lastIndexOf("/"); - Map argc = new HashMap(1); - argc.put(root.substring(index, root.length()).substring(1), value.getValue()); + Map argc = new HashMap<>(1); + argc.put(root.substring(index).substring(1), value.getValue()); if (!auditFilter.accept(root.substring(0, index), argc)) { return Collections.emptyMap(); @@ -760,10 +760,15 @@ public class AuditComponentImpl implements AuditComponent { // Persist the values (if not just gathering data in a pre call for use in a post call) boolean justGatherPreCallData = application.isApplicationJustGeneratingPreCallData(); - if (!justGatherPreCallData) + if (!justGatherPreCallData && isAuditingToDatabaseEnabled()) { entryId = auditDAO.createAuditEntry(applicationId, time, username, auditData); } + if (isAuditingToAuditStorageEnabled()) + { + auditRecordReporter.reportAuditRecord(createAuditRecord(auditData, true, username, entryId, application.getApplicationName())); + } + // Done if (logger.isDebugEnabled()) { @@ -822,7 +827,7 @@ public class AuditComponentImpl implements AuditComponent AuditApplication application, Map values) { - Map newData = new HashMap(values.size()); + Map newData = new HashMap<>(values.size()); List extractors = application.getDataExtractors(); for (DataExtractorDefinition extractorDef : extractors) @@ -900,7 +905,7 @@ public class AuditComponentImpl implements AuditComponent */ private Map generateData(Map generators) { - Map newData = new HashMap(generators.size() + 5); + Map newData = new HashMap<>(generators.size() + 5); for (Map.Entry entry : generators.entrySet()) { String path = entry.getKey(); @@ -925,6 +930,20 @@ public class AuditComponentImpl implements AuditComponent return newData; } + /** + * Creates an AuditRecord from the provided audit data. + */ + private AuditRecord createAuditRecord(Map auditData, boolean inTransaction, String username, Long entryId, String applicationName) + { + int rootSize = applicationName.length() + 2; // Root is constructed like this -> '/' + auditedApplicationName + '/'. + AuditRecord.Builder builder = AuditRecordUtils.generateAuditRecordBuilder(auditData, rootSize); + builder.setAuditRecordType(applicationName); + builder.setInTransaction(inTransaction); + builder.setUsername(username); + builder.setEntryDBId(entryId); + return builder.build(); + } + /** * {@inheritDoc} */ diff --git a/repository/src/main/java/org/alfresco/repo/audit/AuditRecord.java b/repository/src/main/java/org/alfresco/repo/audit/AuditRecord.java new file mode 100644 index 0000000000..2c6ab2d77f --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/audit/AuditRecord.java @@ -0,0 +1,130 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 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 . + * #L% + */ +package org.alfresco.repo.audit; + +import java.io.Serializable; +import java.time.ZonedDateTime; +import java.util.Map; + +public class AuditRecord +{ + private final boolean inTransaction; + private final String auditApplicationId; + private final ZonedDateTime createdAt; + private final String username; + private final Long entryDBId; + private final Map auditData; + + public AuditRecord(Builder builder) + { + this.auditApplicationId = builder.auditRecordType; + this.inTransaction = builder.inTransaction; + this.auditData = builder.auditRecordData; + this.createdAt = ZonedDateTime.now(); + this.username = builder.username; + this.entryDBId = builder.entryDBId; + } + + public String getAuditApplicationId() + { + return auditApplicationId; + } + + public boolean isInTransaction() + { + return inTransaction; + } + + public ZonedDateTime getCreatedAt() + { + return createdAt; + } + + public String getUsername() + { + return username; + } + + public Long getEntryDBId() + { + return entryDBId; + } + + public Map getAuditData() + { + return auditData; + } + + public static Builder builder() + { + return new Builder(); + } + + public static class Builder + { + private String auditRecordType; + private boolean inTransaction; + private Map auditRecordData; + private String username; + private Long entryDBId; + + public Builder setAuditRecordType(String auditRecordType) + { + this.auditRecordType = auditRecordType; + return this; + } + + public Builder setInTransaction(boolean inTransaction) + { + this.inTransaction = inTransaction; + return this; + } + + public Builder setAuditRecordData(Map auditRecordData) + { + this.auditRecordData = auditRecordData; + return this; + } + + public Builder setUsername(String username) + { + this.username = username; + return this; + } + + public Builder setEntryDBId(Long entryDBId) + { + this.entryDBId = entryDBId; + return this; + } + + public AuditRecord build() + { + return new AuditRecord(this); + } + } + +} diff --git a/repository/src/main/java/org/alfresco/repo/audit/AuditRecordReporter.java b/repository/src/main/java/org/alfresco/repo/audit/AuditRecordReporter.java new file mode 100644 index 0000000000..7240b7193a --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/audit/AuditRecordReporter.java @@ -0,0 +1,37 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 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 . + * #L% + */ +package org.alfresco.repo.audit; + +public interface AuditRecordReporter +{ + /** + * This method will report AuditRecord to Audit Storage using RepoEvent2 + * + * @param auditRecord + * represent data that will be reported. + */ + void reportAuditRecord(AuditRecord auditRecord); +} diff --git a/repository/src/main/java/org/alfresco/repo/audit/AuditRecordReporterImpl.java b/repository/src/main/java/org/alfresco/repo/audit/AuditRecordReporterImpl.java new file mode 100644 index 0000000000..522d55d2e6 --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/audit/AuditRecordReporterImpl.java @@ -0,0 +1,40 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 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 . + * #L% + */ +package org.alfresco.repo.audit; + +public class AuditRecordReporterImpl implements AuditRecordReporter +{ + /** + * This method intentionally has an empty implementation. + *

+ * This class provides a no-op implementation of {@link AuditRecordReporter}. + */ + @Override + public void reportAuditRecord(AuditRecord auditRecord) + { + // No operation performed. + } +} diff --git a/repository/src/main/java/org/alfresco/repo/audit/AuditRecordUtils.java b/repository/src/main/java/org/alfresco/repo/audit/AuditRecordUtils.java new file mode 100644 index 0000000000..4b2ba81721 --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/audit/AuditRecordUtils.java @@ -0,0 +1,104 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 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 . + * #L% + */ +package org.alfresco.repo.audit; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; + +public final class AuditRecordUtils +{ + private AuditRecordUtils() + { + // This is a utility class and cannot be instantiated. + } + + /** + * Generates an {@link AuditRecord.Builder} from flat audit data. + *

+ * This method: + *

    + *
  • Translates flat {@code key-value} pairs into a nested JSON structure.
  • + *
  • Preloads the builder with the provided arguments.
  • + *
  • Splits keys by {@code /} to build the nested structure.
  • + *
  • Uses the root key as the application ID.
  • + *
  • Assumes each key starts with the same root, constructed as {@code '/' + auditedApplicationName + '/'}, which is removed before splitting.
  • + *
+ * + * @param data + * a map containing flat audit data as `key-value` pairs + * @param keyRootLength + * is a length of key root. + * @return a preloaded {@link AuditRecord.Builder} + */ + public static AuditRecord.Builder generateAuditRecordBuilder(Map data, int keyRootLength) + { + var auditRecordBuilder = AuditRecord.builder(); + + var rootNode = createRootNode(data, keyRootLength); + + auditRecordBuilder.setAuditRecordData(rootNode); + + return auditRecordBuilder; + } + + @SuppressWarnings("unchecked") + private static HashMap createRootNode(Map data, int keyRootLength) + { + var rootNode = new HashMap(); + + data.forEach((k, v) -> { + var keys = k.substring(keyRootLength).split("/"); + + var current = rootNode; + for (int i = 0; i < keys.length - 1; i++) + { + current = (HashMap) current.computeIfAbsent(keys[i], newMap -> new HashMap()); + } + current.put(keys[keys.length - 1], decodeValueByInstance(v)); + }); + return rootNode; + } + + @SuppressWarnings("unchecked") + private static Serializable decodeValueByInstance(Serializable value) + { + if (value instanceof HashMap) + { + return createRootNode((HashMap) value, 0); + } + else if (value instanceof NodeRef) + { + return ((NodeRef) value).getId(); + } + else + { + return value; + } + } +} diff --git a/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistry.java b/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistry.java index 9cb9c713df..e0d02e6d26 100644 --- a/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistry.java +++ b/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistry.java @@ -58,6 +58,21 @@ public interface AuditModelRegistry */ public boolean isAuditEnabled(); + /** + * Determines whether audit values should be stored in database. True by default if not changed by property. + * + * @return true if audit is enabled. + */ + boolean isAuditingToDatabaseEnabled(); + + /** + * Determines whether audit values should be stored in audit storage. + * + * @return true if auditing to Audit Storage is enabled. + * + */ + boolean isAuditingToAuditStorageEnabled(); + /** * Get a map of all audit applications key by name * diff --git a/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistryImpl.java b/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistryImpl.java index 0bf305ddd2..248ea8904e 100644 --- a/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistryImpl.java +++ b/repository/src/main/java/org/alfresco/repo/audit/model/AuditModelRegistryImpl.java @@ -85,6 +85,9 @@ public class AuditModelRegistryImpl extends AbstractPropertyBackedBean implement { /** The name of the global enablement property. */ public static final String PROPERTY_AUDIT_ENABLED = "audit.enabled"; + + private static final String AUDITING_TO_DATABASE = ".auditingToDatabase"; + private static final String AUDITING_TO_AUDIT_STORAGE = ".auditingToAuditStorage"; /** The name of the strict loading flag. */ public static final String PROPERTY_AUDIT_CONFIG_STRICT = "audit.config.strict"; /** The XSD classpath location. */ @@ -249,6 +252,26 @@ public class AuditModelRegistryImpl extends AbstractPropertyBackedBean implement return value != null && value.equalsIgnoreCase("true"); } + /** + * {@inheritDoc} + */ + @Override + public boolean isAuditingToDatabaseEnabled() + { + String value = getProperty(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_DATABASE); + return value == null || value.equalsIgnoreCase("true"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAuditingToAuditStorageEnabled() + { + String value = getProperty(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_AUDIT_STORAGE); + return value != null && value.equalsIgnoreCase("true"); + } + /** * Enables audit and registers an audit model at a given URL. Does not register across the cluster and should only be used for unit test purposes. * @@ -296,6 +319,8 @@ public class AuditModelRegistryImpl extends AbstractPropertyBackedBean implement // Default value for global enabled property properties.put(AUDIT_PROPERTY_AUDIT_ENABLED, false); + properties.put(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_DATABASE, true); + properties.put(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_AUDIT_STORAGE, false); // Let's search for config files in the appropriate places. The individual applications they contain can still // be enabled/disabled by the bean properties diff --git a/repository/src/main/java/org/alfresco/repo/event2/EventJSONSchema.java b/repository/src/main/java/org/alfresco/repo/event2/EventJSONSchema.java index 49fe24c200..6bae6ecf76 100644 --- a/repository/src/main/java/org/alfresco/repo/event2/EventJSONSchema.java +++ b/repository/src/main/java/org/alfresco/repo/event2/EventJSONSchema.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2020 Alfresco Software Limited + * Copyright (C) 2005 - 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 @@ -38,7 +38,7 @@ import org.alfresco.util.Pair; */ public enum EventJSONSchema { - NODE_CREATED_V1("nodeCreated", 1, EventType.NODE_CREATED), NODE_UPDATED_V1("nodeUpdated", 1, EventType.NODE_UPDATED), NODE_DELETED_V1("nodeDeleted", 1, EventType.NODE_DELETED), CHILD_ASSOC_CREATED_V1("childAssocCreated", 1, EventType.CHILD_ASSOC_CREATED), CHILD_ASSOC_DELETED_V1("childAssocDeleted", 1, EventType.CHILD_ASSOC_DELETED), PEER_ASSOC_CREATED_V1("peerAssocCreated", 1, EventType.PEER_ASSOC_CREATED), PEER_ASSOC_DELETED_V1("peerAssocDeleted", 1, EventType.PEER_ASSOC_DELETED), PERMISSION_UPDATED_V1("permissionUpdated", 1, EventType.PERMISSION_UPDATED); + NODE_CREATED_V1("nodeCreated", 1, EventType.NODE_CREATED), NODE_UPDATED_V1("nodeUpdated", 1, EventType.NODE_UPDATED), NODE_DELETED_V1("nodeDeleted", 1, EventType.NODE_DELETED), CHILD_ASSOC_CREATED_V1("childAssocCreated", 1, EventType.CHILD_ASSOC_CREATED), CHILD_ASSOC_DELETED_V1("childAssocDeleted", 1, EventType.CHILD_ASSOC_DELETED), PEER_ASSOC_CREATED_V1("peerAssocCreated", 1, EventType.PEER_ASSOC_CREATED), PEER_ASSOC_DELETED_V1("peerAssocDeleted", 1, EventType.PEER_ASSOC_DELETED), PERMISSION_UPDATED_V1("permissionUpdated", 1, EventType.PERMISSION_UPDATED), AUDIT_ENTRY_CREATED_V1("auditEntryCreated", 1, EventType.AUDIT_ENTRY_CREATED); private static final String PREFIX = "https://api.alfresco.com/schema/event/repo/v"; diff --git a/repository/src/main/resources/alfresco/audit-services-context.xml b/repository/src/main/resources/alfresco/audit-services-context.xml index 229703437a..0a80ea035a 100644 --- a/repository/src/main/resources/alfresco/audit-services-context.xml +++ b/repository/src/main/resources/alfresco/audit-services-context.xml @@ -37,8 +37,9 @@ + - + @@ -108,5 +109,8 @@ - - \ No newline at end of file + + + + + diff --git a/repository/src/main/resources/alfresco/repository.properties b/repository/src/main/resources/alfresco/repository.properties index e9b55c5c3a..1c88e94740 100644 --- a/repository/src/main/resources/alfresco/repository.properties +++ b/repository/src/main/resources/alfresco/repository.properties @@ -286,6 +286,8 @@ audit.alfresco-access.enabled=false audit.alfresco-access.sub-actions.enabled=false audit.cmischangelog.enabled=false audit.dod5015.enabled=false +audit.enabled.auditingToAuditStorage=false +audit.enabled.auditingToDatabase=true # Setting this flag to true will force startup failure when invalid audit configurations are detected audit.config.strict=false # Audit map filter for AccessAuditor - restricts recorded events to user driven events diff --git a/repository/src/test/java/org/alfresco/AppContext02TestSuite.java b/repository/src/test/java/org/alfresco/AppContext02TestSuite.java index 5d6da7f4b3..7fb04e39f6 100644 --- a/repository/src/test/java/org/alfresco/AppContext02TestSuite.java +++ b/repository/src/test/java/org/alfresco/AppContext02TestSuite.java @@ -48,6 +48,7 @@ import org.alfresco.util.testing.category.NonBuildTests; org.alfresco.repo.audit.UserAuditFilterTest.class, org.alfresco.repo.audit.AuditMethodInterceptorTest.class, org.alfresco.repo.audit.access.AccessAuditorTest.class, + org.alfresco.repo.audit.AuditRecordUtilsTest.class, // the following test will lock up the DB if run in the applicationContext_01 test suite org.alfresco.repo.activities.feed.FeedNotifierTest.class, diff --git a/repository/src/test/java/org/alfresco/repo/audit/AuditComponentTest.java b/repository/src/test/java/org/alfresco/repo/audit/AuditComponentTest.java index b59ac8351c..76ce4a18a6 100644 --- a/repository/src/test/java/org/alfresco/repo/audit/AuditComponentTest.java +++ b/repository/src/test/java/org/alfresco/repo/audit/AuditComponentTest.java @@ -877,6 +877,7 @@ public class AuditComponentTest extends TestCase auditModelRegistry.loadAuditModels(); auditModelRegistry.setProperty("audit.enabled", "true"); + auditModelRegistry.setProperty("audit.enabled.auditingToDatabase", "true"); auditModelRegistry.setProperty("audit.app1.enabled", "true"); auditModelRegistry.setProperty("audit.filter.app1.default.enabled", "true"); diff --git a/repository/src/test/java/org/alfresco/repo/audit/AuditRecordUtilsTest.java b/repository/src/test/java/org/alfresco/repo/audit/AuditRecordUtilsTest.java new file mode 100644 index 0000000000..009c544686 --- /dev/null +++ b/repository/src/test/java/org/alfresco/repo/audit/AuditRecordUtilsTest.java @@ -0,0 +1,123 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 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 . + * #L% + */ + +package org.alfresco.repo.audit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.Serializable; +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +public class AuditRecordUtilsTest +{ + @SuppressWarnings("unchecked") + @Test + public void testGenerateAuditRecordBuilderTest() + { + var testData = new HashMap(); + + testData.put("/alfresco-access/transaction/path", "/app:company_home"); + testData.put("/alfresco-access/transaction/user", "admin"); + testData.put("/alfresco-access/transaction/sub-actions", "updateNodeProperties"); + var now = Instant.now(); + testData.put("/alfresco-access/transaction/properties/from", (Serializable) Map.of(QName.createQName("modified"), Date.from(now))); + testData.put("/alfresco-access/transaction/properties/to", (Serializable) Map.of(QName.createQName("modified"), Date.from(now))); + + var builder = AuditRecordUtils.generateAuditRecordBuilder(testData, "/alfresco-access/".length()); + builder.setAuditRecordType("alfresco-access"); + var auditRecord = builder.build(); + + assertNotNull(auditRecord); + assertEquals("alfresco-access", auditRecord.getAuditApplicationId()); + + var auditData = auditRecord.getAuditData(); + assertEquals(1, auditData.size()); + + var transaction = (HashMap) auditData.get("transaction"); + assertNotNull(transaction); + assertEquals(4, transaction.size()); + assertEquals(testData.get("/alfresco-access/transaction/path"), transaction.get("path")); + assertEquals(testData.get("/alfresco-access/transaction/user"), transaction.get("user")); + assertEquals(testData.get("/alfresco-access/transaction/sub-actions"), transaction.get("sub-actions")); + + var properties = (HashMap) transaction.get("properties"); + assertNotNull(properties); + assertEquals(2, properties.size()); + assertEquals(testData.get("/alfresco-access/transaction/properties/from"), properties.get("from")); + assertEquals(testData.get("/alfresco-access/transaction/properties/to"), properties.get("to")); + + } + + @SuppressWarnings("unchecked") + @Test + public void testGenerateAuditRecordBuilderTestNodeRef() + { + var testData = new HashMap(); + var expectedValue = new HashMap(); + + expectedValue.put("nodeRef", new NodeRef("workspace://SpacesStore/bfa612e6-1a02-46a0-a612-e61a02e6a036")); + expectedValue.put("objectId", "bfa612e6-1a02-46a0-a612-e61a02e6a036;1.0"); + + testData.put("/CMISChangeLog/CREATED/result/value", expectedValue); + + var builder = AuditRecordUtils.generateAuditRecordBuilder(testData, "/CMISChangeLog/".length()); + builder.setAuditRecordType("CMISChangeLog"); + var auditRecord = builder.build(); + + assertNotNull(auditRecord); + + assertEquals("CMISChangeLog", auditRecord.getAuditApplicationId()); + + var auditData = auditRecord.getAuditData(); + assertEquals(1, auditData.size()); + + var created = (HashMap) auditData.get("CREATED"); + assertNotNull(created); + + assertEquals(1, created.size()); + var result = (HashMap) created.get("result"); + assertNotNull(result); + assertEquals(1, result.size()); + + var resultValue = (HashMap) result.get("value"); + assertNotNull(resultValue); + assertEquals(2, resultValue.size()); + + var expectedNodeRef = (NodeRef) expectedValue.get("nodeRef"); + assertEquals(expectedNodeRef.getId(), resultValue.get("nodeRef")); + assertEquals(expectedValue.get("objectId"), resultValue.get("objectId")); + + } +} diff --git a/repository/src/test/java/org/alfresco/repo/audit/AuditTestSuite.java b/repository/src/test/java/org/alfresco/repo/audit/AuditTestSuite.java index 754f3412f8..b9c7c6bad8 100644 --- a/repository/src/test/java/org/alfresco/repo/audit/AuditTestSuite.java +++ b/repository/src/test/java/org/alfresco/repo/audit/AuditTestSuite.java @@ -50,6 +50,7 @@ public class AuditTestSuite extends TestSuite suite.addTestSuite(UserAuditFilterTest.class); suite.addTestSuite(AuditMethodInterceptorTest.class); + suite.addTest(new JUnit4TestAdapter(AuditRecordUtilsTest.class)); suite.addTest(new JUnit4TestAdapter(PropertyAuditFilterTest.class)); suite.addTest(new JUnit4TestAdapter(AccessAuditorTest.class));