diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/audits/AuditAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/audits/AuditAction.java index 02387268..031ca851 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/audits/AuditAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/audits/AuditAction.java @@ -44,6 +44,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput; import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.security.MultiRecordSecurityLock; import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock; import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLockFilters; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; @@ -173,26 +174,53 @@ public class AuditAction extends AbstractQActionFunction securityKeyValues = new HashMap<>(); for(RecordSecurityLock recordSecurityLock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(table.getRecordSecurityLocks()))) { - Serializable keyValue = record == null ? null : record.getValue(recordSecurityLock.getFieldName()); - - if(keyValue == null && oldRecord.isPresent()) - { - LOG.debug("Table with a securityLock, but value not found in field", logPair("table", table.getName()), logPair("field", recordSecurityLock.getFieldName())); - keyValue = oldRecord.get().getValue(recordSecurityLock.getFieldName()); - } - - if(keyValue == null) - { - LOG.debug("Table with a securityLock, but value not found in field", logPair("table", table.getName()), logPair("field", recordSecurityLock.getFieldName()), logPair("oldRecordIsPresent", oldRecord.isPresent())); - } - - securityKeyValues.put(recordSecurityLock.getSecurityKeyType(), keyValue); + getRecordSecurityKeyValues(table, record, oldRecord, recordSecurityLock, securityKeyValues); } return securityKeyValues; } + /*************************************************************************** + ** recursive implementation of getRecordSecurityKeyValues, for dealing with + ** multi-locks + ***************************************************************************/ + private static void getRecordSecurityKeyValues(QTableMetaData table, QRecord record, Optional oldRecord, RecordSecurityLock recordSecurityLock, Map securityKeyValues) + { + ////////////////////////////////////////////////////// + // special case with recursive call for multi-locks // + ////////////////////////////////////////////////////// + if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock) + { + for(RecordSecurityLock subLock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(multiRecordSecurityLock.getLocks()))) + { + getRecordSecurityKeyValues(table, record, oldRecord, subLock, securityKeyValues); + } + + return; + } + + /////////////////////////////////////////// + // by default, deal with non-multi locks // + /////////////////////////////////////////// + Serializable keyValue = record == null ? null : record.getValue(recordSecurityLock.getFieldName()); + + if(keyValue == null && oldRecord.isPresent()) + { + LOG.debug("Table with a securityLock, but value not found in field", logPair("table", table.getName()), logPair("field", recordSecurityLock.getFieldName())); + keyValue = oldRecord.get().getValue(recordSecurityLock.getFieldName()); + } + + if(keyValue == null) + { + LOG.debug("Table with a securityLock, but value not found in field", logPair("table", table.getName()), logPair("field", recordSecurityLock.getFieldName()), logPair("oldRecordIsPresent", oldRecord.isPresent())); + } + + securityKeyValues.put(recordSecurityLock.getSecurityKeyType(), keyValue); + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -218,21 +246,16 @@ public class AuditAction extends AbstractQActionFunction auditRecord = GeneralProcessUtils.getRecordByField("audit", "recordId", recordId); - assertFalse(auditRecord.isPresent()); + // assertFalse(auditRecord.isPresent()); + assertTrue(auditRecord.isPresent()); + assertThat(collectingLogger.getCollectedMessages()).anyMatch(clm -> clm.getMessage().contains("Missing securityKeyValue")); + QLogger.deactivateCollectingLoggerForClass(AuditAction.class); //////////////////////////////////////////////////////////////////////////////////////////////// // try again with a null value in the key - that should be ok - as at least you were thinking // @@ -274,4 +282,118 @@ class AuditActionTest extends BaseTest assertEquals(MapBuilder.of(TestUtils.SECURITY_KEY_TYPE_STORE, null), auditSingleInput.getSecurityKeyValues()); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testGetRecordSecurityKeyValue() + { + QRecord record = new QRecord().withValue("red", 1).withValue("blue", 2).withValue("green", 3).withValue("white", 4); + + RecordSecurityLock redLock = new RecordSecurityLock().withSecurityKeyType("red").withFieldName("red"); + RecordSecurityLock blueLock = new RecordSecurityLock().withSecurityKeyType("blue").withFieldName("blue"); + RecordSecurityLock greenLock = new RecordSecurityLock().withSecurityKeyType("green").withFieldName("green"); + RecordSecurityLock whiteWriteLock = new RecordSecurityLock().withSecurityKeyType("green").withFieldName("white").withLockScope(RecordSecurityLock.LockScope.WRITE); + + QTableMetaData simpleLockTable = new QTableMetaData() + .withRecordSecurityLock(redLock); + assertEquals(Map.of("red", 1), AuditAction.getRecordSecurityKeyValues(simpleLockTable, record, Optional.empty())); + + QTableMetaData writeOnlyLockTable = new QTableMetaData() + .withRecordSecurityLock(whiteWriteLock); + assertEquals(Collections.emptyMap(), AuditAction.getRecordSecurityKeyValues(writeOnlyLockTable, record, Optional.empty())); + + QTableMetaData multiAndLockTable = new QTableMetaData() + .withRecordSecurityLock(new MultiRecordSecurityLock() + .withOperator(MultiRecordSecurityLock.BooleanOperator.AND) + .withLock(redLock) + .withLock(blueLock)); + assertEquals(Map.of("red", 1, "blue", 2), AuditAction.getRecordSecurityKeyValues(multiAndLockTable, record, Optional.empty())); + + QTableMetaData multiOrLockTable = new QTableMetaData() + .withRecordSecurityLock(new MultiRecordSecurityLock() + .withOperator(MultiRecordSecurityLock.BooleanOperator.OR) + .withLock(redLock) + .withLock(blueLock)); + assertEquals(Map.of("red", 1, "blue", 2), AuditAction.getRecordSecurityKeyValues(multiOrLockTable, record, Optional.empty())); + + QTableMetaData multiLevelLockTable = new QTableMetaData() + .withRecordSecurityLock(new MultiRecordSecurityLock() + .withOperator(MultiRecordSecurityLock.BooleanOperator.AND) + .withLock(redLock) + .withLock(whiteWriteLock) + .withLock(new MultiRecordSecurityLock() + .withOperator(MultiRecordSecurityLock.BooleanOperator.OR) + .withLock(blueLock) + .withLock(greenLock))); + assertEquals(Map.of("red", 1, "blue", 2, "green", 3), AuditAction.getRecordSecurityKeyValues(multiLevelLockTable, record, Optional.empty())); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testValidateSecurityKeys() + { + RecordSecurityLock redLock = new RecordSecurityLock().withSecurityKeyType("red").withFieldName("red"); + RecordSecurityLock blueLock = new RecordSecurityLock().withSecurityKeyType("blue").withFieldName("blue"); + RecordSecurityLock greenLock = new RecordSecurityLock().withSecurityKeyType("green").withFieldName("green"); + RecordSecurityLock whiteWriteLock = new RecordSecurityLock().withSecurityKeyType("green").withFieldName("white").withLockScope(RecordSecurityLock.LockScope.WRITE); + + AuditSingleInput inputWithNoKeys = new AuditSingleInput().withSecurityKeyValues(Collections.emptyMap()); + AuditSingleInput inputWithRedKey = new AuditSingleInput().withSecurityKeyValues(Map.of("red", 1)); + AuditSingleInput inputWithBlueKey = new AuditSingleInput().withSecurityKeyValues(Map.of("blue", 2)); + AuditSingleInput inputWithRedAndBlueKeys = new AuditSingleInput().withSecurityKeyValues(Map.of("red", 1, "blue", 2)); + + QTableMetaData noLockTable = new QTableMetaData(); + assertTrue(AuditAction.validateSecurityKeys(inputWithNoKeys, noLockTable)); + assertTrue(AuditAction.validateSecurityKeys(inputWithRedKey, noLockTable)); + + QTableMetaData simpleLockTable = new QTableMetaData() + .withRecordSecurityLock(redLock); + assertFalse(AuditAction.validateSecurityKeys(inputWithNoKeys, simpleLockTable)); + assertTrue(AuditAction.validateSecurityKeys(inputWithRedKey, simpleLockTable)); + assertFalse(AuditAction.validateSecurityKeys(inputWithBlueKey, simpleLockTable)); + + QTableMetaData writeOnlyLockTable = new QTableMetaData() + .withRecordSecurityLock(whiteWriteLock); + assertTrue(AuditAction.validateSecurityKeys(inputWithNoKeys, writeOnlyLockTable)); + assertTrue(AuditAction.validateSecurityKeys(inputWithRedKey, writeOnlyLockTable)); + + QTableMetaData multiAndLockTable = new QTableMetaData() + .withRecordSecurityLock(new MultiRecordSecurityLock() + .withOperator(MultiRecordSecurityLock.BooleanOperator.AND) + .withLock(redLock) + .withLock(blueLock)); + assertFalse(AuditAction.validateSecurityKeys(inputWithNoKeys, multiAndLockTable)); + assertFalse(AuditAction.validateSecurityKeys(inputWithRedKey, multiAndLockTable)); + assertTrue(AuditAction.validateSecurityKeys(inputWithRedAndBlueKeys, multiAndLockTable)); + + QTableMetaData multiOrLockTable = new QTableMetaData() + .withRecordSecurityLock(new MultiRecordSecurityLock() + .withOperator(MultiRecordSecurityLock.BooleanOperator.OR) + .withLock(redLock) + .withLock(blueLock)); + assertFalse(AuditAction.validateSecurityKeys(inputWithNoKeys, multiOrLockTable)); + assertTrue(AuditAction.validateSecurityKeys(inputWithRedKey, multiOrLockTable)); + assertTrue(AuditAction.validateSecurityKeys(inputWithRedAndBlueKeys, multiOrLockTable)); + + QTableMetaData multiLevelLockTable = new QTableMetaData() + .withRecordSecurityLock(new MultiRecordSecurityLock() + .withOperator(MultiRecordSecurityLock.BooleanOperator.AND) + .withLock(redLock) + .withLock(whiteWriteLock) + .withLock(new MultiRecordSecurityLock() + .withOperator(MultiRecordSecurityLock.BooleanOperator.OR) + .withLock(blueLock) + .withLock(greenLock))); + assertFalse(AuditAction.validateSecurityKeys(inputWithNoKeys, multiLevelLockTable)); + assertFalse(AuditAction.validateSecurityKeys(inputWithRedKey, multiLevelLockTable)); + assertTrue(AuditAction.validateSecurityKeys(inputWithRedAndBlueKeys, multiLevelLockTable)); + } + }