Add security, required fields, record-exists validation to UpdateAction. refactor InsertAction to help there

This commit is contained in:
2023-03-30 15:24:40 -05:00
parent adcddff440
commit 3df4513cd1
11 changed files with 850 additions and 315 deletions

View File

@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostInsertCust
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
@ -48,27 +49,16 @@ import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditInput;
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.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.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
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.tables.Association;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -100,7 +90,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
ValueBehaviorApplier.applyFieldBehaviors(insertInput.getInstance(), table, insertInput.getRecords());
setErrorsIfUniqueKeyErrors(insertInput, table);
validateRequiredFields(insertInput);
validateSecurityFields(insertInput);
ValidateRecordSecurityLockHelper.validateSecurityFields(insertInput.getTable(), insertInput.getRecords(), ValidateRecordSecurityLockHelper.Action.INSERT);
InsertOutput insertOutput = qModule.getInsertInterface().execute(insertInput);
List<String> errors = insertOutput.getRecords().stream().flatMap(r -> r.getErrors().stream()).toList();
@ -153,231 +143,6 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
/*******************************************************************************
**
*******************************************************************************/
private void validateSecurityFields(InsertInput insertInput) throws QException
{
QTableMetaData table = insertInput.getTable();
List<RecordSecurityLock> recordSecurityLocks = table.getRecordSecurityLocks();
List<RecordSecurityLock> locksToCheck = new ArrayList<>();
////////////////////////////////////////
// if there are no locks, just return //
////////////////////////////////////////
if(CollectionUtils.nullSafeIsEmpty(recordSecurityLocks))
{
return;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// decide if any locks need checked - where one may not need checked if it has an all-access key, and the user has all-access //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for(RecordSecurityLock recordSecurityLock : recordSecurityLocks)
{
QSecurityKeyType securityKeyType = QContext.getQInstance().getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && QContext.getQSession().hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
{
LOG.debug("Session has " + securityKeyType.getAllAccessKeyName() + " - not checking this lock.");
}
else
{
locksToCheck.add(recordSecurityLock);
}
}
/////////////////////////////////////////////////
// if there are no locks to check, just return //
/////////////////////////////////////////////////
if(locksToCheck.isEmpty())
{
return;
}
////////////////////////////////
// actually check lock values //
////////////////////////////////
for(RecordSecurityLock recordSecurityLock : locksToCheck)
{
if(CollectionUtils.nullSafeIsEmpty(recordSecurityLock.getJoinNameChain()))
{
for(QRecord record : insertInput.getRecords())
{
/////////////////////////////////////////////////////////////////////////
// handle the value being in the table we're inserting (e.g., no join) //
/////////////////////////////////////////////////////////////////////////
QFieldMetaData field = table.getField(recordSecurityLock.getFieldName());
Serializable recordSecurityValue = record.getValue(recordSecurityLock.getFieldName());
validateRecordSecurityValue(table, record, recordSecurityLock, recordSecurityValue, field.getType());
}
}
else
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// else look for the joined record - if it isn't found, assume a fail - else validate security value if found //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
QJoinMetaData leftMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(0));
QJoinMetaData rightMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(recordSecurityLock.getJoinNameChain().size() - 1));
QTableMetaData leftMostJoinTable = QContext.getQInstance().getTable(leftMostJoin.getLeftTable());
for(List<QRecord> inputRecordPage : CollectionUtils.getPages(insertInput.getRecords(), 500))
{
////////////////////////////////////////////////////////////////////////////////////////////////
// set up a query for joined records //
// query will be like (fkey1=? and fkey2=?) OR (fkey1=? and fkey2=?) OR (fkey1=? and fkey2=?) //
////////////////////////////////////////////////////////////////////////////////////////////////
QueryInput queryInput = new QueryInput();
queryInput.setTableName(leftMostJoin.getLeftTable());
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR);
queryInput.setFilter(filter);
for(String joinName : recordSecurityLock.getJoinNameChain())
{
///////////////////////////////////////
// we don't need the right-most join //
///////////////////////////////////////
if(!joinName.equals(rightMostJoin.getName()))
{
queryInput.withQueryJoin(new QueryJoin().withJoinMetaData(QContext.getQInstance().getJoin(joinName)).withSelect(true));
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// foreach input record (in this page), put it in a listing hash, with key = list of join-values //
// e.g., (17,47)=(QRecord1), (18,48)=(QRecord2,QRecord3) //
// also build up the query's sub-filters here (only adding them if they're unique). //
// e.g., 2 order-lines referencing the same orderId don't need to be added to the query twice //
///////////////////////////////////////////////////////////////////////////////////////////////////
ListingHash<List<Serializable>, QRecord> inputRecordMapByJoinFields = new ListingHash<>();
for(QRecord inputRecord : inputRecordPage)
{
List<Serializable> inputRecordJoinValues = new ArrayList<>();
QQueryFilter subFilter = new QQueryFilter();
for(JoinOn joinOn : rightMostJoin.getJoinOns())
{
Serializable inputRecordValue = inputRecord.getValue(joinOn.getRightField());
inputRecordJoinValues.add(inputRecordValue);
subFilter.addCriteria(inputRecordValue == null
? new QFilterCriteria(rightMostJoin.getLeftTable() + "." + joinOn.getLeftField(), QCriteriaOperator.IS_BLANK)
: new QFilterCriteria(rightMostJoin.getLeftTable() + "." + joinOn.getLeftField(), QCriteriaOperator.EQUALS, inputRecordValue));
}
if(!inputRecordMapByJoinFields.containsKey(inputRecordJoinValues))
{
////////////////////////////////////////////////////////////////////////////////
// only add this sub-filter if it's for a list of keys we haven't seen before //
////////////////////////////////////////////////////////////////////////////////
filter.addSubFilter(subFilter);
}
inputRecordMapByJoinFields.add(inputRecordJoinValues, inputRecord);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// execute the query for joined records - then put them in a map with keys corresponding to the join values //
// e.g., (17,47)=(JoinRecord), (18,48)=(JoinRecord) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
QueryOutput queryOutput = new QueryAction().execute(queryInput);
Map<List<Serializable>, QRecord> joinRecordMapByJoinFields = new HashMap<>();
for(QRecord joinRecord : queryOutput.getRecords())
{
List<Serializable> joinRecordValues = new ArrayList<>();
for(JoinOn joinOn : rightMostJoin.getJoinOns())
{
Serializable joinValue = joinRecord.getValue(rightMostJoin.getLeftTable() + "." + joinOn.getLeftField());
if(joinValue == null && joinRecord.getValues().keySet().stream().anyMatch(n -> !n.contains(".")))
{
joinValue = joinRecord.getValue(joinOn.getLeftField());
}
joinRecordValues.add(joinValue);
}
joinRecordMapByJoinFields.put(joinRecordValues, joinRecord);
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// now for each input record, look for its joinRecord - if it isn't found, then this insert //
// isn't allowed. if it is found, then validate its value matches this session's security keys //
//////////////////////////////////////////////////////////////////////////////////////////////////
for(Map.Entry<List<Serializable>, List<QRecord>> entry : inputRecordMapByJoinFields.entrySet())
{
List<Serializable> inputRecordJoinValues = entry.getKey();
List<QRecord> inputRecords = entry.getValue();
if(joinRecordMapByJoinFields.containsKey(inputRecordJoinValues))
{
QRecord joinRecord = joinRecordMapByJoinFields.get(inputRecordJoinValues);
String fieldName = recordSecurityLock.getFieldName().replaceFirst(".*\\.", "");
QFieldMetaData field = leftMostJoinTable.getField(fieldName);
Serializable recordSecurityValue = joinRecord.getValue(fieldName);
if(recordSecurityValue == null && joinRecord.getValues().keySet().stream().anyMatch(n -> n.contains(".")))
{
recordSecurityValue = joinRecord.getValue(recordSecurityLock.getFieldName());
}
for(QRecord inputRecord : inputRecords)
{
validateRecordSecurityValue(table, inputRecord, recordSecurityLock, recordSecurityValue, field.getType());
}
}
else
{
for(QRecord inputRecord : inputRecords)
{
if(RecordSecurityLock.NullValueBehavior.DENY.equals(recordSecurityLock.getNullValueBehavior()))
{
inputRecord.addError("You do not have permission to insert this record - the referenced " + leftMostJoinTable.getLabel() + " was not found.");
}
}
}
}
}
}
}
}
/*******************************************************************************
**
*******************************************************************************/
private static void validateRecordSecurityValue(QTableMetaData table, QRecord record, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType)
{
if(recordSecurityValue == null)
{
/////////////////////////////////////////////////////////////////
// handle null values - error if the NullValueBehavior is DENY //
/////////////////////////////////////////////////////////////////
if(RecordSecurityLock.NullValueBehavior.DENY.equals(recordSecurityLock.getNullValueBehavior()))
{
String lockLabel = CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()) ? recordSecurityLock.getSecurityKeyType() : table.getField(recordSecurityLock.getFieldName()).getLabel();
record.addError("You do not have permission to insert a record without a value in the field: " + lockLabel);
}
}
else
{
if(!QContext.getQSession().hasSecurityKeyValue(recordSecurityLock.getSecurityKeyType(), recordSecurityValue, fieldType))
{
if(CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()))
{
///////////////////////////////////////////////////////////////////////////////////////////////
// avoid telling the user a value from a foreign record that they didn't pass in themselves. //
///////////////////////////////////////////////////////////////////////////////////////////////
record.addError("You do not have permission to insert this record.");
}
else
{
QFieldMetaData field = table.getField(recordSecurityLock.getFieldName());
record.addError("You do not have permission to insert a record with a value of " + recordSecurityValue + " in the field: " + field.getLabel());
}
}
}
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -24,13 +24,16 @@ package com.kingsrook.qqq.backend.core.actions.tables;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
@ -57,6 +60,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -86,7 +90,10 @@ public class UpdateAction
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(updateInput.getBackend());
validatePrimaryKeysAreGiven(updateInput);
validateRecordsExistAndCanBeAccessed(updateInput, oldRecordList);
validateRequiredFields(updateInput);
ValidateRecordSecurityLockHelper.validateSecurityFields(updateInput.getTable(), updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE);
// todo pre-customization - just get to modify the request?
UpdateOutput updateOutput = qModule.getUpdateInterface().execute(updateInput);
@ -114,6 +121,86 @@ public class UpdateAction
/*******************************************************************************
**
*******************************************************************************/
private void validatePrimaryKeysAreGiven(UpdateInput updateInput)
{
QTableMetaData table = updateInput.getTable();
for(QRecord record : CollectionUtils.nonNullList(updateInput.getRecords()))
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// to update a record, we must have its primary key value - so - check - if it's missing, mark it as an error //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(record.getValue(table.getPrimaryKeyField()) == null)
{
record.addError("Missing value in primary key field");
}
}
}
/*******************************************************************************
** Note - the "can be accessed" part of this method name - it implies that
** records that you can't see because of security - that they won't be found
** by the query here, so it's the same to you as if they don't exist at all!
*******************************************************************************/
private void validateRecordsExistAndCanBeAccessed(UpdateInput updateInput, List<QRecord> oldRecordList) throws QException
{
QTableMetaData table = updateInput.getTable();
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
for(List<QRecord> page : CollectionUtils.getPages(updateInput.getRecords(), 1000))
{
List<Serializable> primaryKeysToLookup = new ArrayList<>();
for(QRecord record : page)
{
Serializable primaryKeyValue = record.getValue(table.getPrimaryKeyField());
if(primaryKeyValue != null)
{
primaryKeysToLookup.add(primaryKeyValue);
}
}
Map<Serializable, QRecord> lookedUpRecords = new HashMap<>();
if(CollectionUtils.nullSafeHasContents(oldRecordList))
{
for(QRecord record : oldRecordList)
{
lookedUpRecords.put(record.getValue(table.getPrimaryKeyField()), record);
}
}
else if(!primaryKeysToLookup.isEmpty())
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(table.getName());
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, primaryKeysToLookup)));
QueryOutput queryOutput = new QueryAction().execute(queryInput);
for(QRecord record : queryOutput.getRecords())
{
lookedUpRecords.put(record.getValue(table.getPrimaryKeyField()), record);
}
}
for(QRecord record : page)
{
Serializable value = ValueUtils.getValueAsFieldType(primaryKeyField.getType(), record.getValue(table.getPrimaryKeyField()));
if(value == null)
{
continue;
}
if(!lookedUpRecords.containsKey(value))
{
record.addError("No record was found to update for " + primaryKeyField.getLabel() + " = " + value);
}
}
}
}
/*******************************************************************************
**
*******************************************************************************/
@ -126,7 +213,7 @@ public class UpdateAction
if(!requiredFields.isEmpty())
{
for(QRecord record : updateInput.getRecords())
for(QRecord record : CollectionUtils.nonNullList(updateInput.getRecords()))
{
for(QFieldMetaData requiredField : requiredFields)
{
@ -279,7 +366,7 @@ public class UpdateAction
if(AuditLevel.FIELD.equals(auditLevel))
{
String primaryKeyField = updateInput.getTable().getPrimaryKeyField();
List<Serializable> pkeysBeingUpdated = updateInput.getRecords().stream().map(r -> r.getValue(primaryKeyField)).toList();
List<Serializable> pkeysBeingUpdated = CollectionUtils.nonNullList(updateInput.getRecords()).stream().map(r -> r.getValue(primaryKeyField)).toList();
QueryInput queryInput = new QueryInput();
queryInput.setTableName(updateInput.getTableName());

View File

@ -0,0 +1,313 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2023. 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.actions.tables.helpers;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
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.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
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.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
**
*******************************************************************************/
public class ValidateRecordSecurityLockHelper
{
private static final QLogger LOG = QLogger.getLogger(ValidateRecordSecurityLockHelper.class);
/*******************************************************************************
**
*******************************************************************************/
public enum Action
{
INSERT,
UPDATE,
SELECT
}
/*******************************************************************************
**
*******************************************************************************/
public static void validateSecurityFields(QTableMetaData table, List<QRecord> records, Action action) throws QException
{
List<RecordSecurityLock> locksToCheck = getRecordSecurityLocks(table);
if(CollectionUtils.nullSafeIsEmpty(locksToCheck))
{
return;
}
////////////////////////////////
// actually check lock values //
////////////////////////////////
for(RecordSecurityLock recordSecurityLock : locksToCheck)
{
if(CollectionUtils.nullSafeIsEmpty(recordSecurityLock.getJoinNameChain()))
{
//////////////////////////////////////////////////////////////////////////////////
// handle the value being in the table we're inserting/updating (e.g., no join) //
//////////////////////////////////////////////////////////////////////////////////
QFieldMetaData field = table.getField(recordSecurityLock.getFieldName());
for(QRecord record : records)
{
if(action.equals(Action.UPDATE) && !record.getValues().containsKey(field.getName()))
{
/////////////////////////////////////////////////////////////////////////
// if not updating the security field, then no error can come from it! //
/////////////////////////////////////////////////////////////////////////
continue;
}
Serializable recordSecurityValue = record.getValue(field.getName());
validateRecordSecurityValue(table, record, recordSecurityLock, recordSecurityValue, field.getType(), action);
}
}
else
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// else look for the joined record - if it isn't found, assume a fail - else validate security value if found //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
QJoinMetaData leftMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(0));
QJoinMetaData rightMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(recordSecurityLock.getJoinNameChain().size() - 1));
QTableMetaData leftMostJoinTable = QContext.getQInstance().getTable(leftMostJoin.getLeftTable());
for(List<QRecord> inputRecordPage : CollectionUtils.getPages(records, 500))
{
////////////////////////////////////////////////////////////////////////////////////////////////
// set up a query for joined records //
// query will be like (fkey1=? and fkey2=?) OR (fkey1=? and fkey2=?) OR (fkey1=? and fkey2=?) //
////////////////////////////////////////////////////////////////////////////////////////////////
QueryInput queryInput = new QueryInput();
queryInput.setTableName(leftMostJoin.getLeftTable());
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR);
queryInput.setFilter(filter);
for(String joinName : recordSecurityLock.getJoinNameChain())
{
///////////////////////////////////////
// we don't need the right-most join //
///////////////////////////////////////
if(!joinName.equals(rightMostJoin.getName()))
{
queryInput.withQueryJoin(new QueryJoin().withJoinMetaData(QContext.getQInstance().getJoin(joinName)).withSelect(true));
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// foreach input record (in this page), put it in a listing hash, with key = list of join-values //
// e.g., (17,47)=(QRecord1), (18,48)=(QRecord2,QRecord3) //
// also build up the query's sub-filters here (only adding them if they're unique). //
// e.g., 2 order-lines referencing the same orderId don't need to be added to the query twice //
///////////////////////////////////////////////////////////////////////////////////////////////////
ListingHash<List<Serializable>, QRecord> inputRecordMapByJoinFields = new ListingHash<>();
for(QRecord inputRecord : inputRecordPage)
{
List<Serializable> inputRecordJoinValues = new ArrayList<>();
QQueryFilter subFilter = new QQueryFilter();
for(JoinOn joinOn : rightMostJoin.getJoinOns())
{
Serializable inputRecordValue = inputRecord.getValue(joinOn.getRightField());
inputRecordJoinValues.add(inputRecordValue);
subFilter.addCriteria(inputRecordValue == null
? new QFilterCriteria(rightMostJoin.getLeftTable() + "." + joinOn.getLeftField(), QCriteriaOperator.IS_BLANK)
: new QFilterCriteria(rightMostJoin.getLeftTable() + "." + joinOn.getLeftField(), QCriteriaOperator.EQUALS, inputRecordValue));
}
if(!inputRecordMapByJoinFields.containsKey(inputRecordJoinValues))
{
////////////////////////////////////////////////////////////////////////////////
// only add this sub-filter if it's for a list of keys we haven't seen before //
////////////////////////////////////////////////////////////////////////////////
filter.addSubFilter(subFilter);
}
inputRecordMapByJoinFields.add(inputRecordJoinValues, inputRecord);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// execute the query for joined records - then put them in a map with keys corresponding to the join values //
// e.g., (17,47)=(JoinRecord), (18,48)=(JoinRecord) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
QueryOutput queryOutput = new QueryAction().execute(queryInput);
Map<List<Serializable>, QRecord> joinRecordMapByJoinFields = new HashMap<>();
for(QRecord joinRecord : queryOutput.getRecords())
{
List<Serializable> joinRecordValues = new ArrayList<>();
for(JoinOn joinOn : rightMostJoin.getJoinOns())
{
Serializable joinValue = joinRecord.getValue(rightMostJoin.getLeftTable() + "." + joinOn.getLeftField());
if(joinValue == null && joinRecord.getValues().keySet().stream().anyMatch(n -> !n.contains(".")))
{
joinValue = joinRecord.getValue(joinOn.getLeftField());
}
joinRecordValues.add(joinValue);
}
joinRecordMapByJoinFields.put(joinRecordValues, joinRecord);
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// now for each input record, look for its joinRecord - if it isn't found, then this insert //
// isn't allowed. if it is found, then validate its value matches this session's security keys //
//////////////////////////////////////////////////////////////////////////////////////////////////
for(Map.Entry<List<Serializable>, List<QRecord>> entry : inputRecordMapByJoinFields.entrySet())
{
List<Serializable> inputRecordJoinValues = entry.getKey();
List<QRecord> inputRecords = entry.getValue();
if(joinRecordMapByJoinFields.containsKey(inputRecordJoinValues))
{
QRecord joinRecord = joinRecordMapByJoinFields.get(inputRecordJoinValues);
String fieldName = recordSecurityLock.getFieldName().replaceFirst(".*\\.", "");
QFieldMetaData field = leftMostJoinTable.getField(fieldName);
Serializable recordSecurityValue = joinRecord.getValue(fieldName);
if(recordSecurityValue == null && joinRecord.getValues().keySet().stream().anyMatch(n -> n.contains(".")))
{
recordSecurityValue = joinRecord.getValue(recordSecurityLock.getFieldName());
}
for(QRecord inputRecord : inputRecords)
{
validateRecordSecurityValue(table, inputRecord, recordSecurityLock, recordSecurityValue, field.getType(), action);
}
}
else
{
for(QRecord inputRecord : inputRecords)
{
if(RecordSecurityLock.NullValueBehavior.DENY.equals(recordSecurityLock.getNullValueBehavior()))
{
inputRecord.addError("You do not have permission to " + action.name().toLowerCase() + " this record - the referenced " + leftMostJoinTable.getLabel() + " was not found.");
}
}
}
}
}
}
}
}
/*******************************************************************************
**
*******************************************************************************/
private static List<RecordSecurityLock> getRecordSecurityLocks(QTableMetaData table)
{
List<RecordSecurityLock> recordSecurityLocks = table.getRecordSecurityLocks();
List<RecordSecurityLock> locksToCheck = new ArrayList<>();
////////////////////////////////////////
// if there are no locks, just return //
////////////////////////////////////////
if(CollectionUtils.nullSafeIsEmpty(recordSecurityLocks))
{
return (null);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// decide if any locks need checked - where one may not need checked if it has an all-access key, and the user has all-access //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for(RecordSecurityLock recordSecurityLock : recordSecurityLocks)
{
QSecurityKeyType securityKeyType = QContext.getQInstance().getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && QContext.getQSession().hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
{
LOG.debug("Session has " + securityKeyType.getAllAccessKeyName() + " - not checking this lock.");
}
else
{
locksToCheck.add(recordSecurityLock);
}
}
return (locksToCheck);
}
/*******************************************************************************
**
*******************************************************************************/
static void validateRecordSecurityValue(QTableMetaData table, QRecord record, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType, Action action)
{
if(recordSecurityValue == null)
{
/////////////////////////////////////////////////////////////////
// handle null values - error if the NullValueBehavior is DENY //
/////////////////////////////////////////////////////////////////
if(RecordSecurityLock.NullValueBehavior.DENY.equals(recordSecurityLock.getNullValueBehavior()))
{
String lockLabel = CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()) ? recordSecurityLock.getSecurityKeyType() : table.getField(recordSecurityLock.getFieldName()).getLabel();
record.addError("You do not have permission to " + action.name().toLowerCase() + " a record without a value in the field: " + lockLabel);
}
}
else
{
if(!QContext.getQSession().hasSecurityKeyValue(recordSecurityLock.getSecurityKeyType(), recordSecurityValue, fieldType))
{
if(CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()))
{
///////////////////////////////////////////////////////////////////////////////////////////////
// avoid telling the user a value from a foreign record that they didn't pass in themselves. //
///////////////////////////////////////////////////////////////////////////////////////////////
record.addError("You do not have permission to " + action.name().toLowerCase() + " this record.");
}
else
{
QFieldMetaData field = table.getField(recordSecurityLock.getFieldName());
record.addError("You do not have permission to " + action.name().toLowerCase() + " a record with a value of " + recordSecurityValue + " in the field: " + field.getLabel());
}
}
}
}
}

View File

@ -30,7 +30,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
@ -54,6 +57,8 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
*******************************************************************************/
public class MemoryRecordStore
{
private static final QLogger LOG = QLogger.getLogger(MemoryRecordStore.class);
private static MemoryRecordStore instance;
private Map<String, Map<Serializable, QRecord>> data;
@ -135,7 +140,7 @@ public class MemoryRecordStore
/*******************************************************************************
**
*******************************************************************************/
public List<QRecord> query(QueryInput input)
public List<QRecord> query(QueryInput input) throws QException
{
incrementStatistic(input);
@ -153,7 +158,20 @@ public class MemoryRecordStore
if(recordMatches)
{
records.add(qRecord);
qRecord.setErrors(new ArrayList<>());
ValidateRecordSecurityLockHelper.validateSecurityFields(input.getTable(), List.of(qRecord), ValidateRecordSecurityLockHelper.Action.SELECT);
if(CollectionUtils.nullSafeHasContents(qRecord.getErrors()))
{
//////////////////////////////////////////////////////////////////////////////////////////////////////
// security error! no record for you. but remove the error, so future generations won't see it... //
//////////////////////////////////////////////////////////////////////////////////////////////////////
qRecord.setErrors(new ArrayList<>());
LOG.trace("Error selecting record (presumably security?): " + qRecord.getErrors());
}
else
{
records.add(qRecord);
}
}
}
@ -175,7 +193,7 @@ public class MemoryRecordStore
for(QRecord record : getTableData(leftTable).values())
{
QRecord productRecord = new QRecord();
addRecordToProduct(productRecord, record, leftTable.getName());
addRecordToProduct(productRecord, record, null);
crossProduct.add(productRecord);
}
@ -220,7 +238,9 @@ public class MemoryRecordStore
{
for(JoinOn joinOn : queryJoin.getJoinMetaData().getJoinOns())
{
Serializable leftValue = productRecord.getValue(queryJoin.getBaseTableOrAlias() + "." + joinOn.getLeftField());
Serializable leftValue = productRecord.getValues().containsKey(queryJoin.getBaseTableOrAlias() + "." + joinOn.getLeftField())
? productRecord.getValue(queryJoin.getBaseTableOrAlias() + "." + joinOn.getLeftField())
: productRecord.getValue(joinOn.getLeftField());
Serializable rightValue = nextTableRecord.getValue(joinOn.getRightField());
if(!Objects.equals(leftValue, rightValue))
{
@ -240,7 +260,7 @@ public class MemoryRecordStore
{
for(Map.Entry<String, Serializable> entry : record.getValues().entrySet())
{
productRecord.withValue(tableNameOrAlias + "." + entry.getKey(), entry.getValue());
productRecord.withValue(tableNameOrAlias == null ? entry.getKey() : tableNameOrAlias + "." + entry.getKey(), entry.getValue());
}
}
@ -249,7 +269,7 @@ public class MemoryRecordStore
/*******************************************************************************
**
*******************************************************************************/
public Integer count(CountInput input)
public Integer count(CountInput input) throws QException
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(input.getTableName());
@ -349,9 +369,8 @@ public class MemoryRecordStore
{
Serializable primaryKeyValue = ValueUtils.getValueAsFieldType(primaryKeyField.getType(), record.getValue(primaryKeyField.getName()));
if(primaryKeyValue == null)
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
{
record.addError("Missing value in primary key field");
outputRecords.add(record);
continue;
}

View File

@ -341,7 +341,7 @@ class InsertActionTest extends BaseTest
insertLineItemExtrinsicInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
insertLineItemExtrinsicInput.setRecords(List.of(new QRecord().withValue("lineItemId", 4200).withValue("key", "kidsCanCallYou").withValue("value", "HoJu")));
InsertOutput insertLineItemExtrinsicOutput = new InsertAction().execute(insertLineItemExtrinsicInput);
assertEquals("You do not have permission to insert this record.", insertLineItemExtrinsicOutput.getRecords().get(0).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertLineItemExtrinsicOutput.getRecords().get(0).getErrors().get(0));
}
{
@ -373,7 +373,7 @@ class InsertActionTest extends BaseTest
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(0).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(1).getErrors().get(0));
assertEquals("You do not have permission to insert this record.", insertOutput.getRecords().get(2).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(2).getErrors().get(0));
assertEquals(0, insertOutput.getRecords().get(3).getErrors().size());
assertNotNull(insertOutput.getRecords().get(3).getValueInteger("id"));
}
@ -397,12 +397,12 @@ class InsertActionTest extends BaseTest
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(0).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(1).getErrors().get(0));
assertEquals("You do not have permission to insert this record.", insertOutput.getRecords().get(2).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(2).getErrors().get(0));
assertEquals(0, insertOutput.getRecords().get(3).getErrors().size());
assertNotNull(insertOutput.getRecords().get(3).getValueInteger("id"));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(4).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(5).getErrors().get(0));
assertEquals("You do not have permission to insert this record.", insertOutput.getRecords().get(6).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(6).getErrors().get(0));
assertEquals(0, insertOutput.getRecords().get(7).getErrors().size());
assertNotNull(insertOutput.getRecords().get(7).getValueInteger("id"));
}
@ -457,7 +457,7 @@ class InsertActionTest extends BaseTest
insertLineItemInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM);
insertLineItemInput.setRecords(List.of(new QRecord().withValue("orderId", 42).withValue("sku", "BASIC1").withValue("quantity", 1)));
InsertOutput insertLineItemOutput = new InsertAction().execute(insertLineItemInput);
assertEquals("You do not have permission to insert this record.", insertLineItemOutput.getRecords().get(0).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertLineItemOutput.getRecords().get(0).getErrors().get(0));
}
{
@ -482,7 +482,7 @@ class InsertActionTest extends BaseTest
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(0).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(1).getErrors().get(0));
assertEquals("You do not have permission to insert this record.", insertOutput.getRecords().get(2).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(2).getErrors().get(0));
assertEquals(0, insertOutput.getRecords().get(3).getErrors().size());
assertNotNull(insertOutput.getRecords().get(3).getValueInteger("id"));
}
@ -506,12 +506,12 @@ class InsertActionTest extends BaseTest
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(0).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(1).getErrors().get(0));
assertEquals("You do not have permission to insert this record.", insertOutput.getRecords().get(2).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(2).getErrors().get(0));
assertEquals(0, insertOutput.getRecords().get(3).getErrors().size());
assertNotNull(insertOutput.getRecords().get(3).getValueInteger("id"));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(4).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(5).getErrors().get(0));
assertEquals("You do not have permission to insert this record.", insertOutput.getRecords().get(6).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(6).getErrors().get(0));
assertEquals(0, insertOutput.getRecords().get(7).getErrors().size());
assertNotNull(insertOutput.getRecords().get(7).getValueInteger("id"));
}

