diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/permissions/PermissionsHelper.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/permissions/PermissionsHelper.java index f4c40993..500cea1b 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/permissions/PermissionsHelper.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/permissions/PermissionsHelper.java @@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.actions.permissions; import java.io.Serializable; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; 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' // //////////////////////////////////////////////////////////////////////// - return getPermissionCheckResult(actionInput, rules, permissionBaseName, PrivatePermissionSubType.HAS_ACCESS); + return getPermissionCheckResult(actionInput, rules, permissionBaseName, metaDataWithPermissionRules, PrivatePermissionSubType.HAS_ACCESS); } case READ_WRITE_PERMISSIONS: { @@ -130,9 +129,9 @@ public class PermissionsHelper //////////////////////////////////////////////////////////////// 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: { @@ -141,9 +140,9 @@ public class PermissionsHelper ////////////////////////////////////////////////////////////////////////// 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: { @@ -164,10 +163,6 @@ public class PermissionsHelper - static Map customPermissionCheckerMap = new HashMap<>(); - - - /******************************************************************************* ** *******************************************************************************/ @@ -181,12 +176,7 @@ public class PermissionsHelper ///////////////////////////////////// // todo - avoid stack overflows... // ///////////////////////////////////// - if(!customPermissionCheckerMap.containsKey(effectivePermissionRules.getCustomPermissionChecker().getName())) - { - CustomPermissionChecker customPermissionChecker = QCodeLoader.getAdHoc(CustomPermissionChecker.class, effectivePermissionRules.getCustomPermissionChecker()); - customPermissionCheckerMap.put(effectivePermissionRules.getCustomPermissionChecker().getName(), customPermissionChecker); - } - CustomPermissionChecker customPermissionChecker = customPermissionCheckerMap.get(effectivePermissionRules.getCustomPermissionChecker().getName()); + CustomPermissionChecker customPermissionChecker = QCodeLoader.getAdHoc(CustomPermissionChecker.class, effectivePermissionRules.getCustomPermissionChecker()); customPermissionChecker.checkPermissionsThrowing(actionInput, process); 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) { 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)) { return (PermissionCheckResult.ALLOW); @@ -526,7 +531,7 @@ public class PermissionsHelper 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.")); } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java index ae0611e9..cc600d00 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidator.java @@ -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.ValueTooLongBehavior; 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.QAppMetaData; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection; @@ -356,7 +357,6 @@ public class QInstanceValidator qInstance.getTables().forEach((tableName, table) -> { assertCondition(Objects.equals(tableName, table.getName()), "Inconsistent naming for table: " + tableName + "/" + table.getName() + "."); - validateAppChildHasValidParentAppName(qInstance, table); //////////////////////////////////////// // validate the backend for the table // @@ -465,10 +465,37 @@ public class QInstanceValidator prefix = "Table " + table.getName() + " recordSecurityLock (of key type " + securityKeyTypeName + ") "; - String fieldName = recordSecurityLock.getFieldName(); - if(assertCondition(StringUtils.hasContent(fieldName), prefix + "is missing a fieldName")) + boolean hasAnyBadJoins = false; + 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 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"); @@ -960,8 +987,6 @@ public class QInstanceValidator { assertCondition(Objects.equals(processName, process.getName()), "Inconsistent naming for process: " + processName + "/" + process.getName() + "."); - validateAppChildHasValidParentAppName(qInstance, process); - ///////////////////////////////////////////// // validate the table name for the process // ///////////////////////////////////////////// @@ -1015,7 +1040,6 @@ public class QInstanceValidator qInstance.getReports().forEach((reportName, report) -> { assertCondition(Objects.equals(reportName, report.getName()), "Inconsistent naming for report: " + reportName + "/" + report.getName() + "."); - validateAppChildHasValidParentAppName(qInstance, report); //////////////////////////////////////// // validate dataSources in the report // @@ -1172,6 +1196,7 @@ public class QInstanceValidator { joinTable.getField(fieldNameAfterDot); foundField = true; + break; } catch(Exception e2) { @@ -1216,7 +1241,10 @@ public class QInstanceValidator Set childNames = new HashSet<>(); 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()); 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) { diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/JoinsContext.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/JoinsContext.java index 852a00ac..d155e45e 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/JoinsContext.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/actions/tables/query/JoinsContext.java @@ -28,6 +28,7 @@ import java.util.Map; 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.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.tables.QTableMetaData; 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(String joinName : CollectionUtils.nonNullList(recordSecurityLock.getJoinChain())) + for(String joinName : CollectionUtils.nonNullList(recordSecurityLock.getJoinNameChain())) { if(this.queryJoins.stream().anyMatch(qj -> qj.getJoinMetaData().getName().equals(joinName))) { @@ -86,7 +87,12 @@ public class JoinsContext } 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. } } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/QJoinMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/QJoinMetaData.java index a63ceddb..70874938 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/QJoinMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/joins/QJoinMetaData.java @@ -300,10 +300,20 @@ public class QJoinMetaData *******************************************************************************/ 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")); } - return (withName(getLeftTable() + "Join" + StringUtils.ucFirst(getRightTable()))); + return (leftTable + "Join" + StringUtils.ucFirst(rightTable)); } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppChildMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppChildMetaData.java index 088aefc0..012ccc6c 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppChildMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppChildMetaData.java @@ -28,16 +28,6 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout; *******************************************************************************/ public interface QAppChildMetaData { - /******************************************************************************* - ** - *******************************************************************************/ - void setParentAppName(String parentAppName); - - /******************************************************************************* - ** - *******************************************************************************/ - String getParentAppName(); - /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java index fb1fb4e2..cfe3dc2c 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/layout/QAppMetaData.java @@ -169,7 +169,11 @@ public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRu this.children = new ArrayList<>(); } 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 ** *******************************************************************************/ - @Override public String getParentAppName() { return parentAppName; @@ -214,7 +217,6 @@ public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRu ** Setter for parentAppName ** *******************************************************************************/ - @Override public void setParentAppName(String parentAppName) { this.parentAppName = parentAppName; diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QProcessMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QProcessMetaData.java index e2a75aea..dd0ad81c 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QProcessMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/processes/QProcessMetaData.java @@ -54,8 +54,7 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi private List stepList; // these are the steps that are ran, by-default, in the order they are ran in private Map steps; // this is the full map of possible steps - private String parentAppName; - private QIcon icon; + private QIcon icon; 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 ** diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/reporting/QReportMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/reporting/QReportMetaData.java index 6d855cb5..e8740f29 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/reporting/QReportMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/reporting/QReportMetaData.java @@ -48,8 +48,7 @@ public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissio private List dataSources; private List 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 ** diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/security/RecordSecurityLock.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/security/RecordSecurityLock.java index cc08f098..8a66fae9 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/security/RecordSecurityLock.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/security/RecordSecurityLock.java @@ -34,7 +34,7 @@ public class RecordSecurityLock { private String securityKeyType; private String fieldName; - private List joinChain; // todo - add validation in validator!! + private List joinNameChain; // todo - add validation in validator!! private NullValueBehavior nullValueBehavior = NullValueBehavior.DENY; @@ -152,34 +152,34 @@ public class RecordSecurityLock } + /******************************************************************************* - ** Getter for joinChain + ** Getter for joinNameChain *******************************************************************************/ - public List getJoinChain() + public List getJoinNameChain() { - return (this.joinChain); + return (this.joinNameChain); } /******************************************************************************* - ** Setter for joinChain + ** Setter for joinNameChain *******************************************************************************/ - public void setJoinChain(List joinChain) + public void setJoinNameChain(List joinNameChain) { - this.joinChain = joinChain; + this.joinNameChain = joinNameChain; } /******************************************************************************* - ** Fluent setter for joinChain + ** Fluent setter for joinNameChain *******************************************************************************/ - public RecordSecurityLock withJoinChain(List joinChain) + public RecordSecurityLock withJoinNameChain(List joinNameChain) { - this.joinChain = joinChain; + this.joinNameChain = joinNameChain; return (this); } - } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java index a3aac0c9..e34998c0 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/tables/QTableMetaData.java @@ -80,8 +80,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData private Map customizers; - private String parentAppName; - private QIcon icon; + private QIcon icon; private String recordLabelFormat; private List recordLabelFields; @@ -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 ** diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java index 53f571aa..999e6989 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/metadata/MetaDataActionTest.java @@ -242,7 +242,7 @@ class MetaDataActionTest // 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("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("PersonsByCreateDateBarChart"), result.getWidgets().keySet()); @@ -276,7 +276,8 @@ class MetaDataActionTest MetaDataOutput result = new MetaDataAction().execute(input); 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.getWidgets().keySet()); @@ -322,7 +323,7 @@ class MetaDataActionTest MetaDataOutput result = new MetaDataAction().execute(input); 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.getWidgets().keySet()); diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java index c85f8174..df86f9f4 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/instances/QInstanceValidatorTest.java @@ -504,8 +504,6 @@ class QInstanceValidatorTest void testChildrenWithBadParentAppName() { 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); } diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java index 9fb14f37..14e854c0 100644 --- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java +++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/AbstractRDBMSAction.java @@ -310,11 +310,11 @@ public abstract class AbstractRDBMSAction implements QActionInterface { String whereClauseWithoutSecurity = makeWhereClauseWithoutSecurity(instance, table, joinsContext, filter, params); QQueryFilter securityFilter = getSecurityFilter(instance, session, table, joinsContext); - if(securityFilter == null || CollectionUtils.nullSafeIsEmpty(securityFilter.getCriteria())) + if(!securityFilter.hasAnyCriteria()) { 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 + ")"); } @@ -367,14 +367,14 @@ public abstract class AbstractRDBMSAction implements QActionInterface *******************************************************************************/ private QQueryFilter getSecurityFilter(QInstance instance, QSession session, QTableMetaData table, JoinsContext joinsContext) { - QQueryFilter newFilter = new QQueryFilter(); - newFilter.setBooleanOperator(QQueryFilter.BooleanOperator.AND); - List securityCriteria = new ArrayList<>(); - newFilter.setCriteria(securityCriteria); + QQueryFilter securityFilter = new QQueryFilter(); + securityFilter.setBooleanOperator(QQueryFilter.BooleanOperator.AND); 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())) @@ -382,11 +382,12 @@ public abstract class AbstractRDBMSAction implements QActionInterface QTableMetaData joinTable = instance.getTable(queryJoin.getJoinTable()); 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 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 // @@ -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); @@ -433,16 +437,27 @@ public abstract class AbstractRDBMSAction implements QActionInterface throw (new RuntimeException("Could not find joinMetaData for recordSecurityLock with joinChain member [" + joinName + "]")); } - table = instance.getTable(joinMetaData.getRightTable()); + if(fieldNameTablePrefix.equals(joinMetaData.getLeftTable())) + { + table = instance.getTable(joinMetaData.getLeftTable()); + } + else + { + table = instance.getTable(joinMetaData.getRightTable()); + } + tableNameOrAlias = table.getName(); + fieldName = tableNameOrAlias + "." + fieldNameWithoutTablePrefix; } } /////////////////////////////////////////////////////////////////////////////////////////// // else - get the key values from the session and decide what kind of criterion to build // /////////////////////////////////////////////////////////////////////////////////////////// - List securityKeyValues = session.getSecurityKeyValues(recordSecurityLock.getSecurityKeyType(), table.getField(recordSecurityLock.getFieldName()).getType()); - String fieldName = tableNameOrAlias + "." + recordSecurityLock.getFieldName(); + QQueryFilter lockFilter = new QQueryFilter(); + List lockCriteria = new ArrayList<>(); + lockFilter.setCriteria(lockCriteria); + List securityKeyValues = session.getSecurityKeyValues(recordSecurityLock.getSecurityKeyType(), table.getField(fieldNameWithoutTablePrefix).getType()); if(CollectionUtils.nullSafeIsEmpty(securityKeyValues)) { /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -450,7 +465,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if(RecordSecurityLock.NullValueBehavior.ALLOW.equals(recordSecurityLock.getNullValueBehavior())) { - securityCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IS_BLANK)); + lockCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IS_BLANK)); } 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. // // 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 @@ -469,13 +484,26 @@ public abstract class AbstractRDBMSAction implements QActionInterface ////////////////////////////////////////////////////////////////////////////////////////////////////////////// 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 { - 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); } diff --git a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java index 14db43a7..ecd18857 100644 --- a/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java +++ b/qqq-backend-module-rdbms/src/test/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryActionTest.java @@ -1287,8 +1287,8 @@ public class RDBMSQueryActionTest extends RDBMSActionTest qInstance.getTable(TestUtils.TABLE_NAME_ORDER).getRecordSecurityLocks().clear(); qInstance.getTable(TestUtils.TABLE_NAME_ORDER).withRecordSecurityLock(new RecordSecurityLock() .withSecurityKeyType(TestUtils.TABLE_NAME_STORE) - .withJoinChain(List.of("orderJoinStore")) - .withFieldName("id")); + .withJoinNameChain(List.of("orderJoinStore")) + .withFieldName("store.id")); 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)); diff --git a/qqq-dev-tools/bin/reset-snapshot-deps.sh b/qqq-dev-tools/bin/reset-snapshot-deps.sh index ddfe2aa1..b0d96f46 100755 --- a/qqq-dev-tools/bin/reset-snapshot-deps.sh +++ b/qqq-dev-tools/bin/reset-snapshot-deps.sh @@ -5,6 +5,11 @@ ## 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)" MODULE_LIST_FILE=$QQQ_DEV_TOOLS_DIR/MODULE_LIST diff --git a/qqq-dev-tools/bin/update-all-qqq-deps.sh b/qqq-dev-tools/bin/update-all-qqq-deps.sh index 4e19bcd4..baa73113 100755 --- a/qqq-dev-tools/bin/update-all-qqq-deps.sh +++ b/qqq-dev-tools/bin/update-all-qqq-deps.sh @@ -5,6 +5,11 @@ ## 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)" MODULE_LIST_FILE=$QQQ_DEV_TOOLS_DIR/MODULE_LIST