Remove single-parent concept on app-children; more working version of recordLock from join

This commit is contained in:
2023-01-13 09:23:06 -06:00
parent 9a58c7683b
commit 2b0974f4a5
16 changed files with 162 additions and 157 deletions

View File

@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.actions.permissions;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -121,7 +120,7 @@ public class PermissionsHelper
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// if the entity just has a 'has access', then check for 'has access' // // if the entity just has a 'has access', then check for 'has access' //
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
return getPermissionCheckResult(actionInput, rules, permissionBaseName, PrivatePermissionSubType.HAS_ACCESS); return getPermissionCheckResult(actionInput, rules, permissionBaseName, metaDataWithPermissionRules, PrivatePermissionSubType.HAS_ACCESS);
} }
case READ_WRITE_PERMISSIONS: case READ_WRITE_PERMISSIONS:
{ {
@ -130,9 +129,9 @@ public class PermissionsHelper
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
if(metaDataWithPermissionRules instanceof QTableMetaData) if(metaDataWithPermissionRules instanceof QTableMetaData)
{ {
return getPermissionCheckResult(actionInput, rules, permissionBaseName, PrivatePermissionSubType.READ, PrivatePermissionSubType.WRITE); return getPermissionCheckResult(actionInput, rules, permissionBaseName, metaDataWithPermissionRules, PrivatePermissionSubType.READ, PrivatePermissionSubType.WRITE);
} }
return getPermissionCheckResult(actionInput, rules, permissionBaseName, PrivatePermissionSubType.HAS_ACCESS); return getPermissionCheckResult(actionInput, rules, permissionBaseName, metaDataWithPermissionRules, PrivatePermissionSubType.HAS_ACCESS);
} }
case READ_INSERT_EDIT_DELETE_PERMISSIONS: case READ_INSERT_EDIT_DELETE_PERMISSIONS:
{ {
@ -141,9 +140,9 @@ public class PermissionsHelper
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
if(metaDataWithPermissionRules instanceof QTableMetaData) if(metaDataWithPermissionRules instanceof QTableMetaData)
{ {
return getPermissionCheckResult(actionInput, rules, permissionBaseName, TablePermissionSubType.READ, TablePermissionSubType.INSERT, TablePermissionSubType.EDIT, TablePermissionSubType.DELETE); return getPermissionCheckResult(actionInput, rules, permissionBaseName, metaDataWithPermissionRules, TablePermissionSubType.READ, TablePermissionSubType.INSERT, TablePermissionSubType.EDIT, TablePermissionSubType.DELETE);
} }
return getPermissionCheckResult(actionInput, rules, permissionBaseName, PrivatePermissionSubType.HAS_ACCESS); return getPermissionCheckResult(actionInput, rules, permissionBaseName, metaDataWithPermissionRules, PrivatePermissionSubType.HAS_ACCESS);
} }
default: default:
{ {
@ -164,10 +163,6 @@ public class PermissionsHelper
static Map<String, CustomPermissionChecker> customPermissionCheckerMap = new HashMap<>();
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -181,12 +176,7 @@ public class PermissionsHelper
///////////////////////////////////// /////////////////////////////////////
// todo - avoid stack overflows... // // todo - avoid stack overflows... //
///////////////////////////////////// /////////////////////////////////////
if(!customPermissionCheckerMap.containsKey(effectivePermissionRules.getCustomPermissionChecker().getName()))
{
CustomPermissionChecker customPermissionChecker = QCodeLoader.getAdHoc(CustomPermissionChecker.class, effectivePermissionRules.getCustomPermissionChecker()); CustomPermissionChecker customPermissionChecker = QCodeLoader.getAdHoc(CustomPermissionChecker.class, effectivePermissionRules.getCustomPermissionChecker());
customPermissionCheckerMap.put(effectivePermissionRules.getCustomPermissionChecker().getName(), customPermissionChecker);
}
CustomPermissionChecker customPermissionChecker = customPermissionCheckerMap.get(effectivePermissionRules.getCustomPermissionChecker().getName());
customPermissionChecker.checkPermissionsThrowing(actionInput, process); customPermissionChecker.checkPermissionsThrowing(actionInput, process);
return; return;
} }
@ -421,11 +411,26 @@ public class PermissionsHelper
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
static PermissionCheckResult getPermissionCheckResult(AbstractActionInput actionInput, QPermissionRules rules, String permissionBaseName, PermissionSubType... permissionSubTypes) static PermissionCheckResult getPermissionCheckResult(AbstractActionInput actionInput, QPermissionRules rules, String permissionBaseName, MetaDataWithPermissionRules metaDataWithPermissionRules, PermissionSubType... permissionSubTypes)
{ {
for(PermissionSubType permissionSubType : permissionSubTypes) for(PermissionSubType permissionSubType : permissionSubTypes)
{ {
PermissionSubType effectivePermissionSubType = getEffectivePermissionSubType(rules, permissionSubType); PermissionSubType effectivePermissionSubType = getEffectivePermissionSubType(rules, permissionSubType);
if(rules.getCustomPermissionChecker() != null)
{
try
{
CustomPermissionChecker customPermissionChecker = QCodeLoader.getAdHoc(CustomPermissionChecker.class, rules.getCustomPermissionChecker());
customPermissionChecker.checkPermissionsThrowing(actionInput, metaDataWithPermissionRules);
return (PermissionCheckResult.ALLOW);
}
catch(QPermissionDeniedException e)
{
return (getPermissionDeniedCheckResult(rules));
}
}
if(hasPermission(actionInput.getSession(), permissionBaseName, effectivePermissionSubType)) if(hasPermission(actionInput.getSession(), permissionBaseName, effectivePermissionSubType))
{ {
return (PermissionCheckResult.ALLOW); return (PermissionCheckResult.ALLOW);
@ -526,7 +531,7 @@ public class PermissionsHelper
if(!hasPermission(actionInput.getSession(), permissionBaseName, effectivePermissionSubType)) if(!hasPermission(actionInput.getSession(), permissionBaseName, effectivePermissionSubType))
{ {
LOG.debug("Throwing permission denied for: " + getPermissionName(permissionBaseName, effectivePermissionSubType) + " for " + actionInput.getSession().getUser()); // LOG.debug("Throwing permission denied for: " + getPermissionName(permissionBaseName, effectivePermissionSubType) + " for " + actionInput.getSession().getUser());
throw (new QPermissionDeniedException("Permission denied.")); throw (new QPermissionDeniedException("Permission denied."));
} }
} }

View File

@ -51,6 +51,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior; import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn; 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.layout.QAppChildMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
@ -356,7 +357,6 @@ public class QInstanceValidator
qInstance.getTables().forEach((tableName, table) -> qInstance.getTables().forEach((tableName, table) ->
{ {
assertCondition(Objects.equals(tableName, table.getName()), "Inconsistent naming for table: " + tableName + "/" + table.getName() + "."); assertCondition(Objects.equals(tableName, table.getName()), "Inconsistent naming for table: " + tableName + "/" + table.getName() + ".");
validateAppChildHasValidParentAppName(qInstance, table);
//////////////////////////////////////// ////////////////////////////////////////
// validate the backend for the table // // validate the backend for the table //
@ -465,10 +465,37 @@ public class QInstanceValidator
prefix = "Table " + table.getName() + " recordSecurityLock (of key type " + securityKeyTypeName + ") "; prefix = "Table " + table.getName() + " recordSecurityLock (of key type " + securityKeyTypeName + ") ";
String fieldName = recordSecurityLock.getFieldName(); boolean hasAnyBadJoins = false;
if(assertCondition(StringUtils.hasContent(fieldName), prefix + "is missing a fieldName")) for(String joinName : CollectionUtils.nonNullList(recordSecurityLock.getJoinNameChain()))
{ {
assertCondition(findField(qInstance, table, null, fieldName), prefix + "has an unrecognized fieldName: " + fieldName); if(!assertCondition(qInstance.getJoin(joinName) != null, prefix + "has an unrecognized joinName: " + joinName))
{
hasAnyBadJoins = true;
}
}
String fieldName = recordSecurityLock.getFieldName();
////////////////////////////////////////////////////////////////////////////////
// don't bother trying to validate field names if we know we have a bad join. //
////////////////////////////////////////////////////////////////////////////////
if(assertCondition(StringUtils.hasContent(fieldName), prefix + "is missing a fieldName") && !hasAnyBadJoins)
{
List<QueryJoin> joins = new ArrayList<>();
for(String joinName : CollectionUtils.nonNullList(recordSecurityLock.getJoinNameChain()))
{
QJoinMetaData join = qInstance.getJoin(joinName);
if(join.getLeftTable().equals(table.getName()))
{
joins.add(new QueryJoin(join));
}
else if(join.getRightTable().equals(table.getName()))
{
joins.add(new QueryJoin(join.flip()));
}
}
assertCondition(findField(qInstance, table, joins, fieldName), prefix + "has an unrecognized fieldName: " + fieldName);
} }
assertCondition(recordSecurityLock.getNullValueBehavior() != null, prefix + "is missing a nullValueBehavior"); assertCondition(recordSecurityLock.getNullValueBehavior() != null, prefix + "is missing a nullValueBehavior");
@ -960,8 +987,6 @@ public class QInstanceValidator
{ {
assertCondition(Objects.equals(processName, process.getName()), "Inconsistent naming for process: " + processName + "/" + process.getName() + "."); assertCondition(Objects.equals(processName, process.getName()), "Inconsistent naming for process: " + processName + "/" + process.getName() + ".");
validateAppChildHasValidParentAppName(qInstance, process);
///////////////////////////////////////////// /////////////////////////////////////////////
// validate the table name for the process // // validate the table name for the process //
///////////////////////////////////////////// /////////////////////////////////////////////
@ -1015,7 +1040,6 @@ public class QInstanceValidator
qInstance.getReports().forEach((reportName, report) -> qInstance.getReports().forEach((reportName, report) ->
{ {
assertCondition(Objects.equals(reportName, report.getName()), "Inconsistent naming for report: " + reportName + "/" + report.getName() + "."); assertCondition(Objects.equals(reportName, report.getName()), "Inconsistent naming for report: " + reportName + "/" + report.getName() + ".");
validateAppChildHasValidParentAppName(qInstance, report);
//////////////////////////////////////// ////////////////////////////////////////
// validate dataSources in the report // // validate dataSources in the report //
@ -1172,6 +1196,7 @@ public class QInstanceValidator
{ {
joinTable.getField(fieldNameAfterDot); joinTable.getField(fieldNameAfterDot);
foundField = true; foundField = true;
break;
} }
catch(Exception e2) catch(Exception e2)
{ {
@ -1216,7 +1241,10 @@ public class QInstanceValidator
Set<String> childNames = new HashSet<>(); Set<String> childNames = new HashSet<>();
for(QAppChildMetaData child : app.getChildren()) for(QAppChildMetaData child : app.getChildren())
{ {
assertCondition(Objects.equals(appName, child.getParentAppName()), "Child " + child.getName() + " of app " + appName + " does not have its parent app properly set."); if(child instanceof QAppMetaData childApp)
{
assertCondition(Objects.equals(appName, childApp.getParentAppName()), "Child app " + child.getName() + " of app " + appName + " does not have its parent app properly set.");
}
assertCondition(!childNames.contains(child.getName()), "App " + appName + " contains more than one child named " + child.getName()); assertCondition(!childNames.contains(child.getName()), "App " + appName + " contains more than one child named " + child.getName());
childNames.add(child.getName()); childNames.add(child.getName());
} }
@ -1442,7 +1470,7 @@ public class QInstanceValidator
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private void validateAppChildHasValidParentAppName(QInstance qInstance, QAppChildMetaData appChild) private void validateAppChildHasValidParentAppName(QInstance qInstance, QAppMetaData appChild)
{ {
if(appChild.getParentAppName() != null) if(appChild.getParentAppName() != null)
{ {

View File

@ -28,6 +28,7 @@ import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock; import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; 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.CollectionUtils;
@ -76,7 +77,7 @@ public class JoinsContext
/////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(instance.getTable(tableName).getRecordSecurityLocks())) for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(instance.getTable(tableName).getRecordSecurityLocks()))
{ {
for(String joinName : CollectionUtils.nonNullList(recordSecurityLock.getJoinChain())) for(String joinName : CollectionUtils.nonNullList(recordSecurityLock.getJoinNameChain()))
{ {
if(this.queryJoins.stream().anyMatch(qj -> qj.getJoinMetaData().getName().equals(joinName))) if(this.queryJoins.stream().anyMatch(qj -> qj.getJoinMetaData().getName().equals(joinName)))
{ {
@ -86,7 +87,12 @@ public class JoinsContext
} }
else else
{ {
this.queryJoins.add(new QueryJoin().withJoinMetaData(instance.getJoin(joinName)).withType(QueryJoin.Type.INNER)); // todo aliases? probably. QJoinMetaData join = instance.getJoin(joinName);
if(tableName.equals(join.getRightTable()))
{
join = join.flip();
}
this.queryJoins.add(new QueryJoin().withJoinMetaData(join).withType(QueryJoin.Type.INNER)); // todo aliases? probably.
} }
} }
} }

View File

@ -300,10 +300,20 @@ public class QJoinMetaData
*******************************************************************************/ *******************************************************************************/
public QJoinMetaData withInferredName() public QJoinMetaData withInferredName()
{ {
if(!StringUtils.hasContent(getLeftTable()) || !StringUtils.hasContent(getRightTable())) return (withName(makeInferredJoinName(getLeftTable(), getRightTable())));
}
/*******************************************************************************
**
*******************************************************************************/
public static String makeInferredJoinName(String leftTable, String rightTable)
{
if(!StringUtils.hasContent(leftTable) || !StringUtils.hasContent(rightTable))
{ {
throw (new IllegalStateException("Missing either a left or right table name when trying to set inferred name for join")); throw (new IllegalStateException("Missing either a left or right table name when trying to set inferred name for join"));
} }
return (withName(getLeftTable() + "Join" + StringUtils.ucFirst(getRightTable()))); return (leftTable + "Join" + StringUtils.ucFirst(rightTable));
} }
} }

View File

@ -28,16 +28,6 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
*******************************************************************************/ *******************************************************************************/
public interface QAppChildMetaData public interface QAppChildMetaData
{ {
/*******************************************************************************
**
*******************************************************************************/
void setParentAppName(String parentAppName);
/*******************************************************************************
**
*******************************************************************************/
String getParentAppName();
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/

View File

@ -169,7 +169,11 @@ public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRu
this.children = new ArrayList<>(); this.children = new ArrayList<>();
} }
this.children.add(child); this.children.add(child);
child.setParentAppName(this.getName());
if(child instanceof QAppMetaData childApp)
{
childApp.setParentAppName(this.getName());
}
} }
@ -202,7 +206,6 @@ public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRu
** Getter for parentAppName ** Getter for parentAppName
** **
*******************************************************************************/ *******************************************************************************/
@Override
public String getParentAppName() public String getParentAppName()
{ {
return parentAppName; return parentAppName;
@ -214,7 +217,6 @@ public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRu
** Setter for parentAppName ** Setter for parentAppName
** **
*******************************************************************************/ *******************************************************************************/
@Override
public void setParentAppName(String parentAppName) public void setParentAppName(String parentAppName)
{ {
this.parentAppName = parentAppName; this.parentAppName = parentAppName;

View File

@ -54,7 +54,6 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
private List<QStepMetaData> stepList; // these are the steps that are ran, by-default, in the order they are ran in private List<QStepMetaData> stepList; // these are the steps that are ran, by-default, in the order they are ran in
private Map<String, QStepMetaData> steps; // this is the full map of possible steps private Map<String, QStepMetaData> steps; // this is the full map of possible steps
private String parentAppName;
private QIcon icon; private QIcon icon;
private QScheduleMetaData schedule; private QScheduleMetaData schedule;
@ -388,30 +387,6 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
/*******************************************************************************
** Getter for parentAppName
**
*******************************************************************************/
@Override
public String getParentAppName()
{
return parentAppName;
}
/*******************************************************************************
** Setter for parentAppName
**
*******************************************************************************/
@Override
public void setParentAppName(String parentAppName)
{
this.parentAppName = parentAppName;
}
/******************************************************************************* /*******************************************************************************
** Getter for icon ** Getter for icon
** **

View File

@ -48,7 +48,6 @@ public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissio
private List<QReportDataSource> dataSources; private List<QReportDataSource> dataSources;
private List<QReportView> views; private List<QReportView> views;
private String parentAppName;
private QIcon icon; private QIcon icon;
@ -304,28 +303,6 @@ public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissio
/*******************************************************************************
**
*******************************************************************************/
@Override
public void setParentAppName(String parentAppName)
{
this.parentAppName = parentAppName;
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getParentAppName()
{
return (this.parentAppName);
}
/******************************************************************************* /*******************************************************************************
** Getter for icon ** Getter for icon
** **

View File

@ -34,7 +34,7 @@ public class RecordSecurityLock
{ {
private String securityKeyType; private String securityKeyType;
private String fieldName; private String fieldName;
private List<String> joinChain; // todo - add validation in validator!! private List<String> joinNameChain; // todo - add validation in validator!!
private NullValueBehavior nullValueBehavior = NullValueBehavior.DENY; private NullValueBehavior nullValueBehavior = NullValueBehavior.DENY;
@ -152,34 +152,34 @@ public class RecordSecurityLock
} }
/******************************************************************************* /*******************************************************************************
** Getter for joinChain ** Getter for joinNameChain
*******************************************************************************/ *******************************************************************************/
public List<String> getJoinChain() public List<String> getJoinNameChain()
{ {
return (this.joinChain); return (this.joinNameChain);
} }
/******************************************************************************* /*******************************************************************************
** Setter for joinChain ** Setter for joinNameChain
*******************************************************************************/ *******************************************************************************/
public void setJoinChain(List<String> joinChain) public void setJoinNameChain(List<String> joinNameChain)
{ {
this.joinChain = joinChain; this.joinNameChain = joinNameChain;
} }
/******************************************************************************* /*******************************************************************************
** Fluent setter for joinChain ** Fluent setter for joinNameChain
*******************************************************************************/ *******************************************************************************/
public RecordSecurityLock withJoinChain(List<String> joinChain) public RecordSecurityLock withJoinNameChain(List<String> joinNameChain)
{ {
this.joinChain = joinChain; this.joinNameChain = joinNameChain;
return (this); return (this);
} }
} }

View File

@ -80,7 +80,6 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
private Map<String, QCodeReference> customizers; private Map<String, QCodeReference> customizers;
private String parentAppName;
private QIcon icon; private QIcon icon;
private String recordLabelFormat; private String recordLabelFormat;
@ -538,30 +537,6 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
/*******************************************************************************
** Getter for parentAppName
**
*******************************************************************************/
@Override
public String getParentAppName()
{
return parentAppName;
}
/*******************************************************************************
** Setter for parentAppName
**
*******************************************************************************/
@Override
public void setParentAppName(String parentAppName)
{
this.parentAppName = parentAppName;
}
/******************************************************************************* /*******************************************************************************
** Getter for icon ** Getter for icon
** **

View File

@ -242,7 +242,7 @@ class MetaDataActionTest
// with several permissions set, we should see some things, and they should have permissions turned on // // with several permissions set, we should see some things, and they should have permissions turned on //
///////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////
assertEquals(Set.of("person"), result.getTables().keySet()); assertEquals(Set.of("person"), result.getTables().keySet());
assertEquals(Set.of("increaseBirthdate", "runShapesPersonReport"), result.getProcesses().keySet()); assertEquals(Set.of("increaseBirthdate", "runShapesPersonReport", "person.bulkInsert", "person.bulkEdit", "person.bulkDelete"), result.getProcesses().keySet());
assertEquals(Set.of("shapesPersonReport", "personJoinShapeReport", "simplePersonReport"), result.getReports().keySet()); assertEquals(Set.of("shapesPersonReport", "personJoinShapeReport", "simplePersonReport"), result.getReports().keySet());
assertEquals(Set.of("PersonsByCreateDateBarChart"), result.getWidgets().keySet()); assertEquals(Set.of("PersonsByCreateDateBarChart"), result.getWidgets().keySet());
@ -276,7 +276,8 @@ class MetaDataActionTest
MetaDataOutput result = new MetaDataAction().execute(input); MetaDataOutput result = new MetaDataAction().execute(input);
assertEquals(Set.of("person", "personFile", "personMemory"), result.getTables().keySet()); assertEquals(Set.of("person", "personFile", "personMemory"), result.getTables().keySet());
assertEquals(Set.of("increaseBirthdate"), result.getProcesses().keySet());
assertEquals(Set.of("increaseBirthdate", "personFile.bulkInsert", "personFile.bulkEdit", "personFile.bulkDelete", "personMemory.bulkInsert", "personMemory.bulkEdit", "personMemory.bulkDelete"), result.getProcesses().keySet());
assertEquals(Set.of(), result.getReports().keySet()); assertEquals(Set.of(), result.getReports().keySet());
assertEquals(Set.of(), result.getWidgets().keySet()); assertEquals(Set.of(), result.getWidgets().keySet());
@ -322,7 +323,7 @@ class MetaDataActionTest
MetaDataOutput result = new MetaDataAction().execute(input); MetaDataOutput result = new MetaDataAction().execute(input);
assertEquals(Set.of("person", "personFile", "personMemory"), result.getTables().keySet()); assertEquals(Set.of("person", "personFile", "personMemory"), result.getTables().keySet());
assertEquals(Set.of("increaseBirthdate"), result.getProcesses().keySet()); assertEquals(Set.of("increaseBirthdate", "personFile.bulkInsert", "personFile.bulkEdit", "personMemory.bulkDelete"), result.getProcesses().keySet());
assertEquals(Set.of(), result.getReports().keySet()); assertEquals(Set.of(), result.getReports().keySet());
assertEquals(Set.of(), result.getWidgets().keySet()); assertEquals(Set.of(), result.getWidgets().keySet());

View File

@ -504,8 +504,6 @@ class QInstanceValidatorTest
void testChildrenWithBadParentAppName() void testChildrenWithBadParentAppName()
{ {
String[] reasons = new String[] { "Unrecognized parent app", "does not have its parent app properly set" }; String[] reasons = new String[] { "Unrecognized parent app", "does not have its parent app properly set" };
assertValidationFailureReasons((qInstance) -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON).setParentAppName("notAnApp"), reasons);
assertValidationFailureReasons((qInstance) -> qInstance.getProcess(TestUtils.PROCESS_NAME_GREET_PEOPLE).setParentAppName("notAnApp"), reasons);
assertValidationFailureReasons((qInstance) -> qInstance.getApp(TestUtils.APP_NAME_GREETINGS).setParentAppName("notAnApp"), reasons); assertValidationFailureReasons((qInstance) -> qInstance.getApp(TestUtils.APP_NAME_GREETINGS).setParentAppName("notAnApp"), reasons);
} }

View File

@ -310,11 +310,11 @@ public abstract class AbstractRDBMSAction implements QActionInterface
{ {
String whereClauseWithoutSecurity = makeWhereClauseWithoutSecurity(instance, table, joinsContext, filter, params); String whereClauseWithoutSecurity = makeWhereClauseWithoutSecurity(instance, table, joinsContext, filter, params);
QQueryFilter securityFilter = getSecurityFilter(instance, session, table, joinsContext); QQueryFilter securityFilter = getSecurityFilter(instance, session, table, joinsContext);
if(securityFilter == null || CollectionUtils.nullSafeIsEmpty(securityFilter.getCriteria())) if(!securityFilter.hasAnyCriteria())
{ {
return (whereClauseWithoutSecurity); return (whereClauseWithoutSecurity);
} }
String securityWhereClause = getSqlWhereStringAndPopulateParamsListFromNonNestedFilter(instance, table, joinsContext, securityFilter.getCriteria(), QQueryFilter.BooleanOperator.AND, params); String securityWhereClause = makeWhereClauseWithoutSecurity(instance, table, joinsContext, securityFilter, params);
return ("(" + whereClauseWithoutSecurity + ") AND (" + securityWhereClause + ")"); return ("(" + whereClauseWithoutSecurity + ") AND (" + securityWhereClause + ")");
} }
@ -367,14 +367,14 @@ public abstract class AbstractRDBMSAction implements QActionInterface
*******************************************************************************/ *******************************************************************************/
private QQueryFilter getSecurityFilter(QInstance instance, QSession session, QTableMetaData table, JoinsContext joinsContext) private QQueryFilter getSecurityFilter(QInstance instance, QSession session, QTableMetaData table, JoinsContext joinsContext)
{ {
QQueryFilter newFilter = new QQueryFilter(); QQueryFilter securityFilter = new QQueryFilter();
newFilter.setBooleanOperator(QQueryFilter.BooleanOperator.AND); securityFilter.setBooleanOperator(QQueryFilter.BooleanOperator.AND);
List<QFilterCriteria> securityCriteria = new ArrayList<>();
newFilter.setCriteria(securityCriteria);
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(table.getRecordSecurityLocks())) for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(table.getRecordSecurityLocks()))
{ {
addCriteriaForRecordSecurityLock(instance, session, table, securityCriteria, recordSecurityLock, joinsContext, table.getName()); // todo - uh, if it's a RIGHT (or FULL) join, then, this should be isOuter = true, right?
boolean isOuter = false;
addSubFilterForRecordSecurityLock(instance, session, table, securityFilter, recordSecurityLock, joinsContext, table.getName(), isOuter);
} }
for(QueryJoin queryJoin : CollectionUtils.nonNullList(joinsContext.getQueryJoins())) for(QueryJoin queryJoin : CollectionUtils.nonNullList(joinsContext.getQueryJoins()))
@ -382,11 +382,12 @@ public abstract class AbstractRDBMSAction implements QActionInterface
QTableMetaData joinTable = instance.getTable(queryJoin.getJoinTable()); QTableMetaData joinTable = instance.getTable(queryJoin.getJoinTable());
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(joinTable.getRecordSecurityLocks())) for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(joinTable.getRecordSecurityLocks()))
{ {
addCriteriaForRecordSecurityLock(instance, session, joinTable, securityCriteria, recordSecurityLock, joinsContext, queryJoin.getJoinTableOrItsAlias()); boolean isOuter = queryJoin.getType().equals(QueryJoin.Type.LEFT); // todo full?
addSubFilterForRecordSecurityLock(instance, session, joinTable, securityFilter, recordSecurityLock, joinsContext, queryJoin.getJoinTableOrItsAlias(), isOuter);
} }
} }
return (newFilter); return (securityFilter);
} }
@ -394,7 +395,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
private static void addCriteriaForRecordSecurityLock(QInstance instance, QSession session, QTableMetaData table, List<QFilterCriteria> securityCriteria, RecordSecurityLock recordSecurityLock, JoinsContext joinsContext, String tableNameOrAlias) private static void addSubFilterForRecordSecurityLock(QInstance instance, QSession session, QTableMetaData table, QQueryFilter securityFilter, RecordSecurityLock recordSecurityLock, JoinsContext joinsContext, String tableNameOrAlias, boolean isOuter)
{ {
////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////
// check if the key type has an all-access key, and if so, if it's set to true for the current user/session // // check if the key type has an all-access key, and if so, if it's set to true for the current user/session //
@ -411,9 +412,12 @@ public abstract class AbstractRDBMSAction implements QActionInterface
} }
} }
if(CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinChain())) String fieldName = tableNameOrAlias + "." + recordSecurityLock.getFieldName();
String fieldNameWithoutTablePrefix = recordSecurityLock.getFieldName().replaceFirst(".*\\.", "");
String fieldNameTablePrefix = recordSecurityLock.getFieldName().replaceFirst("\\..*", "");
if(CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()))
{ {
for(String joinName : recordSecurityLock.getJoinChain()) for(String joinName : recordSecurityLock.getJoinNameChain())
{ {
QJoinMetaData joinMetaData = instance.getJoin(joinName); QJoinMetaData joinMetaData = instance.getJoin(joinName);
@ -433,16 +437,27 @@ public abstract class AbstractRDBMSAction implements QActionInterface
throw (new RuntimeException("Could not find joinMetaData for recordSecurityLock with joinChain member [" + joinName + "]")); throw (new RuntimeException("Could not find joinMetaData for recordSecurityLock with joinChain member [" + joinName + "]"));
} }
if(fieldNameTablePrefix.equals(joinMetaData.getLeftTable()))
{
table = instance.getTable(joinMetaData.getLeftTable());
}
else
{
table = instance.getTable(joinMetaData.getRightTable()); table = instance.getTable(joinMetaData.getRightTable());
}
tableNameOrAlias = table.getName(); tableNameOrAlias = table.getName();
fieldName = tableNameOrAlias + "." + fieldNameWithoutTablePrefix;
} }
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// else - get the key values from the session and decide what kind of criterion to build // // else - get the key values from the session and decide what kind of criterion to build //
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
List<Serializable> securityKeyValues = session.getSecurityKeyValues(recordSecurityLock.getSecurityKeyType(), table.getField(recordSecurityLock.getFieldName()).getType()); QQueryFilter lockFilter = new QQueryFilter();
String fieldName = tableNameOrAlias + "." + recordSecurityLock.getFieldName(); List<QFilterCriteria> lockCriteria = new ArrayList<>();
lockFilter.setCriteria(lockCriteria);
List<Serializable> securityKeyValues = session.getSecurityKeyValues(recordSecurityLock.getSecurityKeyType(), table.getField(fieldNameWithoutTablePrefix).getType());
if(CollectionUtils.nullSafeIsEmpty(securityKeyValues)) if(CollectionUtils.nullSafeIsEmpty(securityKeyValues))
{ {
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -450,7 +465,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(RecordSecurityLock.NullValueBehavior.ALLOW.equals(recordSecurityLock.getNullValueBehavior())) if(RecordSecurityLock.NullValueBehavior.ALLOW.equals(recordSecurityLock.getNullValueBehavior()))
{ {
securityCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IS_BLANK)); lockCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IS_BLANK));
} }
else else
{ {
@ -458,7 +473,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
// else, if no user/session values, and null-value behavior is deny, then setup a FALSE condition, to allow no rows. // // else, if no user/session values, and null-value behavior is deny, then setup a FALSE condition, to allow no rows. //
// todo - make some explicit contradiction here - maybe even avoid running the whole query - as you're not allowed ANY records // // todo - make some explicit contradiction here - maybe even avoid running the whole query - as you're not allowed ANY records //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
securityCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IN, Collections.emptyList())); lockCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IN, Collections.emptyList()));
} }
} }
else else
@ -469,13 +484,26 @@ public abstract class AbstractRDBMSAction implements QActionInterface
////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(RecordSecurityLock.NullValueBehavior.ALLOW.equals(recordSecurityLock.getNullValueBehavior())) if(RecordSecurityLock.NullValueBehavior.ALLOW.equals(recordSecurityLock.getNullValueBehavior()))
{ {
securityCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IS_NULL_OR_IN, securityKeyValues)); lockCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IS_NULL_OR_IN, securityKeyValues));
} }
else else
{ {
securityCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IN, securityKeyValues)); lockCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IN, securityKeyValues));
} }
} }
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if this field is on the outer side of an outer join, then if we do a straight filter on it, then we're basically //
// nullifying the outer join... so for an outer join use-case, OR the security field criteria with a primary-key IS NULL //
// which will make missing rows from the join be found. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(isOuter)
{
lockFilter.setBooleanOperator(QQueryFilter.BooleanOperator.OR);
lockFilter.addCriteria(new QFilterCriteria(tableNameOrAlias + "." + table.getPrimaryKeyField(), QCriteriaOperator.IS_BLANK));
}
securityFilter.addSubFilter(lockFilter);
} }

