mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-19 05:30:43 +00:00
CE-567 Add concept of security lock Scope - e.g., READ-WRITE (blocking all access to a record), or just WRITE - which means anyone can read, but you must have the key to WRITE.
This commit is contained in:
@ -53,6 +53,7 @@ import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@ -365,6 +366,36 @@ class DeleteActionTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testSecurityLockWriteScope() throws QException
|
||||
{
|
||||
TestUtils.updatePersonMemoryTableInContextWithWritableByWriteLockAndInsert3TestRecords();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// try to delete 1, 2, and 3. 2 should be blocked, because it has a writable-By that isn't in our session //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QContext.getQSession().setSecurityKeyValues(MapBuilder.of("writableBy", ListBuilder.of("jdoe")));
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
deleteInput.setPrimaryKeys(List.of(1, 2, 3));
|
||||
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
|
||||
|
||||
assertEquals(1, deleteOutput.getRecordsWithErrors().size());
|
||||
assertThat(deleteOutput.getRecordsWithErrors().get(0).getErrors().get(0).getMessage())
|
||||
.contains("You do not have permission")
|
||||
.contains("kmarsh")
|
||||
.contains("Only Writable By");
|
||||
|
||||
assertEquals(1, new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_PERSON_MEMORY)).getCount());
|
||||
assertEquals(1, new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||
.withFilter(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.EQUALS, 2)))).getCount());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -35,9 +35,12 @@ 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.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.QErrorMessage;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -692,4 +695,60 @@ class InsertActionTest extends BaseTest
|
||||
assertEquals(3, TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER).size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testSecurityLockWriteScope() throws QException
|
||||
{
|
||||
TestUtils.updatePersonMemoryTableInContextWithWritableByWriteLockAndInsert3TestRecords();
|
||||
|
||||
QContext.getQSession().setSecurityKeyValues(MapBuilder.of("writableBy", ListBuilder.of("hsimpson")));
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// with only hsimpson in our key, make sure we can't insert a row w/ a different value //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
InsertOutput insertOutput = new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecords(List.of(
|
||||
new QRecord().withValue("id", 100).withValue("firstName", "Jean-Luc").withValue("onlyWritableBy", "jkirk")
|
||||
)));
|
||||
List<QErrorMessage> errors = insertOutput.getRecords().get(0).getErrors();
|
||||
assertEquals(1, errors.size());
|
||||
assertThat(errors.get(0).getMessage())
|
||||
.contains("You do not have permission")
|
||||
.contains("jkirk")
|
||||
.contains("Only Writable By");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// make sure we can insert w/ a null in onlyWritableBy (because key (from test utils) was set to allow null) //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
InsertOutput insertOutput = new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecords(List.of(
|
||||
new QRecord().withValue("id", 101).withValue("firstName", "Benajamin").withValue("onlyWritableBy", null)
|
||||
)));
|
||||
List<QErrorMessage> errors = insertOutput.getRecords().get(0).getErrors();
|
||||
assertEquals(0, errors.size());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// change the null behavior to deny, and try above again, expecting an error //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getRecordSecurityLocks().get(0).setNullValueBehavior(RecordSecurityLock.NullValueBehavior.DENY);
|
||||
{
|
||||
InsertOutput insertOutput = new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecords(List.of(
|
||||
new QRecord().withValue("id", 102).withValue("firstName", "Katherine").withValue("onlyWritableBy", null)
|
||||
)));
|
||||
List<QErrorMessage> errors = insertOutput.getRecords().get(0).getErrors();
|
||||
assertEquals(1, errors.size());
|
||||
assertThat(errors.get(0).getMessage())
|
||||
.contains("You do not have permission")
|
||||
.contains("without a value")
|
||||
.contains("Only Writable By");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,12 +29,18 @@ import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
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.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.QErrorMessage;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
@ -492,7 +498,7 @@ class UpdateActionTest extends BaseTest
|
||||
updateInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM);
|
||||
updateInput.setRecords(List.of(new QRecord().withValue("id", 20).withValue("sku", "BASIC3")));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
assertEquals("No record was found to update for Id = 20", updateOutput.getRecords().get(0).getErrors().get(0).getMessage());
|
||||
assertTrue(updateOutput.getRecords().get(0).getErrors().stream().anyMatch(em -> em.getMessage().equals("No record was found to update for Id = 20")));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -504,7 +510,7 @@ class UpdateActionTest extends BaseTest
|
||||
updateInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM);
|
||||
updateInput.setRecords(List.of(new QRecord().withValue("id", 10).withValue("orderId", 2).withValue("sku", "BASIC3")));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
assertEquals("You do not have permission to update this record - the referenced Order was not found.", updateOutput.getRecords().get(0).getErrors().get(0).getMessage());
|
||||
assertTrue(updateOutput.getRecords().get(0).getErrors().stream().anyMatch(em -> em.getMessage().equals("You do not have permission to update this record - the referenced Order was not found.")));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
@ -528,7 +534,7 @@ class UpdateActionTest extends BaseTest
|
||||
updateInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
|
||||
updateInput.setRecords(List.of(new QRecord().withValue("id", 200).withValue("key", "updatedKey")));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
assertEquals("No record was found to update for Id = 200", updateOutput.getRecords().get(0).getErrors().get(0).getMessage());
|
||||
assertTrue(updateOutput.getRecords().get(0).getErrors().stream().anyMatch(em -> em.getMessage().equals("No record was found to update for Id = 200")));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -706,4 +712,79 @@ class UpdateActionTest extends BaseTest
|
||||
assertEquals(1, TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER).stream().filter(r -> r.getValue("storeId") == null).count());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testSecurityLockWriteScope() throws QException
|
||||
{
|
||||
TestUtils.updatePersonMemoryTableInContextWithWritableByWriteLockAndInsert3TestRecords();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// try to update them all 1, 2, and 3. 2 should be blocked, because it has a writable-By that isn't in our session //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
QContext.getQSession().setSecurityKeyValues(MapBuilder.of("writableBy", ListBuilder.of("jdoe")));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecords(List.of(
|
||||
new QRecord().withValue("id", 1).withValue("lastName", "Kelkhoff"),
|
||||
new QRecord().withValue("id", 2).withValue("lastName", "Chamberlain"),
|
||||
new QRecord().withValue("id", 3).withValue("lastName", "Maes")
|
||||
)));
|
||||
|
||||
List<QRecord> errorRecords = updateOutput.getRecords().stream().filter(r -> CollectionUtils.nullSafeHasContents(r.getErrors())).toList();
|
||||
assertEquals(1, errorRecords.size());
|
||||
assertEquals(2, errorRecords.get(0).getValueInteger("id"));
|
||||
assertThat(errorRecords.get(0).getErrors().get(0).getMessage())
|
||||
.contains("You do not have permission")
|
||||
.contains("kmarsh")
|
||||
.contains("Only Writable By");
|
||||
|
||||
assertEquals(2, new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||
.withFilter(new QQueryFilter(new QFilterCriteria("lastName", QCriteriaOperator.IS_NOT_BLANK)))).getCount());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// now try to change one of the records to have a different value in the lock-field. Should fail (as it's not in our session) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecords(List.of(
|
||||
new QRecord().withValue("id", 1).withValue("onlyWritableBy", "ecartman"))));
|
||||
|
||||
List<QRecord> errorRecords = updateOutput.getRecords().stream().filter(r -> CollectionUtils.nullSafeHasContents(r.getErrors())).toList();
|
||||
assertEquals(1, errorRecords.size());
|
||||
assertThat(errorRecords.get(0).getErrors().get(0).getMessage())
|
||||
.contains("You do not have permission")
|
||||
.contains("ecartman")
|
||||
.contains("Only Writable By");
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// add that to our session and confirm we can do that update //
|
||||
///////////////////////////////////////////////////////////////
|
||||
{
|
||||
QContext.getQSession().setSecurityKeyValues(MapBuilder.of("writableBy", ListBuilder.of("jdoe", "ecartman")));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecords(List.of(
|
||||
new QRecord().withValue("id", 1).withValue("onlyWritableBy", "ecartman"))));
|
||||
List<QRecord> errorRecords = updateOutput.getRecords().stream().filter(r -> CollectionUtils.nullSafeHasContents(r.getErrors())).toList();
|
||||
assertEquals(0, errorRecords.size());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// change the null behavior to deny, then try to udpate a record and remove its onlyWritableBy //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).getRecordSecurityLocks().get(0).setNullValueBehavior(RecordSecurityLock.NullValueBehavior.DENY);
|
||||
{
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(new UpdateInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecords(List.of(
|
||||
new QRecord().withValue("id", 1).withValue("onlyWritableBy", null))));
|
||||
List<QErrorMessage> errors = updateOutput.getRecords().get(0).getErrors();
|
||||
assertEquals(1, errors.size());
|
||||
assertThat(errors.get(0).getMessage())
|
||||
.contains("You do not have permission")
|
||||
.contains("without a value")
|
||||
.contains("Only Writable By");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,15 +33,18 @@ import com.kingsrook.qqq.backend.core.actions.dashboard.PersonsByCreateDateBarCh
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.AddAge;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.person.addtopeoplesage.GetAgeStatistics;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
@ -110,6 +113,9 @@ import com.kingsrook.qqq.backend.core.processes.implementations.etl.basic.BasicE
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamed.StreamedETLProcess;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.reports.RunReportForRecordProcess;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -1393,4 +1399,39 @@ public class TestUtils
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void updatePersonMemoryTableInContextWithWritableByWriteLockAndInsert3TestRecords() throws QException
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
qInstance.addSecurityKeyType(new QSecurityKeyType()
|
||||
.withName("writableBy"));
|
||||
|
||||
QTableMetaData table = qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
table.withField(new QFieldMetaData("onlyWritableBy", QFieldType.STRING).withLabel("Only Writable By"));
|
||||
table.withRecordSecurityLock(new RecordSecurityLock()
|
||||
.withSecurityKeyType("writableBy")
|
||||
.withFieldName("onlyWritableBy")
|
||||
.withNullValueBehavior(RecordSecurityLock.NullValueBehavior.ALLOW)
|
||||
.withLockScope(RecordSecurityLock.LockScope.WRITE));
|
||||
|
||||
QContext.getQSession().setSecurityKeyValues(MapBuilder.of("writableBy", ListBuilder.of("jdoe", "kmarsh")));
|
||||
|
||||
new InsertAction().execute(new InsertInput(TestUtils.TABLE_NAME_PERSON_MEMORY).withRecords(List.of(
|
||||
new QRecord().withValue("id", 1).withValue("firstName", "Darin"),
|
||||
new QRecord().withValue("id", 2).withValue("firstName", "Tim").withValue("onlyWritableBy", "kmarsh"),
|
||||
new QRecord().withValue("id", 3).withValue("firstName", "James").withValue("onlyWritableBy", "jdoe")
|
||||
)));
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// make sure we can query for all 3 records //
|
||||
//////////////////////////////////////////////
|
||||
QContext.getQSession().setSecurityKeyValues(MapBuilder.of("writableBy", ListBuilder.of("jdoe")));
|
||||
assertEquals(3, new CountAction().execute(new CountInput(TestUtils.TABLE_NAME_PERSON_MEMORY)).getCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user