View File

@ -29,13 +29,15 @@ 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.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
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.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.assertTrue;
@ -76,7 +78,6 @@ class UpdateActionTest extends BaseTest
@Test
void testUpdateAssociationsUpdateOneChild() throws QException
{
QInstance qInstance = QContext.getQInstance();
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
insert2OrdersWith3Lines3LineExtrinsicsAnd4OrderExtrinsicAssociations();
@ -93,18 +94,18 @@ class UpdateActionTest extends BaseTest
));
new UpdateAction().execute(updateInput);
List<QRecord> orders = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER);
List<QRecord> orders = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER);
assertEquals(2, orders.size());
assertEquals("ORD123-b", orders.get(0).getValueString("orderNo"));
List<QRecord> orderLines = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM);
List<QRecord> orderLines = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM);
assertEquals(3, orderLines.size());
assertEquals(17, orderLines.get(0).getValueInteger("quantity"));
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
assertEquals(3, lineItemExtrinsics.size());
List<QRecord> orderExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
List<QRecord> orderExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
assertEquals(4, orderExtrinsics.size());
}
@ -116,7 +117,6 @@ class UpdateActionTest extends BaseTest
@Test
void testUpdateAssociationsUpdateOneGrandChild() throws QException
{
QInstance qInstance = QContext.getQInstance();
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
insert2OrdersWith3Lines3LineExtrinsicsAnd4OrderExtrinsicAssociations();
@ -134,18 +134,18 @@ class UpdateActionTest extends BaseTest
));
new UpdateAction().execute(updateInput);
List<QRecord> orders = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER);
List<QRecord> orders = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER);
assertEquals(2, orders.size());
assertEquals("ORD123-b", orders.get(0).getValueString("orderNo"));
List<QRecord> orderLines = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM);
List<QRecord> orderLines = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM);
assertEquals(3, orderLines.size());
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
assertEquals(3, lineItemExtrinsics.size());
assertEquals("LINE-VAL-1-updated", lineItemExtrinsics.get(0).getValueString("value"));
List<QRecord> orderExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
List<QRecord> orderExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
assertEquals(4, orderExtrinsics.size());
}
@ -157,7 +157,6 @@ class UpdateActionTest extends BaseTest
@Test
void testUpdateAssociationsDeleteOneChild() throws QException
{
QInstance qInstance = QContext.getQInstance();
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
insert2OrdersWith3Lines3LineExtrinsicsAnd4OrderExtrinsicAssociations();
@ -173,18 +172,18 @@ class UpdateActionTest extends BaseTest
));
new UpdateAction().execute(updateInput);
List<QRecord> orders = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER);
List<QRecord> orders = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER);
assertEquals(2, orders.size());
assertEquals("ORD123-b", orders.get(0).getValueString("orderNo"));
List<QRecord> orderLines = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM);
List<QRecord> orderLines = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM);
assertEquals(2, orderLines.size());
assertTrue(orderLines.stream().noneMatch(r -> r.getValueInteger("id").equals(1))); // id=1 should be deleted
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
assertEquals(2, lineItemExtrinsics.size()); // one was deleted (when its parent was deleted)
List<QRecord> orderExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
List<QRecord> orderExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
assertEquals(4, orderExtrinsics.size());
}
@ -196,7 +195,6 @@ class UpdateActionTest extends BaseTest
@Test
void testUpdateAssociationsDeleteGrandchildren() throws QException
{
QInstance qInstance = QContext.getQInstance();
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
insert2OrdersWith3Lines3LineExtrinsicsAnd4OrderExtrinsicAssociations();
@ -213,17 +211,17 @@ class UpdateActionTest extends BaseTest
));
new UpdateAction().execute(updateInput);
List<QRecord> orders = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER);
List<QRecord> orders = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER);
assertEquals(2, orders.size());
assertEquals("ORD123-b", orders.get(0).getValueString("orderNo"));
List<QRecord> orderLines = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM);
List<QRecord> orderLines = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM);
assertEquals(3, orderLines.size());
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
assertEquals(1, lineItemExtrinsics.size()); // deleted the two beneath line item id=2
List<QRecord> orderExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
List<QRecord> orderExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
assertEquals(4, orderExtrinsics.size());
}
@ -235,7 +233,6 @@ class UpdateActionTest extends BaseTest
@Test
void testUpdateAssociationsInsertOneChild() throws QException
{
QInstance qInstance = QContext.getQInstance();
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
insert2OrdersWith3Lines3LineExtrinsicsAnd4OrderExtrinsicAssociations();
@ -253,19 +250,19 @@ class UpdateActionTest extends BaseTest
));
new UpdateAction().execute(updateInput);
List<QRecord> orders = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER);
List<QRecord> orders = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER);
assertEquals(2, orders.size());
assertEquals("ORD123-b", orders.get(0).getValueString("orderNo"));
List<QRecord> orderLines = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM);
List<QRecord> orderLines = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM);
assertEquals(4, orderLines.size());
assertEquals("BASIC4", orderLines.get(3).getValueString("sku"));
assertEquals(47, orderLines.get(3).getValueInteger("quantity"));
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
assertEquals(3, lineItemExtrinsics.size());
List<QRecord> orderExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
List<QRecord> orderExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
assertEquals(4, orderExtrinsics.size());
}
@ -277,7 +274,6 @@ class UpdateActionTest extends BaseTest
@Test
void testUpdateAssociationsDeleteAllChildren() throws QException
{
QInstance qInstance = QContext.getQInstance();
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
insert2OrdersWith3Lines3LineExtrinsicsAnd4OrderExtrinsicAssociations();
@ -293,16 +289,16 @@ class UpdateActionTest extends BaseTest
));
new UpdateAction().execute(updateInput);
List<QRecord> orders = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER);
List<QRecord> orders = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER);
assertEquals(2, orders.size());
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
List<QRecord> lineItemExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
assertEquals(0, lineItemExtrinsics.size());
List<QRecord> orderLines = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_LINE_ITEM);
List<QRecord> orderLines = TestUtils.queryTable(TestUtils.TABLE_NAME_LINE_ITEM);
assertEquals(0, orderLines.size()); // all of these got deleted too.
List<QRecord> orderExtrinsics = TestUtils.queryTable(qInstance, TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
List<QRecord> orderExtrinsics = TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER_EXTRINSIC);
assertEquals(4, orderExtrinsics.size());
}
@ -359,7 +355,7 @@ class UpdateActionTest extends BaseTest
new QRecord().withValue("id", 3).withValue("storeId", 999).withValue("orderNo", "ORD3"),
new QRecord().withValue("id", 4).withValue("storeId", 999).withValue("orderNo", "ORD4")
));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
new InsertAction().execute(insertInput);
//////////////////////////////////////////////////
// do our update that we'll test the results of //
@ -395,6 +391,361 @@ class UpdateActionTest extends BaseTest
///////////////////////////////////////////////////////////////////////
assertEquals(1, updateOutput.getRecords().get(3).getErrors().size());
assertEquals("Missing value in required field: Order No", updateOutput.getRecords().get(3).getErrors().get(0));
}
/*******************************************************************************
**
*******************************************************************************/
/*
@Test
void testInsertMultiLevelSecurityJoins() throws QException
{
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE, 1);
//////////////////////////////////////////////////////////////////////////////////////
// null value in the foreign key to the join-table that provides the security value //
//////////////////////////////////////////////////////////////////////////////////////
{
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
insertInput.setRecords(List.of(new QRecord().withValue("lineItemId", null).withValue("key", "kidsCanCallYou").withValue("value", "HoJu")));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(0).getErrors().get(0));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// value in the foreign key to the join-table that provides the security value, but the referenced record isn't found //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
{
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
insertInput.setRecords(List.of(new QRecord().withValue("lineItemId", 1701).withValue("key", "kidsCanCallYou").withValue("value", "HoJu")));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(0).getErrors().get(0));
}
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// insert an order and lineItem with storeId=2 - then, reset our session to only have storeId=1 in it - and try to insert an order-line referencing that order. //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
QContext.getQSession().withSecurityKeyValues(new HashMap<>());
QContext.getQSession().withSecurityKeyValues(TestUtils.SECURITY_KEY_TYPE_STORE, List.of(2));
InsertInput insertOrderInput = new InsertInput();
insertOrderInput.setTableName(TestUtils.TABLE_NAME_ORDER);
insertOrderInput.setRecords(List.of(new QRecord().withValue("id", 42).withValue("storeId", 2)));
InsertOutput insertOrderOutput = new InsertAction().execute(insertOrderInput);
assertEquals(42, insertOrderOutput.getRecords().get(0).getValueInteger("id"));
InsertInput insertLineItemInput = new InsertInput();
insertLineItemInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM);
insertLineItemInput.setRecords(List.of(new QRecord().withValue("id", 4200).withValue("orderId", 42).withValue("sku", "BASIC1").withValue("quantity", 24)));
InsertOutput insertLineItemOutput = new InsertAction().execute(insertLineItemInput);
assertEquals(4200, insertLineItemOutput.getRecords().get(0).getValueInteger("id"));
QContext.getQSession().withSecurityKeyValues(new HashMap<>());
QContext.getQSession().withSecurityKeyValues(TestUtils.SECURITY_KEY_TYPE_STORE, List.of(1));
InsertInput insertLineItemExtrinsicInput = new InsertInput();
insertLineItemExtrinsicInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
insertLineItemExtrinsicInput.setRecords(List.of(new QRecord().withValue("lineItemId", 4200).withValue("key", "kidsCanCallYou").withValue("value", "HoJu")));
InsertOutput insertLineItemExtrinsicOutput = new InsertAction().execute(insertLineItemExtrinsicInput);
assertEquals("You do not have permission to insert this record.", insertLineItemExtrinsicOutput.getRecords().get(0).getErrors().get(0));
}
{
QContext.getQSession().withSecurityKeyValues(new HashMap<>());
QContext.getQSession().withSecurityKeyValues(TestUtils.SECURITY_KEY_TYPE_STORE, List.of(1));
InsertInput insertOrderInput = new InsertInput();
insertOrderInput.setTableName(TestUtils.TABLE_NAME_ORDER);
insertOrderInput.setRecords(List.of(new QRecord().withValue("id", 47).withValue("storeId", 1)));
InsertOutput insertOrderOutput = new InsertAction().execute(insertOrderInput);
assertEquals(47, insertOrderOutput.getRecords().get(0).getValueInteger("id"));
InsertInput insertLineItemInput = new InsertInput();
insertLineItemInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM);
insertLineItemInput.setRecords(List.of(new QRecord().withValue("id", 4700).withValue("orderId", 47).withValue("sku", "BASIC1").withValue("quantity", 74)));
InsertOutput insertLineItemOutput = new InsertAction().execute(insertLineItemInput);
assertEquals(4700, insertLineItemOutput.getRecords().get(0).getValueInteger("id"));
///////////////////////////////////////////////////////
// combine all the above, plus one record that works //
///////////////////////////////////////////////////////
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
insertInput.setRecords(List.of(
new QRecord().withValue("lineItemId", null).withValue("key", "theKidsCanCallYou").withValue("value", "HoJu"),
new QRecord().withValue("lineItemId", 1701).withValue("key", "theKidsCanCallYou").withValue("value", "HoJu"),
new QRecord().withValue("lineItemId", 4200).withValue("key", "theKidsCanCallYou").withValue("value", "HoJu"),
new QRecord().withValue("lineItemId", 4700).withValue("key", "theKidsCanCallYou").withValue("value", "HoJu")
));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(0).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(1).getErrors().get(0));
assertEquals("You do not have permission to insert this record.", insertOutput.getRecords().get(2).getErrors().get(0));
assertEquals(0, insertOutput.getRecords().get(3).getErrors().size());
assertNotNull(insertOutput.getRecords().get(3).getValueInteger("id"));
}
{
/////////////////////////////////////////////////////////////////////////////////
// one more time, but with multiple input records referencing each foreign key //
/////////////////////////////////////////////////////////////////////////////////
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM_EXTRINSIC);
insertInput.setRecords(List.of(
new QRecord().withValue("lineItemId", null).withValue("key", "theKidsCanCallYou").withValue("value", "HoJu"),
new QRecord().withValue("lineItemId", 1701).withValue("key", "theKidsCanCallYou").withValue("value", "HoJu"),
new QRecord().withValue("lineItemId", 4200).withValue("key", "theKidsCanCallYou").withValue("value", "HoJu"),
new QRecord().withValue("lineItemId", 4700).withValue("key", "theKidsCanCallYou").withValue("value", "HoJu"),
new QRecord().withValue("lineItemId", null).withValue("key", "theKidsCanCallYou").withValue("value", "HoJu"),
new QRecord().withValue("lineItemId", 1701).withValue("key", "theKidsCanCallYou").withValue("value", "HoJu"),
new QRecord().withValue("lineItemId", 4200).withValue("key", "theKidsCanCallYou").withValue("value", "HoJu"),
new QRecord().withValue("lineItemId", 4700).withValue("key", "theKidsCanCallYou").withValue("value", "HoJu")
));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(0).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(1).getErrors().get(0));
assertEquals("You do not have permission to insert this record.", insertOutput.getRecords().get(2).getErrors().get(0));
assertEquals(0, insertOutput.getRecords().get(3).getErrors().size());
assertNotNull(insertOutput.getRecords().get(3).getValueInteger("id"));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(4).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(5).getErrors().get(0));
assertEquals("You do not have permission to insert this record.", insertOutput.getRecords().get(6).getErrors().get(0));
assertEquals(0, insertOutput.getRecords().get(7).getErrors().size());
assertNotNull(insertOutput.getRecords().get(7).getValueInteger("id"));
}
}
*/
/*******************************************************************************
**
*******************************************************************************/
/*
@Test
void testInsertSingleLevelSecurityJoins() throws QException
{
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE, 1);
//////////////////////////////////////////////////////////////////////////////////////
// null value in the foreign key to the join-table that provides the security value //
//////////////////////////////////////////////////////////////////////////////////////
{
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM);
insertInput.setRecords(List.of(new QRecord().withValue("orderId", null).withValue("sku", "BASIC1").withValue("quantity", 1)));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(0).getErrors().get(0));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// value in the foreign key to the join-table that provides the security value, but the referenced record isn't found //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
{
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM);
insertInput.setRecords(List.of(new QRecord().withValue("orderId", 1701).withValue("sku", "BASIC1").withValue("quantity", 1)));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(0).getErrors().get(0));
}
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// insert an order with storeId=2 - then, reset our session to only have storeId=1 in it - and try to insert an order-line referencing that order. //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
QContext.getQSession().withSecurityKeyValues(new HashMap<>());
QContext.getQSession().withSecurityKeyValues(TestUtils.SECURITY_KEY_TYPE_STORE, List.of(2));
InsertInput insertOrderInput = new InsertInput();
insertOrderInput.setTableName(TestUtils.TABLE_NAME_ORDER);
insertOrderInput.setRecords(List.of(new QRecord().withValue("id", 42).withValue("storeId", 2)));
InsertOutput insertOrderOutput = new InsertAction().execute(insertOrderInput);
assertEquals(42, insertOrderOutput.getRecords().get(0).getValueInteger("id"));
QContext.getQSession().withSecurityKeyValues(new HashMap<>());
QContext.getQSession().withSecurityKeyValues(TestUtils.SECURITY_KEY_TYPE_STORE, List.of(1));
InsertInput insertLineItemInput = new InsertInput();
insertLineItemInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM);
insertLineItemInput.setRecords(List.of(new QRecord().withValue("orderId", 42).withValue("sku", "BASIC1").withValue("quantity", 1)));
InsertOutput insertLineItemOutput = new InsertAction().execute(insertLineItemInput);
assertEquals("You do not have permission to insert this record.", insertLineItemOutput.getRecords().get(0).getErrors().get(0));
}
{
QContext.getQSession().withSecurityKeyValues(new HashMap<>());
QContext.getQSession().withSecurityKeyValues(TestUtils.SECURITY_KEY_TYPE_STORE, List.of(1));
InsertInput insertOrderInput = new InsertInput();
insertOrderInput.setTableName(TestUtils.TABLE_NAME_ORDER);
insertOrderInput.setRecords(List.of(new QRecord().withValue("id", 47).withValue("storeId", 1)));
new InsertAction().execute(insertOrderInput);
///////////////////////////////////////////////////////
// combine all the above, plus one record that works //
///////////////////////////////////////////////////////
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM);
insertInput.setRecords(List.of(
new QRecord().withValue("orderId", null).withValue("sku", "BASIC1").withValue("quantity", 1),
new QRecord().withValue("orderId", 1701).withValue("sku", "BASIC1").withValue("quantity", 1),
new QRecord().withValue("orderId", 42).withValue("sku", "BASIC1").withValue("quantity", 1),
new QRecord().withValue("orderId", 47).withValue("sku", "BASIC1").withValue("quantity", 1)
));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(0).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(1).getErrors().get(0));
assertEquals("You do not have permission to insert this record.", insertOutput.getRecords().get(2).getErrors().get(0));
assertEquals(0, insertOutput.getRecords().get(3).getErrors().size());
assertNotNull(insertOutput.getRecords().get(3).getValueInteger("id"));
}
{
/////////////////////////////////////////////////////////////////////////////////
// one more time, but with multiple input records referencing each foreign key //
/////////////////////////////////////////////////////////////////////////////////
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_LINE_ITEM);
insertInput.setRecords(List.of(
new QRecord().withValue("orderId", null).withValue("sku", "BASIC1").withValue("quantity", 1),
new QRecord().withValue("orderId", 1701).withValue("sku", "BASIC1").withValue("quantity", 1),
new QRecord().withValue("orderId", 42).withValue("sku", "BASIC1").withValue("quantity", 1),
new QRecord().withValue("orderId", 47).withValue("sku", "BASIC1").withValue("quantity", 1),
new QRecord().withValue("orderId", null).withValue("sku", "BASIC1").withValue("quantity", 1),
new QRecord().withValue("orderId", 1701).withValue("sku", "BASIC1").withValue("quantity", 1),
new QRecord().withValue("orderId", 42).withValue("sku", "BASIC1").withValue("quantity", 1),
new QRecord().withValue("orderId", 47).withValue("sku", "BASIC1").withValue("quantity", 1)
));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(0).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(1).getErrors().get(0));
assertEquals("You do not have permission to insert this record.", insertOutput.getRecords().get(2).getErrors().get(0));
assertEquals(0, insertOutput.getRecords().get(3).getErrors().size());
assertNotNull(insertOutput.getRecords().get(3).getValueInteger("id"));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(4).getErrors().get(0));
assertEquals("You do not have permission to insert this record - the referenced Order was not found.", insertOutput.getRecords().get(5).getErrors().get(0));
assertEquals("You do not have permission to insert this record.", insertOutput.getRecords().get(6).getErrors().get(0));
assertEquals(0, insertOutput.getRecords().get(7).getErrors().size());
assertNotNull(insertOutput.getRecords().get(7).getValueInteger("id"));
}
}
*/
/*******************************************************************************
**
*******************************************************************************/
@Test
void testUpdateNotFoundFails() throws QException
{
QContext.getQSession().withSecurityKeyValues(MapBuilder.of(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, ListBuilder.of(true)));
UpdateInput updateInput = new UpdateInput();
updateInput.setTableName(TestUtils.TABLE_NAME_ORDER);
updateInput.setRecords(List.of(new QRecord().withValue("id", 999).withValue("orderNo", "updated")));
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
assertEquals("No record was found to update for Id = 999", updateOutput.getRecords().get(0).getErrors().get(0));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testSecurityKeyValueDenied() throws QException
{
////////////////////////////////
// insert an order in store 1 //
////////////////////////////////
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE, 1);
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER);
insertInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("orderNo", "original").withValue("storeId", 1)));
new InsertAction().execute(insertInput);
//////////////////////////////////////////////////////////////////////
// now, as a session with store 2, try to update that store 1 order //
// it should error as "not found" //
//////////////////////////////////////////////////////////////////////
QContext.getQSession().withSecurityKeyValues(MapBuilder.of(TestUtils.SECURITY_KEY_TYPE_STORE, ListBuilder.of(2)));
UpdateInput updateInput = new UpdateInput();
updateInput.setTableName(TestUtils.TABLE_NAME_ORDER);
updateInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("orderNo", "updated")));
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
assertEquals("No record was found to update for Id = 1", updateOutput.getRecords().get(0).getErrors().get(0));
QContext.getQSession().withSecurityKeyValues(MapBuilder.of(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, ListBuilder.of(true)));
assertThat(TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER)).noneMatch(r -> r.getValueString("orderNo").equals("updated"));
assertThat(TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER)).anyMatch(r -> r.getValueString("orderNo").equals("original"));
/////////////////////////////////////////////////////////////////////////////////
// now, go back to store 1 in session, and try to change the order to store 2. //
// that should fail, as you don't have permission to write to store 2. //
/////////////////////////////////////////////////////////////////////////////////
QContext.getQSession().withSecurityKeyValues(MapBuilder.of(TestUtils.SECURITY_KEY_TYPE_STORE, ListBuilder.of(1)));
updateInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("orderNo", "updated").withValue("storeId", 2)));
updateOutput = new UpdateAction().execute(updateInput);
assertEquals("You do not have permission to update a record with a value of 2 in the field: Store Id", updateOutput.getRecords().get(0).getErrors().get(0));
QContext.getQSession().withSecurityKeyValues(MapBuilder.of(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, ListBuilder.of(true)));
assertThat(TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER)).noneMatch(r -> r.getValueString("orderNo").equals("updated"));
assertThat(TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER)).anyMatch(r -> r.getValueString("orderNo").equals("original"));
}
/*******************************************************************************
**
*******************************************************************************/
/*
@Test
void testSecurityKeyNullDenied() throws QException
{
QInstance qInstance = QContext.getQInstance();
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE, 1);
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER);
insertInput.setRecords(List.of(new QRecord()));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals("You do not have permission to insert a record without a value in the field: Store Id", insertOutput.getRecords().get(0).getErrors().get(0));
assertEquals(0, TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER).size());
}
*/
/*******************************************************************************
**
*******************************************************************************/
/*
@Test
void testSecurityKeyNullAllowed() throws QException
{
QInstance qInstance = QContext.getQInstance();
qInstance.getTable(TestUtils.TABLE_NAME_ORDER).getRecordSecurityLocks().get(0).setNullValueBehavior(RecordSecurityLock.NullValueBehavior.ALLOW);
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE, 1);
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER);
insertInput.setRecords(List.of(new QRecord()));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals(0, insertOutput.getRecords().get(0).getErrors().size());
assertEquals(1, TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER).size());
}
*/
/*******************************************************************************
**
*******************************************************************************/
/*
@Test
void testSecurityKeyAllAccess() throws QException
{
QInstance qInstance = QContext.getQInstance();
qInstance.getTable(TestUtils.TABLE_NAME_ORDER).getRecordSecurityLocks().get(0).setNullValueBehavior(RecordSecurityLock.NullValueBehavior.ALLOW);
QContext.getQSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_TYPE_STORE_ALL_ACCESS, true);
InsertInput insertInput = new InsertInput();
insertInput.setTableName(TestUtils.TABLE_NAME_ORDER);
insertInput.setRecords(List.of(
new QRecord().withValue("storeId", 999),
new QRecord().withValue("storeId", null)
));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
assertEquals(2, TestUtils.queryTable(TestUtils.TABLE_NAME_ORDER).size());
}
*/
}