View File

@ -1287,8 +1287,8 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
qInstance.getTable(TestUtils.TABLE_NAME_ORDER).getRecordSecurityLocks().clear(); qInstance.getTable(TestUtils.TABLE_NAME_ORDER).getRecordSecurityLocks().clear();
qInstance.getTable(TestUtils.TABLE_NAME_ORDER).withRecordSecurityLock(new RecordSecurityLock() qInstance.getTable(TestUtils.TABLE_NAME_ORDER).withRecordSecurityLock(new RecordSecurityLock()
.withSecurityKeyType(TestUtils.TABLE_NAME_STORE) .withSecurityKeyType(TestUtils.TABLE_NAME_STORE)
.withJoinChain(List.of("orderJoinStore")) .withJoinNameChain(List.of("orderJoinStore"))
.withFieldName("id")); .withFieldName("store.id"));
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.BETWEEN, List.of(2, 7)))); queryInput.setFilter(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.BETWEEN, List.of(2, 7))));
queryInput.setSession(new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_STORE_ALL_ACCESS, true)); queryInput.setSession(new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_STORE_ALL_ACCESS, true));

View File

@ -5,6 +5,11 @@
## Reset the version of all qqq deps in a given pom to the current snapshot. ## Reset the version of all qqq deps in a given pom to the current snapshot.
############################################################################ ############################################################################
if [ ! -e pom.xml ]; then
echo "Error: $0 Must be ran in a directory with a pom.xml"
exit 1;
fi
CURRENT_VERSION="$(cat $QQQ_DEV_TOOLS_DIR/CURRENT-SNAPSHOT-VERSION)" CURRENT_VERSION="$(cat $QQQ_DEV_TOOLS_DIR/CURRENT-SNAPSHOT-VERSION)"
MODULE_LIST_FILE=$QQQ_DEV_TOOLS_DIR/MODULE_LIST MODULE_LIST_FILE=$QQQ_DEV_TOOLS_DIR/MODULE_LIST

View File

@ -5,6 +5,11 @@
## Set the version of all qqq deps in a given pom to the latest snapshot. ## Set the version of all qqq deps in a given pom to the latest snapshot.
############################################################################ ############################################################################
if [ ! -e pom.xml ]; then
echo "Error: $0 Must be ran in a directory with a pom.xml"
exit 1;
fi
CURRENT_VERSION="$(cat $QQQ_DEV_TOOLS_DIR/CURRENT-SNAPSHOT-VERSION)" CURRENT_VERSION="$(cat $QQQ_DEV_TOOLS_DIR/CURRENT-SNAPSHOT-VERSION)"
MODULE_LIST_FILE=$QQQ_DEV_TOOLS_DIR/MODULE_LIST MODULE_LIST_FILE=$QQQ_DEV_TOOLS_DIR/MODULE_LIST