mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Update getRecordSecurityKeyValues and validateSecurityKeys to be aware of multiLocks
This commit is contained in:
@ -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.InsertInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
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.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.RecordSecurityLock;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLockFilters;
|
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLockFilters;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
@ -173,6 +174,35 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
|||||||
Map<String, Serializable> securityKeyValues = new HashMap<>();
|
Map<String, Serializable> securityKeyValues = new HashMap<>();
|
||||||
for(RecordSecurityLock recordSecurityLock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(table.getRecordSecurityLocks())))
|
for(RecordSecurityLock recordSecurityLock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(table.getRecordSecurityLocks())))
|
||||||
{
|
{
|
||||||
|
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<QRecord> oldRecord, RecordSecurityLock recordSecurityLock, Map<String, Serializable> 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());
|
Serializable keyValue = record == null ? null : record.getValue(recordSecurityLock.getFieldName());
|
||||||
|
|
||||||
if(keyValue == null && oldRecord.isPresent())
|
if(keyValue == null && oldRecord.isPresent())
|
||||||
@ -188,8 +218,6 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
|||||||
|
|
||||||
securityKeyValues.put(recordSecurityLock.getSecurityKeyType(), keyValue);
|
securityKeyValues.put(recordSecurityLock.getSecurityKeyType(), keyValue);
|
||||||
}
|
}
|
||||||
return securityKeyValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -218,21 +246,16 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
|||||||
throw (new QException("Requested audit for an unrecognized table name: " + auditSingleInput.getAuditTableName()));
|
throw (new QException("Requested audit for an unrecognized table name: " + auditSingleInput.getAuditTableName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
// validate security keys on the table are given //
|
|
||||||
///////////////////////////////////////////////////
|
|
||||||
for(RecordSecurityLock recordSecurityLock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(table.getRecordSecurityLocks())))
|
|
||||||
{
|
|
||||||
if(auditSingleInput.getSecurityKeyValues() == null || !auditSingleInput.getSecurityKeyValues().containsKey(recordSecurityLock.getSecurityKeyType()))
|
|
||||||
{
|
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
|
// validate security keys on the table are given //
|
||||||
// originally, this case threw... //
|
// originally, this case threw... //
|
||||||
// but i think it's better to record the audit, just //
|
// but i think it's better to record the audit, just //
|
||||||
// missing its security key value, then to fail... //
|
// missing its security key value, then to fail... //
|
||||||
|
// but, maybe should be configurable, etc... //
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
// throw (new QException("Missing securityKeyValue [" + recordSecurityLock.getSecurityKeyType() + "] in audit request for table " + auditSingleInput.getAuditTableName()));
|
if(!validateSecurityKeys(auditSingleInput, table))
|
||||||
LOG.info("Missing securityKeyValue in audit request", logPair("table", auditSingleInput.getAuditTableName()), logPair("securityKey", recordSecurityLock.getSecurityKeyType()));
|
{
|
||||||
}
|
LOG.debug("Missing securityKeyValue in audit request", logPair("table", auditSingleInput.getAuditTableName()), logPair("auditMessage", auditSingleInput.getMessage()), logPair("recordId", auditSingleInput.getRecordId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////
|
////////////////////////////////////////////////
|
||||||
@ -310,6 +333,70 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
static boolean validateSecurityKeys(AuditSingleInput auditSingleInput, QTableMetaData table)
|
||||||
|
{
|
||||||
|
boolean allAreValid = true;
|
||||||
|
for(RecordSecurityLock recordSecurityLock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(table.getRecordSecurityLocks())))
|
||||||
|
{
|
||||||
|
boolean lockIsValid = validateSecurityKeysForLock(auditSingleInput, recordSecurityLock);
|
||||||
|
if(!lockIsValid)
|
||||||
|
{
|
||||||
|
allAreValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (allAreValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
**
|
||||||
|
***************************************************************************/
|
||||||
|
private static boolean validateSecurityKeysForLock(AuditSingleInput auditSingleInput, RecordSecurityLock recordSecurityLock)
|
||||||
|
{
|
||||||
|
if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock)
|
||||||
|
{
|
||||||
|
boolean allSubLocksAreValid = true;
|
||||||
|
boolean anySubLocksAreValid = false;
|
||||||
|
for(RecordSecurityLock lock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(multiRecordSecurityLock.getLocks())))
|
||||||
|
{
|
||||||
|
boolean subLockIsValid = validateSecurityKeysForLock(auditSingleInput, lock);
|
||||||
|
if(subLockIsValid)
|
||||||
|
{
|
||||||
|
anySubLocksAreValid = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allSubLocksAreValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(multiRecordSecurityLock.getOperator().equals(MultiRecordSecurityLock.BooleanOperator.OR))
|
||||||
|
{
|
||||||
|
return (anySubLocksAreValid);
|
||||||
|
}
|
||||||
|
else if(multiRecordSecurityLock.getOperator().equals(MultiRecordSecurityLock.BooleanOperator.AND))
|
||||||
|
{
|
||||||
|
return (allSubLocksAreValid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(auditSingleInput.getSecurityKeyValues() == null || !auditSingleInput.getSecurityKeyValues().containsKey(recordSecurityLock.getSecurityKeyType()))
|
||||||
|
{
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.actions.audits;
|
|||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -37,6 +38,9 @@ import com.kingsrook.qqq.backend.core.model.actions.audits.AuditSingleInput;
|
|||||||
import com.kingsrook.qqq.backend.core.model.audits.AuditsMetaDataProvider;
|
import com.kingsrook.qqq.backend.core.model.audits.AuditsMetaDataProvider;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
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.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||||
import com.kingsrook.qqq.backend.core.processes.utils.GeneralProcessUtils;
|
import com.kingsrook.qqq.backend.core.processes.utils.GeneralProcessUtils;
|
||||||
@ -96,13 +100,17 @@ class AuditActionTest extends BaseTest
|
|||||||
QContext.init(qInstance, new QSession().withUser(new QUser().withFullName(userName)));
|
QContext.init(qInstance, new QSession().withUser(new QUser().withFullName(userName)));
|
||||||
|
|
||||||
int recordId = 1701;
|
int recordId = 1701;
|
||||||
|
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(AuditAction.class);
|
||||||
AuditAction.execute(TestUtils.TABLE_NAME_ORDER, recordId, Map.of(), "Test Audit");
|
AuditAction.execute(TestUtils.TABLE_NAME_ORDER, recordId, Map.of(), "Test Audit");
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
// it should not throw, but it should also not insert the audit. //
|
// it should not throw, but it should also not insert the audit. //
|
||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
Optional<QRecord> auditRecord = GeneralProcessUtils.getRecordByField("audit", "recordId", recordId);
|
Optional<QRecord> 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 //
|
// 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());
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user