diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateAction.java index 8730ae14..d3bd6ac5 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/UpdateAction.java @@ -456,7 +456,7 @@ public class UpdateAction QFieldType fieldType = table.getField(lock.getFieldName()).getType(); Serializable lockValue = ValueUtils.getValueAsFieldType(fieldType, oldRecord.getValue(lock.getFieldName())); - List errors = ValidateRecordSecurityLockHelper.validateRecordSecurityValue(table, lock, lockValue, fieldType, ValidateRecordSecurityLockHelper.Action.UPDATE, Collections.emptyMap()); + List errors = ValidateRecordSecurityLockHelper.validateRecordSecurityValue(table, lock, lockValue, fieldType, ValidateRecordSecurityLockHelper.Action.UPDATE, Collections.emptyMap(), QContext.getQSession()); if(CollectionUtils.nullSafeHasContents(errors)) { errors.forEach(e -> record.addError(e)); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/ValidateRecordSecurityLockHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/ValidateRecordSecurityLockHelper.java index ac0280d7..8a41e71b 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/ValidateRecordSecurityLockHelper.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/ValidateRecordSecurityLockHelper.java @@ -50,6 +50,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType; 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; +import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.statusmessages.PermissionDeniedMessage; import com.kingsrook.qqq.backend.core.model.statusmessages.QErrorMessage; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; @@ -102,7 +103,7 @@ public class ValidateRecordSecurityLockHelper // actually check lock values // //////////////////////////////// Map errorRecords = new HashMap<>(); - evaluateRecordLocks(table, records, action, locksToCheck, errorRecords, new ArrayList<>(), madeUpPrimaryKeys, transaction); + evaluateRecordLocks(table, records, action, locksToCheck, errorRecords, new ArrayList<>(), madeUpPrimaryKeys, transaction, QContext.getQSession()); ///////////////////////////////// // propagate errors to records // @@ -124,6 +125,29 @@ public class ValidateRecordSecurityLockHelper + /*************************************************************************** + ** return boolean if given session can read given record + ***************************************************************************/ + public static boolean allowedToReadRecord(QTableMetaData table, QRecord record, QSession qSession, QBackendTransaction transaction) throws QException + { + MultiRecordSecurityLock locksToCheck = getRecordSecurityLocks(table, Action.SELECT); + if(locksToCheck == null || CollectionUtils.nullSafeIsEmpty(locksToCheck.getLocks())) + { + return (true); + } + + Map errorRecords = new HashMap<>(); + evaluateRecordLocks(table, List.of(record), Action.SELECT, locksToCheck, errorRecords, new ArrayList<>(), Collections.emptyMap(), transaction, qSession); + if(errorRecords.containsKey(record.getValue(table.getPrimaryKeyField()))) + { + return (false); + } + + return (true); + } + + + /******************************************************************************* ** For a list of `records` from a `table`, and a given `action`, evaluate a ** `recordSecurityLock` (which may be a multi-lock) - populating the input map @@ -142,7 +166,7 @@ public class ValidateRecordSecurityLockHelper ** BUT - WRITE locks - in their case, we read the record no matter what, and in ** here we need to verify we have a key that allows us to WRITE the record. *******************************************************************************/ - private static void evaluateRecordLocks(QTableMetaData table, List records, Action action, RecordSecurityLock recordSecurityLock, Map errorRecords, List treePosition, Map madeUpPrimaryKeys, QBackendTransaction transaction) throws QException + private static void evaluateRecordLocks(QTableMetaData table, List records, Action action, RecordSecurityLock recordSecurityLock, Map errorRecords, List treePosition, Map madeUpPrimaryKeys, QBackendTransaction transaction, QSession qSession) throws QException { if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock) { @@ -153,7 +177,7 @@ public class ValidateRecordSecurityLockHelper for(RecordSecurityLock childLock : CollectionUtils.nonNullList(multiRecordSecurityLock.getLocks())) { treePosition.add(i); - evaluateRecordLocks(table, records, action, childLock, errorRecords, treePosition, madeUpPrimaryKeys, transaction); + evaluateRecordLocks(table, records, action, childLock, errorRecords, treePosition, madeUpPrimaryKeys, transaction, qSession); treePosition.remove(treePosition.size() - 1); i++; } @@ -165,7 +189,7 @@ public class ValidateRecordSecurityLockHelper // if this lock has an all-access key, and the user has that key, then there can't be any errors here, so return early // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// QSecurityKeyType securityKeyType = QContext.getQInstance().getSecurityKeyType(recordSecurityLock.getSecurityKeyType()); - if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && QContext.getQSession().hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN)) + if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && qSession.hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN)) { return; } @@ -193,7 +217,7 @@ public class ValidateRecordSecurityLockHelper } Serializable recordSecurityValue = record.getValue(field.getName()); - List recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys); + List recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys, qSession); if(CollectionUtils.nullSafeHasContents(recordErrors)) { errorRecords.computeIfAbsent(record.getValue(primaryKeyField), (k) -> new RecordWithErrors(record)).addAll(recordErrors, treePosition); @@ -339,7 +363,7 @@ public class ValidateRecordSecurityLockHelper for(QRecord inputRecord : inputRecords) { - List recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys); + List recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys, qSession); if(CollectionUtils.nullSafeHasContents(recordErrors)) { errorRecords.computeIfAbsent(inputRecord.getValue(primaryKeyField), (k) -> new RecordWithErrors(inputRecord)).addAll(recordErrors, treePosition); @@ -446,7 +470,7 @@ public class ValidateRecordSecurityLockHelper /******************************************************************************* ** *******************************************************************************/ - public static List validateRecordSecurityValue(QTableMetaData table, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType, Action action, Map madeUpPrimaryKeys) + public static List validateRecordSecurityValue(QTableMetaData table, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType, Action action, Map madeUpPrimaryKeys, QSession qSession) { if(recordSecurityValue == null || (madeUpPrimaryKeys != null && madeUpPrimaryKeys.containsKey(recordSecurityValue))) { @@ -461,7 +485,7 @@ public class ValidateRecordSecurityLockHelper } else { - if(!QContext.getQSession().hasSecurityKeyValue(recordSecurityLock.getSecurityKeyType(), recordSecurityValue, fieldType)) + if(!qSession.hasSecurityKeyValue(recordSecurityLock.getSecurityKeyType(), recordSecurityValue, fieldType)) { if(CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain())) { diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/ValidateRecordSecurityLockHelperTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/ValidateRecordSecurityLockHelperTest.java index 6186ad4e..8cb6a1db 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/ValidateRecordSecurityLockHelperTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/ValidateRecordSecurityLockHelperTest.java @@ -23,14 +23,22 @@ package com.kingsrook.qqq.backend.core.actions.tables.helpers; import java.util.List; +import java.util.Map; import com.kingsrook.qqq.backend.core.BaseTest; import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper.RecordWithErrors; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; 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.tables.QTableMetaData; +import com.kingsrook.qqq.backend.core.model.session.QSession; import com.kingsrook.qqq.backend.core.model.statusmessages.BadInputStatusMessage; +import com.kingsrook.qqq.backend.core.utils.TestUtils; import org.junit.jupiter.api.Test; import static com.kingsrook.qqq.backend.core.model.metadata.security.MultiRecordSecurityLock.BooleanOperator.AND; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /******************************************************************************* @@ -106,4 +114,29 @@ class ValidateRecordSecurityLockHelperTest extends BaseTest } } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testAllowedToReadRecord() throws QException + { + QTableMetaData table = QContext.getQInstance().getTables().get(TestUtils.TABLE_NAME_ORDER); + + QSession sessionWithStore1 = new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE, 1); + QSession sessionWithStore2 = new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE, 2); + QSession sessionWithStore1and2 = new QSession().withSecurityKeyValues(Map.of(TestUtils.SECURITY_KEY_TYPE_STORE, List.of(1, 2))); + QSession sessionWithStoresAllAccess = new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true); + QSession sessionWithNoStores = new QSession(); + + QRecord recordStore1 = new QRecord().withValue("storeId", 1); + + assertTrue(ValidateRecordSecurityLockHelper.allowedToReadRecord(table, recordStore1, sessionWithStore1, null)); + assertFalse(ValidateRecordSecurityLockHelper.allowedToReadRecord(table, recordStore1, sessionWithStore2, null)); + assertTrue(ValidateRecordSecurityLockHelper.allowedToReadRecord(table, recordStore1, sessionWithStore1and2, null)); + assertTrue(ValidateRecordSecurityLockHelper.allowedToReadRecord(table, recordStore1, sessionWithStoresAllAccess, null)); + assertFalse(ValidateRecordSecurityLockHelper.allowedToReadRecord(table, recordStore1, sessionWithNoStores, null)); + } + } \ No newline at end of file