View File

@ -325,7 +325,7 @@ public class TableBasedAuthenticationModuleTest extends BaseTest
MemoryRecordStore.setCollectStatistics(true);
assertTrue(new TableBasedAuthenticationModule().isSessionValid(qInstance, session));
Map<String, Integer> statistics = MemoryRecordStore.getStatistics();
assertEquals(3, statistics.get(MemoryRecordStore.STAT_QUERIES_RAN));
assertEquals(4, statistics.get(MemoryRecordStore.STAT_QUERIES_RAN));
}

View File

@ -892,6 +892,16 @@ public abstract class AbstractRDBMSAction implements QActionInterface
/*******************************************************************************
** Make it easy (e.g., for tests) to turn on logging of SQL
*******************************************************************************/
public static void setLogSQL(boolean on)
{
System.setProperty("qqq.rdbms.logSQL", String.valueOf(on));
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -104,14 +104,6 @@ public class RDBMSUpdateAction extends AbstractRDBMSAction implements UpdateInte
.toList();
recordsByFieldBeingUpdated.add(updatableFields, record);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// to update a record, we must have its primary key value - so - check - if it's missing, mark it as an error //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(record.getValue(table.getPrimaryKeyField()) == null)
{
record.addError("Missing value in primary key field");
}
if(CollectionUtils.nullSafeIsEmpty(record.getErrors()))
{
haveAnyWithoutErorrs = true;

View File

@ -27,6 +27,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
@ -74,7 +75,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
{
UpdateInput updateInput = initUpdateRequest();
updateInput.setRecords(null);
UpdateOutput updateResult = new RDBMSUpdateAction().execute(updateInput);
UpdateOutput updateResult = new UpdateAction().execute(updateInput);
assertEquals(0, updateResult.getRecords().size());
}
@ -88,8 +89,8 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
{
UpdateInput updateInput = initUpdateRequest();
updateInput.setRecords(Collections.emptyList());
new RDBMSUpdateAction().execute(updateInput);
UpdateOutput updateResult = new RDBMSUpdateAction().execute(updateInput);
new UpdateAction().execute(updateInput);
UpdateOutput updateResult = new UpdateAction().execute(updateInput);
assertEquals(0, updateResult.getRecords().size());
}
@ -110,9 +111,9 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
.withValue("birthDate", "2210-05-20");
updateInput.setRecords(List.of(record));
UpdateOutput updateResult = new RDBMSUpdateAction().execute(updateInput);
UpdateOutput updateResult = new UpdateAction().execute(updateInput);
Map<String, Integer> statistics = QueryManager.getStatistics();
assertEquals(1, statistics.get(QueryManager.STAT_QUERIES_RAN));
assertEquals(2, statistics.get(QueryManager.STAT_QUERIES_RAN));
assertEquals(1, updateResult.getRecords().size(), "Should return 1 row");
assertEquals(2, updateResult.getRecords().get(0).getValue("id"), "Should have id=2 in the row");
@ -163,12 +164,12 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
updateInput.setRecords(List.of(record1, record2, record3));
UpdateOutput updateResult = new RDBMSUpdateAction().execute(updateInput);
UpdateOutput updateResult = new UpdateAction().execute(updateInput);
// this test runs one batch and one regular query
Map<String, Integer> statistics = QueryManager.getStatistics();
assertEquals(1, statistics.get(QueryManager.STAT_BATCHES_RAN));
assertEquals(1, statistics.get(QueryManager.STAT_QUERIES_RAN));
assertEquals(2, statistics.get(QueryManager.STAT_QUERIES_RAN));
assertEquals(3, updateResult.getRecords().size(), "Should return 3 rows");
assertEquals(1, updateResult.getRecords().get(0).getValue("id"), "Should have expected ids in the row");
@ -234,7 +235,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
updateInput.setRecords(List.of(record1, record2));
UpdateOutput updateResult = new RDBMSUpdateAction().execute(updateInput);
UpdateOutput updateResult = new UpdateAction().execute(updateInput);
Map<String, Integer> statistics = QueryManager.getStatistics();
assertEquals(1, statistics.get(QueryManager.STAT_BATCHES_RAN));
@ -287,9 +288,9 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
updateInput.setRecords(records);
UpdateOutput updateResult = new RDBMSUpdateAction().execute(updateInput);
UpdateOutput updateResult = new UpdateAction().execute(updateInput);
Map<String, Integer> statistics = QueryManager.getStatistics();
assertEquals(1, statistics.get(QueryManager.STAT_QUERIES_RAN));
assertEquals(2, statistics.get(QueryManager.STAT_QUERIES_RAN));
assertEquals(5, updateResult.getRecords().size(), "Should return 5 rows");
// todo - add errors to QRecord? assertTrue(updateResult.getRecords().stream().noneMatch(qrs -> CollectionUtils.nullSafeHasContents(qrs.getErrors())), "There should be no errors");
@ -320,7 +321,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
.withValue("id", 1)
.withValue("firstName", "Johnny Updated"));
updateInput.setRecords(records);
new RDBMSUpdateAction().execute(updateInput);
new UpdateAction().execute(updateInput);
String updatedModifyDate = selectModifyDate(1);
@ -345,7 +346,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
.withValue("createDate", "2022-10-03T10:29:35Z")
.withValue("firstName", "Johnny Updated"));
updateInput.setRecords(records);
new RDBMSUpdateAction().execute(updateInput);
new UpdateAction().execute(updateInput);
}
@ -362,7 +363,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
records.add(new QRecord()
.withValue("firstName", "Johnny Updated"));
updateInput.setRecords(records);
UpdateOutput updateOutput = new RDBMSUpdateAction().execute(updateInput);
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
assertFalse(updateOutput.getRecords().get(0).getErrors().isEmpty());
assertEquals("Missing value in primary key field", updateOutput.getRecords().get(0).getErrors().get(0));
}
@ -374,7 +375,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
.withValue("id", null)
.withValue("firstName", "Johnny Updated"));
updateInput.setRecords(records);
UpdateOutput updateOutput = new RDBMSUpdateAction().execute(updateInput);
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
assertFalse(updateOutput.getRecords().get(0).getErrors().isEmpty());
assertEquals("Missing value in primary key field", updateOutput.getRecords().get(0).getErrors().get(0));
}
@ -389,7 +390,7 @@ public class RDBMSUpdateActionTest extends RDBMSActionTest
.withValue("id", 2)
.withValue("firstName", "Johnny Updated"));
updateInput.setRecords(records);
UpdateOutput updateOutput = new RDBMSUpdateAction().execute(updateInput);
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
assertFalse(updateOutput.getRecords().get(0).getErrors().isEmpty());
assertEquals("Missing value in primary key field", updateOutput.getRecords().get(0).getErrors().get(0));

View File

@ -76,9 +76,6 @@ class QJavalinApiHandlerTest extends BaseTest
protected static QJavalinImplementation qJavalinImplementation;
private static final String OAUTH_CLIENT_ID = "test-oauth-client-id";
private static final String OAUTH_CLIENT_SECRET = "test-oauth-client-secret";
/*******************************************************************************