mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-09-10 14:11:58 +00:00
Compare commits
41 Commits
25.3.0.33
...
feature/AC
Author | SHA1 | Date | |
---|---|---|---|
|
ecb373252d | ||
|
872ad1c185 | ||
|
b26d8f9037 | ||
|
affb8bcf20 | ||
|
70544df30d | ||
|
7e1a631bdd | ||
|
fa24c26e30 | ||
|
76f720cffa | ||
|
23aecaebd1 | ||
|
7ece5d6096 | ||
|
b2a74e0044 | ||
|
7295b60406 | ||
|
4417886601 | ||
|
6a7b86087c | ||
|
a5ec7043a7 | ||
|
6a0f73905b | ||
|
2bdf65a6d1 | ||
|
c3408033ae | ||
|
4fdb9ac4c3 | ||
|
2556e3dd2a | ||
|
55c4c35c81 | ||
|
672e2427c1 | ||
|
ae562db334 | ||
|
45f58e6a96 | ||
|
4bf7c74848 | ||
|
be5658548d | ||
|
eee26d1491 | ||
|
685842fe00 | ||
|
0114731890 | ||
|
e47bf16332 | ||
|
e29d135859 | ||
|
a2d2bd28c1 | ||
|
3b82e9d9bb | ||
|
d49f989af2 | ||
|
3261cc1bd7 | ||
|
1c7f7cba24 | ||
|
ecec9ddaaa | ||
|
e1067e3156 | ||
|
9336090a5a | ||
|
207c207980 | ||
|
410348960f |
@@ -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"
|
||||
}
|
||||
|
2
pom.xml
2
pom.xml
@@ -54,7 +54,7 @@
|
||||
<dependency.alfresco-transform-core.version>5.2.1</dependency.alfresco-transform-core.version>
|
||||
<dependency.alfresco-transform-service.version>4.2.1</dependency.alfresco-transform-service.version>
|
||||
<dependency.alfresco-greenmail.version>7.1</dependency.alfresco-greenmail.version>
|
||||
<dependency.acs-event-model.version>1.0.5</dependency.acs-event-model.version>
|
||||
<dependency.acs-event-model.version>1.0.9</dependency.acs-event-model.version>
|
||||
|
||||
<dependency.aspectj.version>1.9.22.1</dependency.aspectj.version>
|
||||
<dependency.spring.version>6.2.8</dependency.spring.version>
|
||||
|
@@ -70,6 +70,13 @@ public interface AuditComponent
|
||||
*/
|
||||
public void setUserAuditFilter(UserAuditFilter userAuditFilter);
|
||||
|
||||
/**
|
||||
* @param auditRecordReporter
|
||||
* AuditRecordReporter
|
||||
* @since 25.2
|
||||
*/
|
||||
public void setAuditRecordReporter(AuditRecordReporter auditRecordReporter);
|
||||
|
||||
/**
|
||||
* Get all registered audit applications, whether active or not.
|
||||
*
|
||||
|
@@ -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<Long> 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<String> disabledPaths = (Set<String>) propertyValueDAO.getPropertyById(disabledPathsId);
|
||||
return new HashSet<String>(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<String> 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<String, Serializable> pathedValues = new HashMap<String, Serializable>(values.size() * 2);
|
||||
Map<String, Serializable> pathedValues = new HashMap<>(values.size() * 2);
|
||||
for (Map.Entry<String, Serializable> 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<Map<String, Serializable>> callback = new RetryingTransactionCallback<Map<String, Serializable>>() {
|
||||
public Map<String, Serializable> execute() throws Throwable
|
||||
{
|
||||
return recordAuditValuesImpl(mappedValues);
|
||||
}
|
||||
};
|
||||
RetryingTransactionCallback<Map<String, Serializable>> 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<String, Serializable> recordAuditValuesImpl(Map<String, Serializable> mappedValues)
|
||||
{
|
||||
// Group the values by root path
|
||||
Map<String, Map<String, Serializable>> mappedValuesByRootKey = new HashMap<String, Map<String, Serializable>>();
|
||||
Map<String, Map<String, Serializable>> mappedValuesByRootKey = new HashMap<>();
|
||||
for (Map.Entry<String, Serializable> entry : mappedValues.entrySet())
|
||||
{
|
||||
String path = entry.getKey();
|
||||
String rootKey = AuditApplication.getRootKey(path);
|
||||
Map<String, Serializable> rootKeyMappedValues = mappedValuesByRootKey.get(rootKey);
|
||||
if (rootKeyMappedValues == null)
|
||||
{
|
||||
rootKeyMappedValues = new HashMap<String, Serializable>(7);
|
||||
mappedValuesByRootKey.put(rootKey, rootKeyMappedValues);
|
||||
}
|
||||
Map<String, Serializable> rootKeyMappedValues = mappedValuesByRootKey.computeIfAbsent(rootKey, k -> new HashMap<>(7));
|
||||
rootKeyMappedValues.put(path, entry.getValue());
|
||||
}
|
||||
|
||||
Map<String, Serializable> allAuditedValues = new HashMap<String, Serializable>(mappedValues.size() * 2 + 1);
|
||||
Map<String, Serializable> allAuditedValues = new HashMap<>(mappedValues.size() * 2 + 1);
|
||||
// Now audit for each of the root keys
|
||||
for (Map.Entry<String, Map<String, Serializable>> 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<String, Serializable> auditData = generateData(generators);
|
||||
|
||||
// Now extract values
|
||||
Map<String, Serializable> extractedData = AuthenticationUtil.runAs(new RunAsWork<Map<String, Serializable>>() {
|
||||
public Map<String, Serializable> doWork() throws Exception
|
||||
{
|
||||
return extractData(application, values);
|
||||
}
|
||||
}, AuthenticationUtil.getSystemUserName());
|
||||
Map<String, Serializable> 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<String, Serializable> argc = new HashMap<String, Serializable>(1);
|
||||
argc.put(root.substring(index, root.length()).substring(1), value.getValue());
|
||||
Map<String, Serializable> 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<String, Serializable> values)
|
||||
{
|
||||
Map<String, Serializable> newData = new HashMap<String, Serializable>(values.size());
|
||||
Map<String, Serializable> newData = new HashMap<>(values.size());
|
||||
|
||||
List<DataExtractorDefinition> extractors = application.getDataExtractors();
|
||||
for (DataExtractorDefinition extractorDef : extractors)
|
||||
@@ -900,7 +905,7 @@ public class AuditComponentImpl implements AuditComponent
|
||||
*/
|
||||
private Map<String, Serializable> generateData(Map<String, DataGenerator> generators)
|
||||
{
|
||||
Map<String, Serializable> newData = new HashMap<String, Serializable>(generators.size() + 5);
|
||||
Map<String, Serializable> newData = new HashMap<>(generators.size() + 5);
|
||||
for (Map.Entry<String, DataGenerator> entry : generators.entrySet())
|
||||
{
|
||||
String path = entry.getKey();
|
||||
@@ -925,6 +930,28 @@ public class AuditComponentImpl implements AuditComponent
|
||||
return newData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AuditRecord from the provided audit data.
|
||||
*
|
||||
* @param auditData
|
||||
* the map containing audit data
|
||||
* @param inTransaction
|
||||
* whether the record is created within a transaction
|
||||
* @param applicationName
|
||||
* the name of the audit application
|
||||
* @return a constructed AuditRecord instance
|
||||
*/
|
||||
private AuditRecord createAuditRecord(Map<String, Serializable> 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}
|
||||
*/
|
||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #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<String, Serializable> 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<String, Serializable> getAuditData()
|
||||
{
|
||||
return auditData;
|
||||
}
|
||||
|
||||
public static Builder builder()
|
||||
{
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
private String auditRecordType;
|
||||
private boolean inTransaction;
|
||||
private Map<String, Serializable> 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<String, Serializable> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #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);
|
||||
}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.audit;
|
||||
|
||||
public class AuditRecordReporterImpl implements AuditRecordReporter
|
||||
{
|
||||
/**
|
||||
* This method intentionally has an empty implementation.
|
||||
* <p>
|
||||
* This class provides a no-op implementation of {@link AuditRecordReporter}. Implement this method if audit record reporting is required in the future.
|
||||
*/
|
||||
@Override
|
||||
public void reportAuditRecord(AuditRecord auditRecord)
|
||||
{
|
||||
// No operation performed.
|
||||
}
|
||||
}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #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 class AuditRecordUtils
|
||||
{
|
||||
private AuditRecordUtils()
|
||||
{
|
||||
// This is a utility class and cannot be instantiated.
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an {@link AuditRecord.Builder} from flat audit data.
|
||||
* <p>
|
||||
* This method:
|
||||
* <ul>
|
||||
* <li>Translates flat {@code key-value} pairs into a nested JSON structure.</li>
|
||||
* <li>Preloads the builder with the provided arguments.</li>
|
||||
* <li>Splits keys by {@code /} to build the nested structure.</li>
|
||||
* <li>Uses the root key as the application ID.</li>
|
||||
* <li>Assumes each key starts with the same root, constructed as {@code '/' + auditedApplicationName + '/'}, which is removed before splitting.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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<String, Serializable> data, int keyRootLength)
|
||||
{
|
||||
var auditRecordBuilder = AuditRecord.builder();
|
||||
|
||||
var rootNode = createRootNode(data, keyRootLength);
|
||||
|
||||
auditRecordBuilder.setAuditRecordData(rootNode);
|
||||
|
||||
return auditRecordBuilder;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static HashMap<String, Serializable> createRootNode(Map<String, Serializable> data, int keyRootLength)
|
||||
{
|
||||
var rootNode = new HashMap<String, Serializable>();
|
||||
|
||||
data.forEach((k, v) -> {
|
||||
var keys = k.substring(keyRootLength).split("/");
|
||||
|
||||
var current = rootNode;
|
||||
for (int i = 0; i < keys.length - 1; i++)
|
||||
{
|
||||
current = (HashMap<String, Serializable>) current.computeIfAbsent(keys[i], newMap -> new HashMap<String, Serializable>());
|
||||
}
|
||||
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<String, Serializable>) value, 0);
|
||||
}
|
||||
else if (value instanceof NodeRef)
|
||||
{
|
||||
return ((NodeRef) value).getId();
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
@@ -58,6 +58,21 @@ public interface AuditModelRegistry
|
||||
*/
|
||||
public boolean isAuditEnabled();
|
||||
|
||||
/**
|
||||
* Determines whether audit values should be stored in database. <code>True</code> by default if not changed by property.
|
||||
*
|
||||
* @return <code>true</code> if audit is enabled.
|
||||
*/
|
||||
boolean isAuditingToDatabaseEnabled();
|
||||
|
||||
/**
|
||||
* Determines whether audit values should be stored in audit storage.
|
||||
*
|
||||
* @return <code>true</code> if auditing to Audit Storage is enabled.
|
||||
*
|
||||
*/
|
||||
boolean isAuditingToAuditStorageEnabled();
|
||||
|
||||
/**
|
||||
* Get a map of all audit applications key by name
|
||||
*
|
||||
|
@@ -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
|
||||
|
@@ -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";
|
||||
|
||||
|
@@ -37,8 +37,9 @@
|
||||
<property name="properties" ref="global-properties" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="auditRecordReporter" ref="auditRecordReporter"/>
|
||||
</bean>
|
||||
|
||||
|
||||
<!-- User Audit Filter -->
|
||||
|
||||
<bean id="userAuditFilter" class="org.alfresco.repo.audit.UserAuditFilter">
|
||||
@@ -108,5 +109,8 @@
|
||||
|
||||
<!-- Reference in the audit registry managed bean -->
|
||||
<alias name="Audit" alias="auditModel.modelRegistry"/>
|
||||
|
||||
</beans>
|
||||
|
||||
<!-- Audit Record Reported -->
|
||||
<bean id="auditRecordReporter" class="org.alfresco.repo.audit.AuditRecordReporterImpl"/>
|
||||
|
||||
</beans>
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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");
|
||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #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<String, Serializable>();
|
||||
|
||||
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<String, ?>) 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<String, Object>) 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<String, Serializable>();
|
||||
var expectedValue = new HashMap<String, Serializable>();
|
||||
|
||||
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<String, ?>) auditData.get("CREATED");
|
||||
assertNotNull(created);
|
||||
|
||||
assertEquals(1, created.size());
|
||||
var result = (HashMap<String, Object>) created.get("result");
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.size());
|
||||
|
||||
var resultValue = (HashMap<String, Object>) 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"));
|
||||
|
||||
}
|
||||
}
|
@@ -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));
|
||||
|
||||
|
Reference in New Issue
Block a user