diff --git a/config/alfresco/audit-services-context.xml b/config/alfresco/audit-services-context.xml index 6a9a0539f6..7d8549e0c3 100644 --- a/config/alfresco/audit-services-context.xml +++ b/config/alfresco/audit-services-context.xml @@ -38,6 +38,7 @@ + diff --git a/source/java/org/alfresco/repo/audit/AuditBootstrapTest.java b/source/java/org/alfresco/repo/audit/AuditBootstrapTest.java index 38a0f329d2..4594275347 100644 --- a/source/java/org/alfresco/repo/audit/AuditBootstrapTest.java +++ b/source/java/org/alfresco/repo/audit/AuditBootstrapTest.java @@ -121,7 +121,9 @@ public class AuditBootstrapTest extends TestCase public void testGetApplicationId() { - Long appId = auditModelRegistry.getAuditApplicationId(APPLICATION_TEST); + AuditApplication app = auditModelRegistry.getAuditApplication(APPLICATION_TEST); + assertNotNull(app); + Long appId = app.getApplicationId(); assertNotNull("No audit application ID for " + APPLICATION_TEST, appId); } diff --git a/source/java/org/alfresco/repo/audit/AuditComponent.java b/source/java/org/alfresco/repo/audit/AuditComponent.java index 13637a28e6..48b0b59b3f 100644 --- a/source/java/org/alfresco/repo/audit/AuditComponent.java +++ b/source/java/org/alfresco/repo/audit/AuditComponent.java @@ -83,6 +83,50 @@ public interface AuditComponent /* * V3.2 from here on. Put all fixes to the older audit code before this point, please. */ + + /** + * Enable auditing (if it is not already enabled) for all paths that contain the given path. + * The path is the path as originally logged (see {@link #audit(String, String, Map)}) and + * not the path that the generated data may contain - although this would be similarly + * enabled. + *

+ * If the enabled + * + * @param applicationName the name of the application being logged to + * @param path the audit path to enable auditing on + * + * @since 3.2 + */ + void enableAudit(String applicationName, String path); + + /** + * Disable auditing (if it is not already disabled) for all paths that contain the given path. + * The path is the path as originally logged (see {@link #audit(String, String, Map)}) and + * not the path that the generated data may contain - although this would be similarly + * disabled. + *

+ * If the path is /x/y then any data paths that start with /x/y will be stripped + * out before data generators and data recorders are applied. If the path represents + * the root path of the application, then auditing for that application is effectively disabled. + * + * @param applicationName the name of the application being logged to + * @param path the audit path to enable auditing on + * + * @since 3.2 + */ + void disableAudit(String applicationName, String path); + + /** + * Remove all disabled paths i.e. enable all per-path based auditing. Auditing may still be + * disabled globally. This is primarily for test purposes; applications should know which + * paths need {@link #enableAudit(String, String) enabling} or + * {@link #disableAudit(String, String) disabling}. + * + * @param applicationName the name of the application + * + * @since 3.2 + */ + void resetDisabledPaths(String applicationName); /** * Record a set of values against the given session. The map is a path - starting with '/' @@ -118,6 +162,8 @@ public interface AuditComponent * @param from the start search time (null to start at the beginning) * @param to the end search time (null for no limit) * @param maxResults the maximum number of results to retrieve (zero or negative to ignore) + * + * @since 3.2 */ void auditQuery( AuditQueryCallback callback, @@ -135,6 +181,8 @@ public interface AuditComponent * @param searchKey the audit key path that must exist (null to ignore) * @param searchString an audit value string that must exist (null to ignore) * @param maxResults the maximum number of results to retrieve (zero or negative to ignore) + * + * @since 3.2 */ void auditQuery( AuditQueryCallback callback, diff --git a/source/java/org/alfresco/repo/audit/AuditComponentImpl.java b/source/java/org/alfresco/repo/audit/AuditComponentImpl.java index a3a2de7167..8a498cfc30 100644 --- a/source/java/org/alfresco/repo/audit/AuditComponentImpl.java +++ b/source/java/org/alfresco/repo/audit/AuditComponentImpl.java @@ -31,8 +31,11 @@ import java.net.UnknownHostException; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.audit.extractor.DataExtractor; @@ -42,6 +45,7 @@ import org.alfresco.repo.audit.model.AuditEntry; import org.alfresco.repo.audit.model.AuditModelRegistry; import org.alfresco.repo.audit.model.TrueFalseUnset; 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.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; @@ -759,6 +763,7 @@ public class AuditComponentImpl implements AuditComponent */ private AuditModelRegistry auditModelRegistry; + private PropertyValueDAO propertyValueDAO; /** * Set the registry holding the audit models @@ -769,6 +774,200 @@ public class AuditComponentImpl implements AuditComponent this.auditModelRegistry = auditModelRegistry; } + /** + * Set the DAO for manipulating property values + * @since 3.2 + */ + public void setPropertyValueDAO(PropertyValueDAO propertyValueDAO) + { + this.propertyValueDAO = propertyValueDAO; + } + + /** + * @param application the audit application object + * @return Returns a copy of the set of disabled paths associated with the application + */ + @SuppressWarnings("unchecked") + private Set getDisabledPaths(AuditApplication application) + { + try + { + Long disabledPathsId = application.getDisabledPathsId(); + Set disabledPaths = (Set) propertyValueDAO.getPropertyById(disabledPathsId); + return new HashSet(disabledPaths); + } + catch (Throwable e) + { + // Might be an invalid ID, somehow + auditModelRegistry.loadAuditModels(); + throw new AlfrescoRuntimeException("Unabled to get AuditApplication disabled paths: " + application, e); + } + } + + /** + * {@inheritDoc} + * @since 3.2 + */ + public void enableAudit(String applicationName, String path) + { + ParameterCheck.mandatory("applicationName", applicationName); + ParameterCheck.mandatory("path", path); + + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE) + { + throw new IllegalStateException("Auditing requires a read-write transaction."); + } + + AuditApplication application = auditModelRegistry.getAuditApplication(applicationName); + if (application == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("No audit application named '" + applicationName + "' has been registered."); + } + return; + } + // Check the path against the application + application.checkPath(path); + + Long disabledPathsId = application.getDisabledPathsId(); + Set disabledPaths = getDisabledPaths(application); + + // Remove any paths that start with the given path + boolean changed = false; + Iterator iterateDisabledPaths = disabledPaths.iterator(); + while (iterateDisabledPaths.hasNext()) + { + String disabledPath = iterateDisabledPaths.next(); + if (disabledPath.startsWith(path)) + { + iterateDisabledPaths.remove(); + changed = true; + } + } + // Persist, if necessary + if (changed) + { + propertyValueDAO.updateProperty(disabledPathsId, (Serializable) disabledPaths); + if (logger.isDebugEnabled()) + { + logger.debug( + "Audit disabled paths updated: \n" + + " Application: " + applicationName + "\n" + + " Disabled: " + disabledPaths); + } + } + // Done + } + + /** + * {@inheritDoc} + * @since 3.2 + */ + public void disableAudit(String applicationName, String path) + { + ParameterCheck.mandatory("applicationName", applicationName); + ParameterCheck.mandatory("path", path); + + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE) + { + throw new IllegalStateException("Auditing requires a read-write transaction."); + } + + AuditApplication application = auditModelRegistry.getAuditApplication(applicationName); + if (application == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("No audit application named '" + applicationName + "' has been registered."); + } + return; + } + // Check the path against the application + application.checkPath(path); + + Long disabledPathsId = application.getDisabledPathsId(); + Set disabledPaths = getDisabledPaths(application); + + // Shortcut if the disabled paths contain the exact path + if (disabledPaths.contains(path)) + { + if (logger.isDebugEnabled()) + { + logger.debug( + "Audit disable path already present: \n" + + " Path: " + path); + } + return; + } + + // Bring the set up to date by stripping out unwanted paths + Iterator iterateDisabledPaths = disabledPaths.iterator(); + while (iterateDisabledPaths.hasNext()) + { + String disabledPath = iterateDisabledPaths.next(); + if (disabledPath.startsWith(path)) + { + // We will be superceding this + iterateDisabledPaths.remove(); + } + else if (path.startsWith(disabledPath)) + { + // There is already a superceding path + if (logger.isDebugEnabled()) + { + logger.debug( + "Audit disable path superceded: \n" + + " Path: " + path + "\n" + + " Superceded by: " + disabledPath); + } + return; + } + } + // Add our path in + disabledPaths.add(path); + // Upload the new set + propertyValueDAO.updateProperty(disabledPathsId, (Serializable) disabledPaths); + // Done + if (logger.isDebugEnabled()) + { + logger.debug( + "Audit disabled paths updated: \n" + + " Application: " + applicationName + "\n" + + " Disabled: " + disabledPaths); + } + } + + /** + * {@inheritDoc} + * @since 3.2 + */ + public void resetDisabledPaths(String applicationName) + { + ParameterCheck.mandatory("applicationName", applicationName); + + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE) + { + throw new IllegalStateException("Auditing requires a read-write transaction."); + } + AuditApplication application = auditModelRegistry.getAuditApplication(applicationName); + if (application == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("No audit application named '" + applicationName + "' has been registered."); + } + return; + } + Long disabledPathsId = application.getDisabledPathsId(); + propertyValueDAO.updateProperty(disabledPathsId, (Serializable) Collections.emptySet()); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Removed all disabled paths for application " + applicationName); + } + } + /** * {@inheritDoc} * @since 3.2 @@ -801,13 +1000,27 @@ public class AuditComponentImpl implements AuditComponent // Check the path against the application application.checkPath(rootPath); // Get the model ID for the application - Long applicationId = auditModelRegistry.getAuditApplicationId(applicationName); + Long applicationId = application.getApplicationId(); if (applicationId == null) { throw new AuditException("No persisted instance exists for audit application: " + applicationName); } - - // TODO: Check if the root path is enabled or not + + // Get all disabled paths + Set disabledPaths = getDisabledPaths(application); + // Check if the root path has been disabled + // This is a fast check and will usually be activated if there are any exclusions + for (String disabledPath : disabledPaths) + { + if (rootPath.startsWith(disabledPath)) + { + logger.debug( + "Audit values root path has been excluded by disabled paths: \n" + + " Application: " + application + "\n" + + " Root Path: " + rootPath); + return Collections.emptyMap(); + } + } // Build the key paths using the session root path Map pathedValues = new HashMap(values.size() * 2); @@ -818,6 +1031,34 @@ public class AuditComponentImpl implements AuditComponent pathedValues.put(path, entry.getValue()); } + // Eliminate any paths that have been disabled + Iterator pathedValuesKeyIterator = pathedValues.keySet().iterator(); + while(pathedValuesKeyIterator.hasNext()) + { + String pathedValueKey = pathedValuesKeyIterator.next(); + for (String disabledPath : disabledPaths) + { + if (pathedValueKey.startsWith(disabledPath)) + { + // The pathed value is excluded + pathedValuesKeyIterator.remove(); + } + } + } + // Check if there is anything left + if (pathedValues.size() == 0) + { + if (logger.isDebugEnabled()) + { + logger.debug( + "Audit values have all been excluded by disabled paths: \n" + + " Application: " + application + "\n" + + " Root Path: " + rootPath + "\n" + + " Values: " + values); + } + return Collections.emptyMap(); + } + // Generate data Map generators = application.getDataGenerators(pathedValues.keySet()); Map auditData = generateData(generators); diff --git a/source/java/org/alfresco/repo/audit/AuditComponentTest.java b/source/java/org/alfresco/repo/audit/AuditComponentTest.java index 3a2e3f8341..03383dc59d 100644 --- a/source/java/org/alfresco/repo/audit/AuditComponentTest.java +++ b/source/java/org/alfresco/repo/audit/AuditComponentTest.java @@ -105,6 +105,17 @@ public class AuditComponentTest extends TestCase // Authenticate user = "User-" + getName(); AuthenticationUtil.setFullyAuthenticatedUser(user); + + final RetryingTransactionCallback resetDisabledPathsCallback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + auditComponent.resetDisabledPaths(APPLICATION_TEST); + auditComponent.resetDisabledPaths(APPLICATION_ACTIONS_TEST); + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(resetDisabledPathsCallback); } @Override @@ -368,4 +379,74 @@ public class AuditComponentTest extends TestCase logger.debug(sb.toString()); } + + /** + * Test disabling of audit using audit paths + */ + public void testAudit_EnableDisableAuditPaths() throws Exception + { + Serializable valueA = new Date(); + Serializable valueB = "BBB-value-here"; + Serializable valueC = new Float(16.0F); + // Get a noderef + final Map parameters = new HashMap(13); + parameters.put("A", valueA); + parameters.put("B", valueB); + parameters.put("C", valueC); + // lowercase versions are not in the config + parameters.put("a", valueA); + parameters.put("b", valueB); + parameters.put("c", valueC); + + Map result = auditTestAction("action-01", nodeRef, parameters); + + final Map expected = new HashMap(); + expected.put("/actions-test/actions/user", AuthenticationUtil.getFullyAuthenticatedUser()); + expected.put("/actions-test/actions/context-node/noderef", nodeRef); + expected.put("/actions-test/actions/action-01/params/A/value", valueA); + expected.put("/actions-test/actions/action-01/params/B/value", valueB); + expected.put("/actions-test/actions/action-01/params/C/value", valueC); + + // Check + checkAuditMaps(result, expected); + + // Good. Now disable a path and recheck + RetryingTransactionCallback disableAuditCallback = new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + Map expectedInner = new HashMap(expected); + + auditComponent.disableAudit(APPLICATION_ACTIONS_TEST, "/actions-test/actions/action-01/params/A"); + expectedInner.remove("/actions-test/actions/action-01/params/A/value"); + Map result = auditTestAction("action-01", nodeRef, parameters); + checkAuditMaps(result, expectedInner); + + auditComponent.disableAudit(APPLICATION_ACTIONS_TEST, "/actions-test/actions/action-01/params/B"); + expectedInner.remove("/actions-test/actions/action-01/params/B/value"); + result = auditTestAction("action-01", nodeRef, parameters); + checkAuditMaps(result, expectedInner); + + auditComponent.disableAudit(APPLICATION_ACTIONS_TEST, "/actions-test"); + expectedInner.clear(); + result = auditTestAction("action-01", nodeRef, parameters); + checkAuditMaps(result, expectedInner); + + // Enabling something lower down should make no difference + auditComponent.enableAudit(APPLICATION_ACTIONS_TEST, "/actions-test/actions/action-01/params/B"); + expectedInner.clear(); + result = auditTestAction("action-01", nodeRef, parameters); + checkAuditMaps(result, expectedInner); + + // Enabling the root should give back everything + auditComponent.enableAudit(APPLICATION_ACTIONS_TEST, "/actions-test"); + expectedInner = new HashMap(expected); + result = auditTestAction("action-01", nodeRef, parameters); + checkAuditMaps(result, expectedInner); + + return null; + } + }; + transactionService.getRetryingTransactionHelper().doInTransaction(disableAuditCallback, false); + } } diff --git a/source/java/org/alfresco/repo/audit/model/AuditApplication.java b/source/java/org/alfresco/repo/audit/model/AuditApplication.java index bdeabc7a59..99a5f6493c 100644 --- a/source/java/org/alfresco/repo/audit/model/AuditApplication.java +++ b/source/java/org/alfresco/repo/audit/model/AuditApplication.java @@ -62,6 +62,8 @@ public class AuditApplication private final Map dataGeneratorsByName; @SuppressWarnings("unused") private final Application application; + private final Long applicationId; + private final Long disabledPathsId; /** Derived expaned map for fast lookup */ private Map> dataExtractors = new HashMap>(11); @@ -76,7 +78,9 @@ public class AuditApplication /* package */ AuditApplication( Map dataExtractorsByName, Map dataGeneratorsByName, - Application application) + Application application, + Long applicationId, + Long disabledPathsId) { this.dataExtractorsByName = dataExtractorsByName; this.dataGeneratorsByName = dataGeneratorsByName; @@ -84,6 +88,8 @@ public class AuditApplication this.applicationName = application.getName(); this.applicationKey = application.getKey(); + this.applicationId = applicationId; + this.disabledPathsId = disabledPathsId; buildAuditPaths(application); } @@ -118,6 +124,8 @@ public class AuditApplication StringBuilder sb = new StringBuilder(56); sb.append("AuditApplication") .append("[ name=").append(applicationName) + .append(", id=").append(applicationId) + .append(", disabledPathsId=").append(disabledPathsId) .append("]"); return sb.toString(); } @@ -138,6 +146,24 @@ public class AuditApplication return applicationKey; } + /** + * Get the database ID for this application + */ + public Long getApplicationId() + { + return applicationId; + } + + /** + * Get the property representing the set of disabled paths for the application + * + * @return Returns an ID Set of disabled paths + */ + public Long getDisabledPathsId() + { + return disabledPathsId; + } + /** * Helper method to check that a path is correct for this application instance * diff --git a/source/java/org/alfresco/repo/audit/model/AuditModelRegistry.java b/source/java/org/alfresco/repo/audit/model/AuditModelRegistry.java index ad31db8a9d..f01888ed88 100644 --- a/source/java/org/alfresco/repo/audit/model/AuditModelRegistry.java +++ b/source/java/org/alfresco/repo/audit/model/AuditModelRegistry.java @@ -96,14 +96,6 @@ public class AuditModelRegistry * Used to lookup the audit application java hierarchy */ private final Map auditApplicationsByName; - /** - * Used to lookup a reference to the application - */ - private final Map auditApplicationIdsByApplicationsName; - /** - * Used to lookup application disabled paths - */ - private final Map> auditDisabledPathsByApplicationsName; /** * Default constructor @@ -119,8 +111,6 @@ public class AuditModelRegistry auditModelUrls = new HashSet(7); auditModels = new ArrayList(7); auditApplicationsByName = new HashMap(7); - auditApplicationIdsByApplicationsName = new HashMap(7); - auditDisabledPathsByApplicationsName = new HashMap>(7); } /** @@ -215,7 +205,6 @@ public class AuditModelRegistry { auditModels.clear(); auditApplicationsByName.clear(); - auditApplicationIdsByApplicationsName.clear(); } /** @@ -223,7 +212,8 @@ public class AuditModelRegistry * the audit models for later retrieval. Models are loaded from the locations given by the * {@link #registerModel(URL) register} methods. *

- * Note, the models are loaded in a new transaction. + * Note, the models are loaded in a new transaction, so this method can be called by any code + * at any time. */ public void loadAuditModels() { @@ -268,7 +258,7 @@ public class AuditModelRegistry clearCaches(); try { - transactionService.getRetryingTransactionHelper().doInTransaction(loadModelsCallback); + transactionService.getRetryingTransactionHelper().doInTransaction(loadModelsCallback, false, true); } finally { @@ -276,25 +266,6 @@ public class AuditModelRegistry } } - /** - * Get the ID of the persisted audit application for the given application name - * - * @param applicationName the name of the audited application - * @return the unique ID of the persisted application (null if not found) - */ - public Long getAuditApplicationId(String applicationName) - { - readLock.lock(); - try - { - return auditApplicationIdsByApplicationsName.get(applicationName); - } - finally - { - readLock.unlock(); - } - } - /** * Get the application model for the given application name * @@ -314,25 +285,6 @@ public class AuditModelRegistry } } - /** - * Get all disabled paths for the given application name - * - * @param applicationName the name of the audited application - * @return a set of paths for which logging is disabled - */ - public Set getAuditDisabledPaths(String applicationName) - { - readLock.lock(); - try - { - return auditDisabledPathsByApplicationsName.get(applicationName); - } - finally - { - readLock.unlock(); - } - } - /** * Unmarshalls the Audit model from the URL. * @@ -552,10 +504,13 @@ public class AuditModelRegistry auditDAO.updateAuditApplicationModel(appInfo.getId(), auditModelId); } - AuditApplication wrapperApp = new AuditApplication(dataExtractorsByName, dataGeneratorsByName, application); + AuditApplication wrapperApp = new AuditApplication( + dataExtractorsByName, + dataGeneratorsByName, + application, + appInfo.getId(), + appInfo.getDisabledPathsId()); auditApplicationsByName.put(name, wrapperApp); - auditApplicationIdsByApplicationsName.put(name, appInfo.getId()); - auditDisabledPathsByApplicationsName.put(name, appInfo.getDisabledPaths()); } // Store the model itself auditModels.add(audit); diff --git a/source/java/org/alfresco/repo/domain/audit/AbstractAuditDAOImpl.java b/source/java/org/alfresco/repo/domain/audit/AbstractAuditDAOImpl.java index 03b1074da0..9f6e439bce 100644 --- a/source/java/org/alfresco/repo/domain/audit/AbstractAuditDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/audit/AbstractAuditDAOImpl.java @@ -209,7 +209,6 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO * alf_audit_application */ - @SuppressWarnings("unchecked") public AuditApplicationInfo getAuditApplication(String application) { AuditApplicationEntity entity = getAuditApplicationByName(application); @@ -223,9 +222,7 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO appInfo.setId(entity.getId()); appInfo.setname(application); appInfo.setModelId(entity.getAuditModelId()); - // Resolve the disabled paths - Set disabledPaths = (Set) propertyValueDAO.getPropertyById(entity.getDisabledPathsId()); - appInfo.setDisabledPaths(disabledPaths); + appInfo.setDisabledPathsId(entity.getDisabledPathsId()); // Done if (logger.isDebugEnabled()) { @@ -252,7 +249,7 @@ public abstract class AbstractAuditDAOImpl implements AuditDAO appInfo.setId(entity.getId()); appInfo.setname(application); appInfo.setModelId(modelId); - appInfo.setDisabledPaths(disabledPaths); + appInfo.setDisabledPathsId(disabledPathsId); // Done if (logger.isDebugEnabled()) { diff --git a/source/java/org/alfresco/repo/domain/audit/AuditDAO.java b/source/java/org/alfresco/repo/domain/audit/AuditDAO.java index bfb910ed26..189fcbeb4e 100644 --- a/source/java/org/alfresco/repo/domain/audit/AuditDAO.java +++ b/source/java/org/alfresco/repo/domain/audit/AuditDAO.java @@ -78,7 +78,7 @@ public interface AuditDAO private Long id; private String name; private Long modelId; - private Set disabledPaths; + private Long disabledPathsId; @Override public String toString() @@ -88,7 +88,7 @@ public interface AuditDAO .append("[ id=").append(id) .append(", name=").append(name) .append(", modelId=").append(modelId) - .append(", disabledPaths=").append(disabledPaths) + .append(", disabledPathsId=").append(disabledPathsId) .append("]"); return sb.toString(); } @@ -117,13 +117,13 @@ public interface AuditDAO { this.modelId = modelId; } - public Set getDisabledPaths() + public Long getDisabledPathsId() { - return disabledPaths; + return disabledPathsId; } - public void setDisabledPaths(Set disabledPaths) + public void setDisabledPathsId(Long disabledPathsId) { - this.disabledPaths = disabledPaths; + this.disabledPathsId = disabledPathsId; } }