mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
CE-882 Add filterForReadLockTree (required cloning in RecordSecurityLock)
This commit is contained in:
@ -22,6 +22,7 @@
|
|||||||
package com.kingsrook.qqq.backend.core.model.metadata.security;
|
package com.kingsrook.qqq.backend.core.model.metadata.security;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -41,7 +42,7 @@ import java.util.Map;
|
|||||||
** - READ_AND_WRITE means that users cannot read or write records without a valid key.
|
** - READ_AND_WRITE means that users cannot read or write records without a valid key.
|
||||||
** - WRITE means that users cannot write records without a valid key (but they can read them).
|
** - WRITE means that users cannot write records without a valid key (but they can read them).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class RecordSecurityLock
|
public class RecordSecurityLock implements Cloneable
|
||||||
{
|
{
|
||||||
private String securityKeyType;
|
private String securityKeyType;
|
||||||
private String fieldName;
|
private String fieldName;
|
||||||
@ -50,6 +51,26 @@ public class RecordSecurityLock
|
|||||||
|
|
||||||
private LockScope lockScope = LockScope.READ_AND_WRITE;
|
private LockScope lockScope = LockScope.READ_AND_WRITE;
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
protected RecordSecurityLock clone() throws CloneNotSupportedException
|
||||||
|
{
|
||||||
|
RecordSecurityLock clone = (RecordSecurityLock) super.clone();
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// deep-clone the list //
|
||||||
|
/////////////////////////
|
||||||
|
if(joinNameChain != null)
|
||||||
|
{
|
||||||
|
clone.joinNameChain = new ArrayList<>();
|
||||||
|
clone.joinNameChain.addAll(joinNameChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (clone);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -265,4 +286,22 @@ public class RecordSecurityLock
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "RecordSecurityLock{" +
|
||||||
|
"securityKeyType='" + securityKeyType + '\'' +
|
||||||
|
", fieldName='" + fieldName + '\'' +
|
||||||
|
", joinNameChain=" + joinNameChain +
|
||||||
|
", nullValueBehavior=" + nullValueBehavior +
|
||||||
|
", lockScope=" + lockScope +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,41 @@ public class RecordSecurityLockFilters
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** filter a list of locks so that we only see the ones that apply to reads.
|
||||||
|
*******************************************************************************/
|
||||||
|
public static MultiRecordSecurityLock filterForReadLockTree(List<RecordSecurityLock> recordSecurityLocks)
|
||||||
|
{
|
||||||
|
if(recordSecurityLocks == null)
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiRecordSecurityLock result = new MultiRecordSecurityLock();
|
||||||
|
result.setOperator(MultiRecordSecurityLock.BooleanOperator.AND);
|
||||||
|
|
||||||
|
for(RecordSecurityLock recordSecurityLock : recordSecurityLocks)
|
||||||
|
{
|
||||||
|
if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock)
|
||||||
|
{
|
||||||
|
MultiRecordSecurityLock filteredSubLock = filterForReadLockTree(multiRecordSecurityLock.getLocks());
|
||||||
|
filteredSubLock.setOperator(multiRecordSecurityLock.getOperator());
|
||||||
|
result.withLock(filteredSubLock);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(RecordSecurityLock.LockScope.READ_AND_WRITE.equals(recordSecurityLock.getLockScope()))
|
||||||
|
{
|
||||||
|
result.withLock(recordSecurityLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** filter a list of locks so that we only see the ones that apply to writes.
|
** filter a list of locks so that we only see the ones that apply to writes.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||||
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||||
|
* contact@kingsrook.com
|
||||||
|
* https://github.com/Kingsrook/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.kingsrook.qqq.backend.core.model.metadata.security;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static com.kingsrook.qqq.backend.core.model.metadata.security.MultiRecordSecurityLock.BooleanOperator.AND;
|
||||||
|
import static com.kingsrook.qqq.backend.core.model.metadata.security.MultiRecordSecurityLock.BooleanOperator.OR;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for RecordSecurityLockFilters
|
||||||
|
*******************************************************************************/
|
||||||
|
class RecordSecurityLockFiltersTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void test()
|
||||||
|
{
|
||||||
|
MultiRecordSecurityLock nullBecauseNull = RecordSecurityLockFilters.filterForReadLockTree(null);
|
||||||
|
assertNull(nullBecauseNull);
|
||||||
|
|
||||||
|
MultiRecordSecurityLock emptyBecauseEmptyList = RecordSecurityLockFilters.filterForReadLockTree(List.of());
|
||||||
|
assertEquals(0, emptyBecauseEmptyList.getLocks().size());
|
||||||
|
|
||||||
|
MultiRecordSecurityLock emptyBecauseAllWrite = RecordSecurityLockFilters.filterForReadLockTree(List.of(
|
||||||
|
new RecordSecurityLock().withFieldName("A").withLockScope(RecordSecurityLock.LockScope.WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("B").withLockScope(RecordSecurityLock.LockScope.WRITE)
|
||||||
|
));
|
||||||
|
assertEquals(0, emptyBecauseAllWrite.getLocks().size());
|
||||||
|
|
||||||
|
MultiRecordSecurityLock onlyA = RecordSecurityLockFilters.filterForReadLockTree(List.of(
|
||||||
|
new RecordSecurityLock().withFieldName("A").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("B").withLockScope(RecordSecurityLock.LockScope.WRITE)
|
||||||
|
));
|
||||||
|
assertMultiRecordSecurityLock(onlyA, AND, "A");
|
||||||
|
|
||||||
|
MultiRecordSecurityLock twoOutOfThreeTopLevel = RecordSecurityLockFilters.filterForReadLockTree(List.of(
|
||||||
|
new RecordSecurityLock().withFieldName("A").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("B").withLockScope(RecordSecurityLock.LockScope.WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("C").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE)
|
||||||
|
));
|
||||||
|
assertMultiRecordSecurityLock(twoOutOfThreeTopLevel, AND, "A", "C");
|
||||||
|
|
||||||
|
MultiRecordSecurityLock treeOfAllReads = RecordSecurityLockFilters.filterForReadLockTree(List.of(
|
||||||
|
new MultiRecordSecurityLock().withOperator(OR).withLocks(List.of(
|
||||||
|
new RecordSecurityLock().withFieldName("A").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("B").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE)
|
||||||
|
)),
|
||||||
|
new MultiRecordSecurityLock().withOperator(OR).withLocks(List.of(
|
||||||
|
new RecordSecurityLock().withFieldName("C").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("D").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE)
|
||||||
|
))
|
||||||
|
));
|
||||||
|
assertEquals(2, treeOfAllReads.getLocks().size());
|
||||||
|
assertEquals(AND, treeOfAllReads.getOperator());
|
||||||
|
assertMultiRecordSecurityLock((MultiRecordSecurityLock) treeOfAllReads.getLocks().get(0), OR, "A", "B");
|
||||||
|
assertMultiRecordSecurityLock((MultiRecordSecurityLock) treeOfAllReads.getLocks().get(1), OR, "C", "D");
|
||||||
|
|
||||||
|
MultiRecordSecurityLock treeWithOneBranchReadsOneBranchWrites = RecordSecurityLockFilters.filterForReadLockTree(List.of(
|
||||||
|
new MultiRecordSecurityLock().withOperator(OR).withLocks(List.of(
|
||||||
|
new RecordSecurityLock().withFieldName("A").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("B").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE)
|
||||||
|
)),
|
||||||
|
new MultiRecordSecurityLock().withOperator(OR).withLocks(List.of(
|
||||||
|
new RecordSecurityLock().withFieldName("C").withLockScope(RecordSecurityLock.LockScope.WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("D").withLockScope(RecordSecurityLock.LockScope.WRITE)
|
||||||
|
))
|
||||||
|
));
|
||||||
|
assertEquals(2, treeWithOneBranchReadsOneBranchWrites.getLocks().size());
|
||||||
|
assertEquals(AND, treeWithOneBranchReadsOneBranchWrites.getOperator());
|
||||||
|
assertMultiRecordSecurityLock((MultiRecordSecurityLock) treeWithOneBranchReadsOneBranchWrites.getLocks().get(0), OR, "A", "B");
|
||||||
|
assertMultiRecordSecurityLock((MultiRecordSecurityLock) treeWithOneBranchReadsOneBranchWrites.getLocks().get(1), OR);
|
||||||
|
|
||||||
|
MultiRecordSecurityLock deepSparseTree = RecordSecurityLockFilters.filterForReadLockTree(List.of(
|
||||||
|
new MultiRecordSecurityLock().withOperator(OR).withLocks(List.of(
|
||||||
|
new MultiRecordSecurityLock().withOperator(AND).withLocks(List.of(
|
||||||
|
new RecordSecurityLock().withFieldName("A").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("B").withLockScope(RecordSecurityLock.LockScope.WRITE)
|
||||||
|
)),
|
||||||
|
new RecordSecurityLock().withFieldName("C").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("D").withLockScope(RecordSecurityLock.LockScope.WRITE)
|
||||||
|
)),
|
||||||
|
new MultiRecordSecurityLock().withOperator(OR).withLocks(List.of(
|
||||||
|
new MultiRecordSecurityLock().withOperator(AND).withLocks(List.of(
|
||||||
|
new RecordSecurityLock().withFieldName("E").withLockScope(RecordSecurityLock.LockScope.WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("F").withLockScope(RecordSecurityLock.LockScope.WRITE)
|
||||||
|
)),
|
||||||
|
new MultiRecordSecurityLock().withOperator(AND).withLocks(List.of(
|
||||||
|
new RecordSecurityLock().withFieldName("G").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("H").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE)
|
||||||
|
))
|
||||||
|
)),
|
||||||
|
new RecordSecurityLock().withFieldName("I").withLockScope(RecordSecurityLock.LockScope.READ_AND_WRITE),
|
||||||
|
new RecordSecurityLock().withFieldName("J").withLockScope(RecordSecurityLock.LockScope.WRITE)
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(3, deepSparseTree.getLocks().size());
|
||||||
|
assertEquals(AND, deepSparseTree.getOperator());
|
||||||
|
MultiRecordSecurityLock deepChild0 = (MultiRecordSecurityLock) deepSparseTree.getLocks().get(0);
|
||||||
|
assertEquals(2, deepChild0.getLocks().size());
|
||||||
|
assertEquals(OR, deepChild0.getOperator());
|
||||||
|
MultiRecordSecurityLock deepGrandChild0 = (MultiRecordSecurityLock) deepChild0.getLocks().get(0);
|
||||||
|
assertMultiRecordSecurityLock(deepGrandChild0, AND, "A");
|
||||||
|
assertEquals("C", deepChild0.getLocks().get(1).getFieldName());
|
||||||
|
|
||||||
|
MultiRecordSecurityLock deepChild1 = (MultiRecordSecurityLock) deepSparseTree.getLocks().get(1);
|
||||||
|
assertEquals(2, deepChild1.getLocks().size());
|
||||||
|
assertEquals(OR, deepChild1.getOperator());
|
||||||
|
MultiRecordSecurityLock deepGrandChild1 = (MultiRecordSecurityLock) deepChild1.getLocks().get(0);
|
||||||
|
assertMultiRecordSecurityLock(deepGrandChild1, AND);
|
||||||
|
MultiRecordSecurityLock deepGrandChild2 = (MultiRecordSecurityLock) deepChild1.getLocks().get(1);
|
||||||
|
assertMultiRecordSecurityLock(deepGrandChild2, AND, "G", "H");
|
||||||
|
|
||||||
|
assertEquals("I", deepSparseTree.getLocks().get(2).getFieldName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private void assertMultiRecordSecurityLock(MultiRecordSecurityLock lock, MultiRecordSecurityLock.BooleanOperator operator, String... lockFieldNames)
|
||||||
|
{
|
||||||
|
assertEquals(lockFieldNames.length, lock.getLocks().size());
|
||||||
|
assertEquals(operator, lock.getOperator());
|
||||||
|
|
||||||
|
for(int i = 0; i < lockFieldNames.length; i++)
|
||||||
|
{
|
||||||
|
assertEquals(lockFieldNames[i], lock.getLocks().get(i).getFieldName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user