implementation of record security locks, and permissions

This commit is contained in:
2023-01-11 10:24:31 -06:00
parent e4d37e3db9
commit 23e9abeb74
83 changed files with 6639 additions and 504 deletions

View File

@ -27,6 +27,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionCheckResult;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
@ -40,6 +42,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMeta
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendWidgetMetaData;
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.permissions.MetaDataWithPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
@ -70,10 +73,18 @@ public class MetaDataAction
Map<String, QFrontendTableMetaData> tables = new LinkedHashMap<>();
for(Map.Entry<String, QTableMetaData> entry : metaDataInput.getInstance().getTables().entrySet())
{
String tableName = entry.getKey();
String tableName = entry.getKey();
QTableMetaData table = entry.getValue();
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, table);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{
continue;
}
QBackendMetaData backendForTable = metaDataInput.getInstance().getBackendForTable(tableName);
tables.put(tableName, new QFrontendTableMetaData(backendForTable, entry.getValue(), false));
treeNodes.put(tableName, new AppTreeNode(entry.getValue()));
tables.put(tableName, new QFrontendTableMetaData(metaDataInput, backendForTable, table, false));
treeNodes.put(tableName, new AppTreeNode(table));
}
metaDataOutput.setTables(tables);
@ -83,8 +94,17 @@ public class MetaDataAction
Map<String, QFrontendProcessMetaData> processes = new LinkedHashMap<>();
for(Map.Entry<String, QProcessMetaData> entry : metaDataInput.getInstance().getProcesses().entrySet())
{
processes.put(entry.getKey(), new QFrontendProcessMetaData(entry.getValue(), false));
treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue()));
String processName = entry.getKey();
QProcessMetaData process = entry.getValue();
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, process);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{
continue;
}
processes.put(processName, new QFrontendProcessMetaData(metaDataInput, process, false));
treeNodes.put(processName, new AppTreeNode(process));
}
metaDataOutput.setProcesses(processes);
@ -94,8 +114,17 @@ public class MetaDataAction
Map<String, QFrontendReportMetaData> reports = new LinkedHashMap<>();
for(Map.Entry<String, QReportMetaData> entry : metaDataInput.getInstance().getReports().entrySet())
{
reports.put(entry.getKey(), new QFrontendReportMetaData(entry.getValue(), false));
treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue()));
String reportName = entry.getKey();
QReportMetaData report = entry.getValue();
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, report);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{
continue;
}
reports.put(reportName, new QFrontendReportMetaData(metaDataInput, report, false));
treeNodes.put(reportName, new AppTreeNode(report));
}
metaDataOutput.setReports(reports);
@ -105,7 +134,16 @@ public class MetaDataAction
Map<String, QFrontendWidgetMetaData> widgets = new LinkedHashMap<>();
for(Map.Entry<String, QWidgetMetaDataInterface> entry : metaDataInput.getInstance().getWidgets().entrySet())
{
widgets.put(entry.getKey(), new QFrontendWidgetMetaData(entry.getValue()));
String widgetName = entry.getKey();
QWidgetMetaDataInterface widget = entry.getValue();
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, widget);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{
continue;
}
widgets.put(widgetName, new QFrontendWidgetMetaData(metaDataInput, widget));
}
metaDataOutput.setWidgets(widgets);
@ -115,14 +153,32 @@ public class MetaDataAction
Map<String, QFrontendAppMetaData> apps = new LinkedHashMap<>();
for(Map.Entry<String, QAppMetaData> entry : metaDataInput.getInstance().getApps().entrySet())
{
apps.put(entry.getKey(), new QFrontendAppMetaData(entry.getValue()));
treeNodes.put(entry.getKey(), new AppTreeNode(entry.getValue()));
String appName = entry.getKey();
QAppMetaData app = entry.getValue();
if(CollectionUtils.nullSafeHasContents(entry.getValue().getChildren()))
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, app);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{
for(QAppChildMetaData child : entry.getValue().getChildren())
continue;
}
apps.put(appName, new QFrontendAppMetaData(app, metaDataOutput));
treeNodes.put(appName, new AppTreeNode(app));
if(CollectionUtils.nullSafeHasContents(app.getChildren()))
{
for(QAppChildMetaData child : app.getChildren())
{
apps.get(entry.getKey()).addChild(new AppTreeNode(child));
if(child instanceof MetaDataWithPermissionRules metaDataWithPermissionRules)
{
PermissionCheckResult childPermissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, metaDataWithPermissionRules);
if(childPermissionResult.equals(PermissionCheckResult.DENY_HIDE))
{
continue;
}
}
apps.get(appName).addChild(new AppTreeNode(child));
}
}
}
@ -136,7 +192,7 @@ public class MetaDataAction
{
if(appMetaData.getParentAppName() == null)
{
buildAppTree(treeNodes, appTree, appMetaData);
buildAppTree(metaDataInput, treeNodes, appTree, appMetaData);
}
}
metaDataOutput.setAppTree(appTree);
@ -161,7 +217,7 @@ public class MetaDataAction
/*******************************************************************************
**
*******************************************************************************/
private void buildAppTree(Map<String, AppTreeNode> treeNodes, List<AppTreeNode> nodeList, QAppChildMetaData childMetaData)
private void buildAppTree(MetaDataInput metaDataInput, Map<String, AppTreeNode> treeNodes, List<AppTreeNode> nodeList, QAppChildMetaData childMetaData)
{
AppTreeNode treeNode = treeNodes.get(childMetaData.getName());
if(treeNode == null)
@ -176,7 +232,16 @@ public class MetaDataAction
{
for(QAppChildMetaData child : app.getChildren())
{
buildAppTree(treeNodes, treeNode.getChildren(), child);
if(child instanceof MetaDataWithPermissionRules metaDataWithPermissionRules)
{
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, metaDataWithPermissionRules);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{
continue;
}
}
buildAppTree(metaDataInput, treeNodes, treeNode.getChildren(), child);
}
}
}

View File

@ -52,7 +52,7 @@ public class ProcessMetaDataAction
{
throw (new QNotFoundException("Process [" + processMetaDataInput.getProcessName() + "] was not found."));
}
processMetaDataOutput.setProcess(new QFrontendProcessMetaData(process, true));
processMetaDataOutput.setProcess(new QFrontendProcessMetaData(processMetaDataInput, process, true));
// todo post-customization - can do whatever w/ the result if you want

View File

@ -54,7 +54,7 @@ public class TableMetaDataAction
throw (new QNotFoundException("Table [" + tableMetaDataInput.getTableName() + "] was not found."));
}
QBackendMetaData backendForTable = tableMetaDataInput.getInstance().getBackendForTable(table.getName());
tableMetaDataOutput.setTable(new QFrontendTableMetaData(backendForTable, table, true));
tableMetaDataOutput.setTable(new QFrontendTableMetaData(tableMetaDataInput, backendForTable, table, true));
// todo post-customization - can do whatever w/ the result if you want

View File

@ -0,0 +1,204 @@
/*
* 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.permissions;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
**
*******************************************************************************/
public class AvailablePermission extends QRecordEntity
{
public static final String TABLE_NAME = "availablePermission";
@QField(label = "Permission Name")
private String name;
@QField(label = "Object")
private String objectName;
@QField()
private String objectType;
@QField()
private String permissionType;
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(o == null || getClass() != o.getClass())
{
return false;
}
AvailablePermission that = (AvailablePermission) o;
return Objects.equals(name, that.name) && Objects.equals(objectName, that.objectName) && Objects.equals(objectType, that.objectType) && Objects.equals(permissionType, that.permissionType);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public int hashCode()
{
return Objects.hash(name, objectName, objectType, permissionType);
}
/*******************************************************************************
** Getter for name
*******************************************************************************/
public String getName()
{
return (this.name);
}
/*******************************************************************************
** Setter for name
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Fluent setter for name
*******************************************************************************/
public AvailablePermission withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Getter for objectType
*******************************************************************************/
public String getObjectType()
{
return (this.objectType);
}
/*******************************************************************************
** Setter for objectType
*******************************************************************************/
public void setObjectType(String objectType)
{
this.objectType = objectType;
}
/*******************************************************************************
** Fluent setter for objectType
*******************************************************************************/
public AvailablePermission withObjectType(String objectType)
{
this.objectType = objectType;
return (this);
}
/*******************************************************************************
** Getter for permissionType
*******************************************************************************/
public String getPermissionType()
{
return (this.permissionType);
}
/*******************************************************************************
** Setter for permissionType
*******************************************************************************/
public void setPermissionType(String permissionType)
{
this.permissionType = permissionType;
}
/*******************************************************************************
** Fluent setter for permissionType
*******************************************************************************/
public AvailablePermission withPermissionType(String permissionType)
{
this.permissionType = permissionType;
return (this);
}
/*******************************************************************************
** Getter for objectName
*******************************************************************************/
public String getObjectName()
{
return (this.objectName);
}
/*******************************************************************************
** Setter for objectName
*******************************************************************************/
public void setObjectName(String objectName)
{
this.objectName = objectName;
}
/*******************************************************************************
** Fluent setter for objectName
*******************************************************************************/
public AvailablePermission withObjectName(String objectName)
{
this.objectName = objectName;
return (this);
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.permissions;
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
**
*******************************************************************************/
public class BulkTableActionProcessPermissionChecker implements CustomPermissionChecker
{
private static final Logger LOG = LogManager.getLogger(BulkTableActionProcessPermissionChecker.class);
/*******************************************************************************
**
*******************************************************************************/
@Override
public void checkPermissionsThrowing(AbstractActionInput actionInput, MetaDataWithPermissionRules metaDataWithPermissionRules) throws QPermissionDeniedException
{
String processName = metaDataWithPermissionRules.getName();
if(processName != null && processName.indexOf('.') > -1)
{
String[] parts = processName.split("\\.", 2);
String tableName = parts[0];
String bulkActionName = parts[1];
AbstractTableActionInput tableActionInput = new AbstractTableActionInput(actionInput.getInstance());
tableActionInput.setSession(actionInput.getSession());
tableActionInput.setTableName(tableName);
switch(bulkActionName)
{
case "bulkInsert" -> PermissionsHelper.checkTablePermissionThrowing(tableActionInput, TablePermissionSubType.INSERT);
case "bulkEdit" -> PermissionsHelper.checkTablePermissionThrowing(tableActionInput, TablePermissionSubType.EDIT);
case "bulkDelete" -> PermissionsHelper.checkTablePermissionThrowing(tableActionInput, TablePermissionSubType.DELETE);
default -> LOG.warn("Unexpected bulk action name when checking permissions for process: " + processName);
}
}
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.permissions;
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
/*******************************************************************************
**
*******************************************************************************/
public interface CustomPermissionChecker
{
/*******************************************************************************
**
*******************************************************************************/
void checkPermissionsThrowing(AbstractActionInput actionInput, MetaDataWithPermissionRules metaDataWithPermissionRules) throws QPermissionDeniedException;
}

View File

@ -0,0 +1,33 @@
/*
* 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.permissions;
/*******************************************************************************
**
*******************************************************************************/
public enum PermissionCheckResult
{
ALLOW,
DENY_HIDE,
DENY_DISABLE;
}

View File

@ -0,0 +1,36 @@
/*
* 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.permissions;
/*******************************************************************************
**
*******************************************************************************/
sealed interface PermissionSubType permits PrivatePermissionSubType, TablePermissionSubType
{
/*******************************************************************************
**
*******************************************************************************/
String getPermissionSuffix();
}

View File

@ -0,0 +1,579 @@
/*
* 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.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;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.DenyBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithName;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/*******************************************************************************
**
*******************************************************************************/
public class PermissionsHelper
{
private static final Logger LOG = LogManager.getLogger(PermissionsHelper.class);
/*******************************************************************************
**
*******************************************************************************/
public static void checkTablePermissionThrowing(AbstractTableActionInput tableActionInput, TablePermissionSubType permissionSubType) throws QPermissionDeniedException
{
checkTablePermissionThrowing(tableActionInput, tableActionInput.getTableName(), permissionSubType);
}
/*******************************************************************************
**
*******************************************************************************/
private static void checkTablePermissionThrowing(AbstractActionInput actionInput, String tableName, TablePermissionSubType permissionSubType) throws QPermissionDeniedException
{
warnAboutPermissionSubTypeForTables(permissionSubType);
QTableMetaData table = actionInput.getInstance().getTable(tableName);
commonCheckPermissionThrowing(getEffectivePermissionRules(table, actionInput.getInstance()), permissionSubType, table.getName(), actionInput);
}
/*******************************************************************************
**
*******************************************************************************/
public static boolean hasTablePermission(AbstractActionInput actionInput, String tableName, TablePermissionSubType permissionSubType)
{
try
{
checkTablePermissionThrowing(actionInput, tableName, permissionSubType);
return (true);
}
catch(QPermissionDeniedException e)
{
return (false);
}
}
/*******************************************************************************
**
*******************************************************************************/
public static PermissionCheckResult getPermissionCheckResult(AbstractActionInput actionInput, MetaDataWithPermissionRules metaDataWithPermissionRules)
{
QPermissionRules rules = getEffectivePermissionRules(metaDataWithPermissionRules, actionInput.getInstance());
String permissionBaseName = getEffectivePermissionBaseName(rules, metaDataWithPermissionRules.getName());
switch(rules.getLevel())
{
case NOT_PROTECTED:
{
/////////////////////////////////////////////////
// if the entity isn't protected, always ALLOW //
/////////////////////////////////////////////////
return PermissionCheckResult.ALLOW;
}
case HAS_ACCESS_PERMISSION:
{
////////////////////////////////////////////////////////////////////////
// if the entity just has a 'has access', then check for 'has access' //
////////////////////////////////////////////////////////////////////////
return getPermissionCheckResult(actionInput, rules, permissionBaseName, PrivatePermissionSubType.HAS_ACCESS);
}
case READ_WRITE_PERMISSIONS:
{
////////////////////////////////////////////////////////////////
// if the table is configured w/ read/write, check for either //
////////////////////////////////////////////////////////////////
if(metaDataWithPermissionRules instanceof QTableMetaData)
{
return getPermissionCheckResult(actionInput, rules, permissionBaseName, PrivatePermissionSubType.READ, PrivatePermissionSubType.WRITE);
}
return getPermissionCheckResult(actionInput, rules, permissionBaseName, PrivatePermissionSubType.HAS_ACCESS);
}
case READ_INSERT_EDIT_DELETE_PERMISSIONS:
{
//////////////////////////////////////////////////////////////////////////
// if the table is configured w/ read/insert/edit/delete, check for any //
//////////////////////////////////////////////////////////////////////////
if(metaDataWithPermissionRules instanceof QTableMetaData)
{
return getPermissionCheckResult(actionInput, rules, permissionBaseName, TablePermissionSubType.READ, TablePermissionSubType.INSERT, TablePermissionSubType.EDIT, TablePermissionSubType.DELETE);
}
return getPermissionCheckResult(actionInput, rules, permissionBaseName, PrivatePermissionSubType.HAS_ACCESS);
}
default:
{
return getPermissionDeniedCheckResult(rules);
}
}
}
/*******************************************************************************
**
*******************************************************************************/
public static void checkProcessPermissionThrowing(AbstractActionInput actionInput, String processName) throws QPermissionDeniedException
{
checkProcessPermissionThrowing(actionInput, processName, Collections.emptyMap());
}
static Map<String, CustomPermissionChecker> customPermissionCheckerMap = new HashMap<>();
/*******************************************************************************
**
*******************************************************************************/
public static void checkProcessPermissionThrowing(AbstractActionInput actionInput, String processName, Map<String, Serializable> processValues) throws QPermissionDeniedException
{
QProcessMetaData process = actionInput.getInstance().getProcess(processName);
QPermissionRules effectivePermissionRules = getEffectivePermissionRules(process, actionInput.getInstance());
if(effectivePermissionRules.getCustomPermissionChecker() != null)
{
/////////////////////////////////////
// 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.checkPermissionsThrowing(actionInput, process);
return;
}
commonCheckPermissionThrowing(effectivePermissionRules, PrivatePermissionSubType.HAS_ACCESS, process.getName(), actionInput);
}
/*******************************************************************************
**
*******************************************************************************/
public static boolean hasProcessPermission(AbstractActionInput actionInput, String processName)
{
try
{
checkProcessPermissionThrowing(actionInput, processName);
return (true);
}
catch(QPermissionDeniedException e)
{
return (false);
}
}
/*******************************************************************************
**
*******************************************************************************/
public static void checkAppPermissionThrowing(AbstractActionInput actionInput, String appName) throws QPermissionDeniedException
{
QAppMetaData app = actionInput.getInstance().getApp(appName);
commonCheckPermissionThrowing(getEffectivePermissionRules(app, actionInput.getInstance()), PrivatePermissionSubType.HAS_ACCESS, app.getName(), actionInput);
}
/*******************************************************************************
**
*******************************************************************************/
public static boolean hasAppPermission(AbstractActionInput actionInput, String appName)
{
try
{
checkAppPermissionThrowing(actionInput, appName);
return (true);
}
catch(QPermissionDeniedException e)
{
return (false);
}
}
/*******************************************************************************
**
*******************************************************************************/
public static void checkReportPermissionThrowing(AbstractActionInput actionInput, String reportName) throws QPermissionDeniedException
{
QReportMetaData report = actionInput.getInstance().getReport(reportName);
commonCheckPermissionThrowing(getEffectivePermissionRules(report, actionInput.getInstance()), PrivatePermissionSubType.HAS_ACCESS, report.getName(), actionInput);
}
/*******************************************************************************
**
*******************************************************************************/
public static boolean hasReportPermission(AbstractActionInput actionInput, String reportName)
{
try
{
checkReportPermissionThrowing(actionInput, reportName);
return (true);
}
catch(QPermissionDeniedException e)
{
return (false);
}
}
/*******************************************************************************
**
*******************************************************************************/
public static void checkWidgetPermissionThrowing(AbstractActionInput actionInput, String widgetName) throws QPermissionDeniedException
{
QWidgetMetaDataInterface widget = actionInput.getInstance().getWidget(widgetName);
commonCheckPermissionThrowing(getEffectivePermissionRules(widget, actionInput.getInstance()), PrivatePermissionSubType.HAS_ACCESS, widget.getName(), actionInput);
}
/*******************************************************************************
**
*******************************************************************************/
public static boolean hasWidgetPermission(AbstractActionInput actionInput, String widgetName)
{
try
{
checkWidgetPermissionThrowing(actionInput, widgetName);
return (true);
}
catch(QPermissionDeniedException e)
{
return (false);
}
}
/*******************************************************************************
**
*******************************************************************************/
public static Collection<String> getAllAvailablePermissionNames(QInstance instance)
{
return (getAllAvailablePermissions(instance).stream()
.map(AvailablePermission::getName)
.collect(Collectors.toCollection(LinkedHashSet::new)));
}
/*******************************************************************************
**
*******************************************************************************/
public static Collection<AvailablePermission> getAllAvailablePermissions(QInstance instance)
{
Collection<AvailablePermission> rs = new LinkedHashSet<>();
for(QTableMetaData tableMetaData : instance.getTables().values())
{
if(tableMetaData.getIsHidden())
{
continue;
}
QPermissionRules rules = getEffectivePermissionRules(tableMetaData, instance);
String baseName = getEffectivePermissionBaseName(rules, tableMetaData.getName());
for(TablePermissionSubType permissionSubType : TablePermissionSubType.values())
{
addEffectiveAvailablePermission(rules, permissionSubType, rs, baseName, tableMetaData, "Table");
}
}
for(QProcessMetaData processMetaData : instance.getProcesses().values())
{
if(processMetaData.getIsHidden())
{
continue;
}
QPermissionRules rules = getEffectivePermissionRules(processMetaData, instance);
String baseName = getEffectivePermissionBaseName(rules, processMetaData.getName());
addEffectiveAvailablePermission(rules, PrivatePermissionSubType.HAS_ACCESS, rs, baseName, processMetaData, "Process");
}
for(QAppMetaData appMetaData : instance.getApps().values())
{
QPermissionRules rules = getEffectivePermissionRules(appMetaData, instance);
String baseName = getEffectivePermissionBaseName(rules, appMetaData.getName());
addEffectiveAvailablePermission(rules, PrivatePermissionSubType.HAS_ACCESS, rs, baseName, appMetaData, "App");
}
for(QReportMetaData reportMetaData : instance.getReports().values())
{
QPermissionRules rules = getEffectivePermissionRules(reportMetaData, instance);
String baseName = getEffectivePermissionBaseName(rules, reportMetaData.getName());
addEffectiveAvailablePermission(rules, PrivatePermissionSubType.HAS_ACCESS, rs, baseName, reportMetaData, "Report");
}
for(QWidgetMetaDataInterface widgetMetaData : instance.getWidgets().values())
{
QPermissionRules rules = getEffectivePermissionRules(widgetMetaData, instance);
String baseName = getEffectivePermissionBaseName(rules, widgetMetaData.getName());
addEffectiveAvailablePermission(rules, PrivatePermissionSubType.HAS_ACCESS, rs, baseName, widgetMetaData, "Widget");
}
return (rs);
}
/*******************************************************************************
**
*******************************************************************************/
private static void addEffectiveAvailablePermission(QPermissionRules rules, PermissionSubType permissionSubType, Collection<AvailablePermission> rs, String baseName, MetaDataWithName metaDataWithName, String objectType)
{
PermissionSubType effectivePermissionSubType = getEffectivePermissionSubType(rules, permissionSubType);
if(effectivePermissionSubType != null)
{
rs.add(new AvailablePermission()
.withName(getPermissionName(baseName, effectivePermissionSubType))
.withObjectName(metaDataWithName.getLabel())
.withPermissionType(effectivePermissionSubType.toString())
.withObjectType(objectType));
}
}
/*******************************************************************************
**
*******************************************************************************/
public static QPermissionRules getEffectivePermissionRules(MetaDataWithPermissionRules metaDataWithPermissionRules, QInstance instance)
{
return (metaDataWithPermissionRules.getPermissionRules());
}
/*******************************************************************************
**
*******************************************************************************/
static boolean hasPermission(QSession session, String permissionBaseName, PermissionSubType permissionSubType)
{
if(permissionSubType == null)
{
return (true);
}
String permissionName = getPermissionName(permissionBaseName, permissionSubType);
return (session.hasPermission(permissionName));
}
/*******************************************************************************
**
*******************************************************************************/
static PermissionCheckResult getPermissionCheckResult(AbstractActionInput actionInput, QPermissionRules rules, String permissionBaseName, PermissionSubType... permissionSubTypes)
{
for(PermissionSubType permissionSubType : permissionSubTypes)
{
PermissionSubType effectivePermissionSubType = getEffectivePermissionSubType(rules, permissionSubType);
if(hasPermission(actionInput.getSession(), permissionBaseName, effectivePermissionSubType))
{
return (PermissionCheckResult.ALLOW);
}
}
return (getPermissionDeniedCheckResult(rules));
}
/*******************************************************************************
**
*******************************************************************************/
static String getEffectivePermissionBaseName(QPermissionRules rules, String standardName)
{
if(rules != null && StringUtils.hasContent(rules.getPermissionBaseName()))
{
return (rules.getPermissionBaseName());
}
return (standardName);
}
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("checkstyle:indentation")
static PermissionSubType getEffectivePermissionSubType(QPermissionRules rules, PermissionSubType originalPermissionSubType)
{
if(rules == null || rules.getLevel() == null)
{
return (originalPermissionSubType);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if the original permission sub-type is "hasAccess" - then this is a check for a process/report/widget. //
// in that case - never return the table-level read/write/insert/edit/delete options //
////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(PrivatePermissionSubType.HAS_ACCESS.equals(originalPermissionSubType))
{
return switch(rules.getLevel())
{
case NOT_PROTECTED -> null;
default -> PrivatePermissionSubType.HAS_ACCESS;
};
}
else
{
////////////////////////////////////////////////////////////////////////////////////////////////////////
// else, this is a table check - so - based on the rules being used for this table, map the requested //
// permission sub-type to what we expect to be set for the table //
////////////////////////////////////////////////////////////////////////////////////////////////////////
return switch(rules.getLevel())
{
case NOT_PROTECTED -> null;
case HAS_ACCESS_PERMISSION -> PrivatePermissionSubType.HAS_ACCESS;
case READ_WRITE_PERMISSIONS ->
{
if(PrivatePermissionSubType.READ.equals(originalPermissionSubType) || PrivatePermissionSubType.WRITE.equals(originalPermissionSubType))
{
yield (originalPermissionSubType);
}
else if(TablePermissionSubType.INSERT.equals(originalPermissionSubType) || TablePermissionSubType.EDIT.equals(originalPermissionSubType) || TablePermissionSubType.DELETE.equals(originalPermissionSubType))
{
yield (PrivatePermissionSubType.WRITE);
}
else if(TablePermissionSubType.READ.equals(originalPermissionSubType))
{
yield (PrivatePermissionSubType.READ);
}
else
{
throw new IllegalStateException("Unexpected permissionSubType: " + originalPermissionSubType);
}
}
case READ_INSERT_EDIT_DELETE_PERMISSIONS -> originalPermissionSubType;
};
}
}
/*******************************************************************************
**
*******************************************************************************/
private static void commonCheckPermissionThrowing(QPermissionRules rules, PermissionSubType permissionSubType, String name, AbstractActionInput actionInput) throws QPermissionDeniedException
{
PermissionSubType effectivePermissionSubType = getEffectivePermissionSubType(rules, permissionSubType);
String permissionBaseName = getEffectivePermissionBaseName(rules, name);
if(effectivePermissionSubType == null)
{
return;
}
if(!hasPermission(actionInput.getSession(), permissionBaseName, effectivePermissionSubType))
{
LOG.debug("Throwing permission denied for: " + getPermissionName(permissionBaseName, effectivePermissionSubType) + " for " + actionInput.getSession().getUser());
throw (new QPermissionDeniedException("Permission denied."));
}
}
/*******************************************************************************
**
*******************************************************************************/
private static String getPermissionName(String permissionBaseName, PermissionSubType permissionSubType)
{
return permissionBaseName + "." + permissionSubType.getPermissionSuffix();
}
/*******************************************************************************
**
*******************************************************************************/
private static PermissionCheckResult getPermissionDeniedCheckResult(QPermissionRules rules)
{
if(rules == null || rules.getDenyBehavior() == null || rules.getDenyBehavior().equals(DenyBehavior.HIDDEN))
{
return (PermissionCheckResult.DENY_HIDE);
}
else
{
return (PermissionCheckResult.DENY_DISABLE);
}
}
/*******************************************************************************
**
*******************************************************************************/
private static void warnAboutPermissionSubTypeForTables(PermissionSubType permissionSubType)
{
if(permissionSubType == null)
{
return;
}
if(permissionSubType == PrivatePermissionSubType.HAS_ACCESS)
{
LOG.warn("PermissionSubType.HAS_ACCESS should not be checked for a table");
}
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.permissions;
/*******************************************************************************
**
*******************************************************************************/
enum PrivatePermissionSubType implements PermissionSubType
{
HAS_ACCESS("hasAccess"), // for processes, reports, etc - basically, not tables.
READ("read"), // for a table in read/write mode - or - for read (query, get, count) on a table in full-mode
WRITE("write");
private final String permissionSuffix;
/*******************************************************************************
**
*******************************************************************************/
PrivatePermissionSubType(String permissionSuffix)
{
this.permissionSuffix = permissionSuffix;
}
/*******************************************************************************
** Getter for permissionSuffix
*******************************************************************************/
public String getPermissionSuffix()
{
return (this.permissionSuffix);
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.permissions;
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
/*******************************************************************************
**
*******************************************************************************/
public class ReportProcessPermissionChecker implements CustomPermissionChecker
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public void checkPermissionsThrowing(AbstractActionInput actionInput, MetaDataWithPermissionRules metaDataWithPermissionRules) throws QPermissionDeniedException
{
if(actionInput instanceof RunProcessInput runProcessInput)
{
String reportName = runProcessInput.getValueString("reportName");
if(reportName != null)
{
PermissionsHelper.checkReportPermissionThrowing(actionInput, reportName);
}
}
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.permissions;
/*******************************************************************************
**
*******************************************************************************/
public enum TablePermissionSubType implements PermissionSubType
{
READ("read"), // for a table in read/write mode - or - for read (query, get, count) on a table in full-mode
INSERT("insert"), // for table-insert.
EDIT("edit"), // for table-edit.
DELETE("delete"); // for table-delete.
private final String permissionSuffix;
/*******************************************************************************
**
*******************************************************************************/
TablePermissionSubType(String permissionSuffix)
{
this.permissionSuffix = permissionSuffix;
}
/*******************************************************************************
** Getter for permissionSuffix
*******************************************************************************/
public String getPermissionSuffix()
{
return (this.permissionSuffix);
}
}

View File

@ -0,0 +1,51 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. 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.exceptions;
/*******************************************************************************
* Exception thrown if user doesn't have permission for an action
*
*******************************************************************************/
public class QPermissionDeniedException extends QException
{
/*******************************************************************************
** Constructor of message
**
*******************************************************************************/
public QPermissionDeniedException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor of message & cause
**
*******************************************************************************/
public QPermissionDeniedException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@ -32,8 +32,12 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.permissions.BulkTableActionProcessPermissionChecker;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
@ -42,6 +46,8 @@ 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;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
@ -133,6 +139,21 @@ public class QInstanceEnricher
{
qInstance.getPossibleValueSources().values().forEach(this::enrichPossibleValueSource);
}
if(qInstance.getWidgets() != null)
{
qInstance.getWidgets().values().forEach(this::enrichWidget);
}
}
/*******************************************************************************
**
*******************************************************************************/
private void enrichWidget(QWidgetMetaDataInterface widgetMetaData)
{
enrichPermissionRules(widgetMetaData);
}
@ -175,6 +196,60 @@ public class QInstanceEnricher
{
table.setRecordLabelFormat(String.join(" ", Collections.nCopies(table.getRecordLabelFields().size(), "%s")));
}
enrichPermissionRules(table);
}
/*******************************************************************************
**
*******************************************************************************/
private void enrichPermissionRules(MetaDataWithPermissionRules metaDataWithPermissionRules)
{
///////////////////////////////////////////////////////////////////////
// make sure there's a permissionsRule object in the metaData object //
///////////////////////////////////////////////////////////////////////
if(metaDataWithPermissionRules.getPermissionRules() == null)
{
if(qInstance.getDefaultPermissionRules() != null)
{
metaDataWithPermissionRules.setPermissionRules(qInstance.getDefaultPermissionRules().clone());
}
else
{
metaDataWithPermissionRules.setPermissionRules(QPermissionRules.defaultInstance().clone());
}
}
QPermissionRules permissionRules = metaDataWithPermissionRules.getPermissionRules();
/////////////////////////////////////////////////////////////////////////////////
// now make sure the required fields are all set in the permissionRules object //
/////////////////////////////////////////////////////////////////////////////////
if(permissionRules.getLevel() == null)
{
if(qInstance.getDefaultPermissionRules() != null && qInstance.getDefaultPermissionRules().getLevel() != null)
{
permissionRules.setLevel(qInstance.getDefaultPermissionRules().getLevel());
}
else
{
permissionRules.setLevel(QPermissionRules.defaultInstance().getLevel());
}
}
if(permissionRules.getDenyBehavior() == null)
{
if(qInstance.getDefaultPermissionRules() != null && qInstance.getDefaultPermissionRules().getDenyBehavior() != null)
{
permissionRules.setDenyBehavior(qInstance.getDefaultPermissionRules().getDenyBehavior());
}
else
{
permissionRules.setDenyBehavior(QPermissionRules.defaultInstance().getDenyBehavior());
}
}
}
@ -193,6 +268,8 @@ public class QInstanceEnricher
{
process.getStepList().forEach(this::enrichStep);
}
enrichPermissionRules(process);
}
@ -323,6 +400,8 @@ public class QInstanceEnricher
{
enrichAppSection(section);
}
enrichPermissionRules(app);
}
@ -411,6 +490,8 @@ public class QInstanceEnricher
}
}
}
enrichPermissionRules(report);
}
@ -506,7 +587,9 @@ public class QInstanceEnricher
.withName(processName)
.withLabel(table.getLabel() + " Bulk Insert")
.withTableName(table.getName())
.withIsHidden(true);
.withIsHidden(true)
.withPermissionRules(qInstance.getDefaultPermissionRules().clone()
.withCustomPermissionChecker(new QCodeReference(BulkTableActionProcessPermissionChecker.class, QCodeUsage.CUSTOMIZER)));
List<QFieldMetaData> editableFields = new ArrayList<>();
for(QFieldSection section : CollectionUtils.nonNullList(table.getSections()))
@ -568,7 +651,9 @@ public class QInstanceEnricher
.withName(processName)
.withLabel(table.getLabel() + " Bulk Edit")
.withTableName(table.getName())
.withIsHidden(true);
.withIsHidden(true)
.withPermissionRules(qInstance.getDefaultPermissionRules().clone()
.withCustomPermissionChecker(new QCodeReference(BulkTableActionProcessPermissionChecker.class, QCodeUsage.CUSTOMIZER)));
List<QFieldMetaData> editableFields = table.getFields().values().stream()
.filter(QFieldMetaData::getIsEditable)
@ -613,7 +698,9 @@ public class QInstanceEnricher
.withName(processName)
.withLabel(table.getLabel() + " Bulk Delete")
.withTableName(table.getName())
.withIsHidden(true);
.withIsHidden(true)
.withPermissionRules(qInstance.getDefaultPermissionRules().clone()
.withCustomPermissionChecker(new QCodeReference(BulkTableActionProcessPermissionChecker.class, QCodeUsage.CUSTOMIZER)));
List<QFieldMetaData> tableFields = table.getFields().values().stream().toList();
process.getFrontendStep("review").setRecordListFields(tableFields);

View File

@ -61,6 +61,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMeta
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
@ -138,6 +140,7 @@ public class QInstanceValidator
validatePossibleValueSources(qInstance);
validateQueuesAndProviders(qInstance);
validateJoins(qInstance);
validateSecurityKeyTypes(qInstance);
validateUniqueTopLevelNames(qInstance);
}
@ -156,6 +159,35 @@ public class QInstanceValidator
/*******************************************************************************
**
*******************************************************************************/
private void validateSecurityKeyTypes(QInstance qInstance)
{
Set<String> usedNames = new HashSet<>();
qInstance.getSecurityKeyTypes().forEach((name, securityKeyType) ->
{
if(assertCondition(StringUtils.hasContent(securityKeyType.getName()), "Missing name for a securityKeyType"))
{
assertCondition(Objects.equals(name, securityKeyType.getName()), "Inconsistent naming for securityKeyType: " + name + "/" + securityKeyType.getName() + ".");
assertCondition(!usedNames.contains(name), "More than one SecurityKeyType with name (or allAccessKeyName) of: " + name);
usedNames.add(name);
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
{
assertCondition(!usedNames.contains(securityKeyType.getAllAccessKeyName()), "More than one SecurityKeyType with name (or allAccessKeyName) of: " + securityKeyType.getAllAccessKeyName());
usedNames.add(securityKeyType.getAllAccessKeyName());
}
if(StringUtils.hasContent(securityKeyType.getPossibleValueSourceName()))
{
assertCondition(qInstance.getPossibleValueSource(securityKeyType.getPossibleValueSourceName()) != null, "Unrecognized possibleValueSourceName in securityKeyType: " + name);
}
}
});
}
/*******************************************************************************
**
*******************************************************************************/
@ -387,17 +419,11 @@ public class QInstanceValidator
}
}
if(CollectionUtils.nullSafeHasContents(table.getFields()))
for(String fieldName : CollectionUtils.nonNullMap(table.getFields()).keySet())
{
for(String fieldName : table.getFields().keySet())
{
assertCondition(fieldNamesInSections.contains(fieldName), "Table " + tableName + " field " + fieldName + " is not listed in any field sections.");
}
assertCondition(fieldNamesInSections.contains(fieldName), "Table " + tableName + " field " + fieldName + " is not listed in any field sections.");
}
///////////////////////////////
// validate the record label //
///////////////////////////////
if(table.getRecordLabelFields() != null && table.getFields() != null)
{
for(String recordLabelField : table.getRecordLabelFields())
@ -406,51 +432,51 @@ public class QInstanceValidator
}
}
if(table.getCustomizers() != null)
for(Map.Entry<String, QCodeReference> entry : CollectionUtils.nonNullMap(table.getCustomizers()).entrySet())
{
for(Map.Entry<String, QCodeReference> entry : table.getCustomizers().entrySet())
{
validateTableCustomizer(tableName, entry.getKey(), entry.getValue());
}
validateTableCustomizer(tableName, entry.getKey(), entry.getValue());
}
//////////////////////////////////////
// validate the table's automations //
//////////////////////////////////////
if(table.getAutomationDetails() != null)
{
validateTableAutomationDetails(qInstance, table);
}
//////////////////////////////////////
// validate the table's unique keys //
//////////////////////////////////////
if(table.getUniqueKeys() != null)
{
validateTableUniqueKeys(table);
}
/////////////////////////////////////////////
// validate the table's associated scripts //
/////////////////////////////////////////////
if(table.getAssociatedScripts() != null)
{
validateAssociatedScripts(table);
}
//////////////////////
// validate cacheOf //
//////////////////////
if(table.getCacheOf() != null)
{
validateTableCacheOf(qInstance, table);
}
validateTableAutomationDetails(qInstance, table);
validateTableUniqueKeys(table);
validateAssociatedScripts(table);
validateTableCacheOf(qInstance, table);
validateTableRecordSecurityLocks(qInstance, table);
});
}
}
/*******************************************************************************
**
*******************************************************************************/
private void validateTableRecordSecurityLocks(QInstance qInstance, QTableMetaData table)
{
String prefix = "Table " + table.getName() + " ";
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(table.getRecordSecurityLocks()))
{
String securityKeyTypeName = recordSecurityLock.getSecurityKeyType();
if(assertCondition(StringUtils.hasContent(securityKeyTypeName), prefix + "has a recordSecurityLock that is missing a securityKeyType"))
{
assertCondition(qInstance.getSecurityKeyType(securityKeyTypeName) != null, prefix + "has a recordSecurityLock with an unrecognized securityKeyType: " + securityKeyTypeName);
}
prefix = "Table " + table.getName() + " recordSecurityLock (of key type " + securityKeyTypeName + ") ";
String fieldName = recordSecurityLock.getFieldName();
if(assertCondition(StringUtils.hasContent(fieldName), prefix + "is missing a fieldName"))
{
assertCondition(findField(qInstance, table, null, fieldName), prefix + "has an unrecognized fieldName: " + fieldName);
}
assertCondition(recordSecurityLock.getNullValueBehavior() != null, prefix + "is missing a nullValueBehavior");
}
}
/*******************************************************************************
**
*******************************************************************************/
@ -465,16 +491,36 @@ public class QInstanceValidator
"Unrecognized possibleValueSourceName " + field.getPossibleValueSourceName() + " in table " + tableName + " for field " + fieldName + ".");
}
String prefix = "Field " + fieldName + " in table " + tableName + " ";
ValueTooLongBehavior behavior = field.getBehavior(qInstance, ValueTooLongBehavior.class);
if(behavior != null && !behavior.equals(ValueTooLongBehavior.PASS_THROUGH))
{
assertCondition(field.getMaxLength() != null, "Field " + fieldName + " in table " + tableName + " specifies a ValueTooLongBehavior, but not a maxLength.");
assertCondition(field.getMaxLength() != null, prefix + "specifies a ValueTooLongBehavior, but not a maxLength.");
}
if(field.getMaxLength() != null)
{
assertCondition(field.getMaxLength() > 0, "Field " + fieldName + " in table " + tableName + " has an invalid maxLength (" + field.getMaxLength() + ") - must be greater than 0.");
assertCondition(field.getType().isStringLike(), "Field " + fieldName + " in table " + tableName + " has maxLength, but is not of a supported type (" + field.getType() + ") - must be a string-like type.");
assertCondition(field.getMaxLength() > 0, prefix + "has an invalid maxLength (" + field.getMaxLength() + ") - must be greater than 0.");
assertCondition(field.getType().isStringLike(), prefix + "has maxLength, but is not of a supported type (" + field.getType() + ") - must be a string-like type.");
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// this condition doesn't make sense/apply - because the default value-too-long behavior is pass-through, so, idk, just omit //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// assertCondition(behavior != null, prefix + "specifies a maxLength, but no ValueTooLongBehavior.");
}
FieldSecurityLock fieldSecurityLock = field.getFieldSecurityLock();
if(fieldSecurityLock != null)
{
String securityKeyTypeName = fieldSecurityLock.getSecurityKeyType();
if(assertCondition(StringUtils.hasContent(securityKeyTypeName), prefix + "has a fieldSecurityLock that is missing a securityKeyType"))
{
assertCondition(qInstance.getSecurityKeyType(securityKeyTypeName) != null, prefix + "has a fieldSecurityLock with an unrecognized securityKeyType: " + securityKeyTypeName);
}
assertCondition(fieldSecurityLock.getDefaultBehavior() != null, prefix + "has a fieldSecurityLock that is missing a defaultBehavior");
assertCondition(CollectionUtils.nullSafeHasContents(fieldSecurityLock.getOverrideValues()), prefix + "has a fieldSecurityLock that is missing overrideValues");
}
}
@ -485,9 +531,14 @@ public class QInstanceValidator
*******************************************************************************/
private void validateTableCacheOf(QInstance qInstance, QTableMetaData table)
{
CacheOf cacheOf = table.getCacheOf();
String prefix = "Table " + table.getName() + " cacheOf ";
String sourceTableName = cacheOf.getSourceTable();
CacheOf cacheOf = table.getCacheOf();
if(cacheOf == null)
{
return;
}
String prefix = "Table " + table.getName() + " cacheOf ";
String sourceTableName = cacheOf.getSourceTable();
if(assertCondition(StringUtils.hasContent(sourceTableName), prefix + "is missing a sourceTable name"))
{
assertCondition(qInstance.getTable(sourceTableName) != null, prefix + "is referencing an unknown sourceTable: " + sourceTableName);
@ -519,7 +570,7 @@ public class QInstanceValidator
private void validateAssociatedScripts(QTableMetaData table)
{
Set<String> usedFieldNames = new HashSet<>();
for(AssociatedScript associatedScript : table.getAssociatedScripts())
for(AssociatedScript associatedScript : CollectionUtils.nonNullList(table.getAssociatedScripts()))
{
if(assertCondition(StringUtils.hasContent(associatedScript.getFieldName()), "Table " + table.getName() + " has an associatedScript without a fieldName"))
{
@ -548,7 +599,7 @@ public class QInstanceValidator
private void validateTableUniqueKeys(QTableMetaData table)
{
Set<Set<String>> ukSets = new HashSet<>();
for(UniqueKey uniqueKey : table.getUniqueKeys())
for(UniqueKey uniqueKey : CollectionUtils.nonNullList(table.getUniqueKeys()))
{
if(assertCondition(CollectionUtils.nullSafeHasContents(uniqueKey.getFieldNames()), table.getName() + " has a uniqueKey with no fields"))
{
@ -573,11 +624,15 @@ public class QInstanceValidator
*******************************************************************************/
private void validateTableAutomationDetails(QInstance qInstance, QTableMetaData table)
{
QTableAutomationDetails automationDetails = table.getAutomationDetails();
if(automationDetails == null)
{
return;
}
String tableName = table.getName();
String prefix = "Table " + tableName + " automationDetails ";
QTableAutomationDetails automationDetails = table.getAutomationDetails();
//////////////////////////////////////
// validate the automation provider //
//////////////////////////////////////
@ -1028,7 +1083,7 @@ public class QInstanceValidator
assertCondition(usedDataSourceNames.contains(view.getVarianceDataSourceName()), viewErrorPrefix + "has an unrecognized varianceDataSourceName: " + view.getVarianceDataSourceName());
}
boolean hasColumns = CollectionUtils.nullSafeHasContents(view.getColumns());
boolean hasColumns = CollectionUtils.nullSafeHasContents(view.getColumns());
boolean hasViewCustomizer = view.getViewCustomizer() != null;
assertCondition(hasColumns || hasViewCustomizer, viewErrorPrefix + "does not have any columns or a view customizer.");
@ -1092,7 +1147,7 @@ public class QInstanceValidator
/*******************************************************************************
** Look for a field name in either a table, or the tables referenced in a list of query joins.
*******************************************************************************/
private static boolean findField(QInstance qInstance, QTableMetaData table, List<QueryJoin> queryJoins, String fieldName)
private boolean findField(QInstance qInstance, QTableMetaData table, List<QueryJoin> queryJoins, String fieldName)
{
boolean foundField = false;
try
@ -1105,22 +1160,34 @@ public class QInstanceValidator
if(fieldName.contains("."))
{
String fieldNameAfterDot = fieldName.substring(fieldName.lastIndexOf(".") + 1);
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
if(CollectionUtils.nullSafeHasContents(queryJoins))
{
QTableMetaData joinTable = qInstance.getTable(queryJoin.getJoinTable());
if(joinTable != null)
for(QueryJoin queryJoin : CollectionUtils.nonNullList(queryJoins))
{
try
QTableMetaData joinTable = qInstance.getTable(queryJoin.getJoinTable());
if(joinTable != null)
{
joinTable.getField(fieldNameAfterDot);
foundField = true;
}
catch(Exception e2)
{
continue;
try
{
joinTable.getField(fieldNameAfterDot);
foundField = true;
}
catch(Exception e2)
{
continue;
}
}
}
}
else
{
errors.add("QInstanceValidator does not yet support finding a field that looks like a join field, but isn't associated with a query.");
return (true);
// todo! for(QJoinMetaData join : CollectionUtils.nonNullMap(qInstance.getJoins()).values())
// {
// }
}
}
}
return foundField;

View File

@ -186,4 +186,27 @@ public class AbstractActionInput
{
this.asyncJobCallback = asyncJobCallback;
}
/*******************************************************************************
** Fluent setter for instance
*******************************************************************************/
public AbstractActionInput withInstance(QInstance instance)
{
this.instance = instance;
return (this);
}
/*******************************************************************************
** Fluent setter for session
*******************************************************************************/
public AbstractActionInput withSession(QSession session)
{
this.session = session;
return (this);
}
}

View File

@ -25,13 +25,14 @@ package com.kingsrook.qqq.backend.core.model.actions;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
/*******************************************************************************
** Base class for input for any qqq action that works against a table.
**
*******************************************************************************/
public abstract class AbstractTableActionInput extends AbstractActionInput
public class AbstractTableActionInput extends AbstractActionInput
{
private String tableName;
@ -95,4 +96,28 @@ public abstract class AbstractTableActionInput extends AbstractActionInput
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName
*******************************************************************************/
public AbstractTableActionInput withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Fluent setter for session
*******************************************************************************/
@Override
public AbstractTableActionInput withSession(QSession session)
{
super.withSession(session);
return (this);
}
}

View File

@ -22,7 +22,7 @@
package com.kingsrook.qqq.backend.core.model.actions.metadata;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
@ -30,11 +30,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
** Input for meta-data for a table.
**
*******************************************************************************/
public class TableMetaDataInput extends AbstractActionInput
public class TableMetaDataInput extends AbstractTableActionInput
{
private String tableName;
/*******************************************************************************
**
@ -53,25 +50,4 @@ public class TableMetaDataInput extends AbstractActionInput
super(instance);
}
/*******************************************************************************
** Getter for tableName
**
*******************************************************************************/
public String getTableName()
{
return tableName;
}
/*******************************************************************************
** Setter for tableName
**
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
}

View File

@ -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.security.RecordSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -69,6 +70,37 @@ public class JoinsContext
}
aliasToTableNameMap.put(tableNameOrAlias, joinTable.getName());
}
///////////////////////////////////////////////////////////////
// ensure any joins that contribute a recordLock are present //
///////////////////////////////////////////////////////////////
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(instance.getTable(tableName).getRecordSecurityLocks()))
{
for(String joinName : CollectionUtils.nonNullList(recordSecurityLock.getJoinChain()))
{
if(this.queryJoins.stream().anyMatch(qj -> qj.getJoinMetaData().getName().equals(joinName)))
{
///////////////////////////////////////////////////////
// we're good - we're already joining on this table! //
///////////////////////////////////////////////////////
}
else
{
this.queryJoins.add(new QueryJoin().withJoinMetaData(instance.getJoin(joinName)).withType(QueryJoin.Type.INNER)); // todo aliases? probably.
}
}
}
/* todo!!
for(QueryJoin queryJoin : queryJoins)
{
QTableMetaData joinTable = instance.getTable(queryJoin.getJoinTable());
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(joinTable.getRecordSecurityLocks()))
{
// addCriteriaForRecordSecurityLock(instance, session, joinTable, securityCriteria, recordSecurityLock, joinsContext, queryJoin.getJoinTableOrItsAlias());
}
}
*/
}

View File

@ -32,6 +32,7 @@ public enum QCriteriaOperator
NOT_EQUALS,
IN,
NOT_IN,
IS_NULL_OR_IN,
STARTS_WITH,
ENDS_WITH,
CONTAINS,

View File

@ -340,7 +340,7 @@ public class QQueryFilter implements Serializable, Cloneable
{
for(QFilterCriteria criterion : CollectionUtils.nonNullList(criteria))
{
rs.append(criterion).append(" ").append(getBooleanOperator());
rs.append(criterion).append(" ").append(getBooleanOperator()).append(" ");
}
for(QQueryFilter subFilter : CollectionUtils.nonNullList(subFilters))

View File

@ -25,8 +25,10 @@ package com.kingsrook.qqq.backend.core.model.metadata;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
@ -42,13 +44,16 @@ import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNodeType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
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.StringUtils;
import io.github.cdimascio.dotenv.Dotenv;
import io.github.cdimascio.dotenv.DotenvEntry;
@ -73,20 +78,21 @@ public class QInstance
////////////////////////////////////////////////////////////////////////////////////////////
// Important to use LinkedHashmap here, to preserve the order in which entries are added. //
////////////////////////////////////////////////////////////////////////////////////////////
private Map<String, QTableMetaData> tables = new LinkedHashMap<>();
private Map<String, QJoinMetaData> joins = new LinkedHashMap<>();
private Map<String, QPossibleValueSource> possibleValueSources = new LinkedHashMap<>();
private Map<String, QProcessMetaData> processes = new LinkedHashMap<>();
private Map<String, QAppMetaData> apps = new LinkedHashMap<>();
private Map<String, QReportMetaData> reports = new LinkedHashMap<>();
private Map<String, QWidgetMetaDataInterface> widgets = new LinkedHashMap<>();
private Map<String, QQueueProviderMetaData> queueProviders = new LinkedHashMap<>();
private Map<String, QQueueMetaData> queues = new LinkedHashMap<>();
private Map<String, QTableMetaData> tables = new LinkedHashMap<>();
private Map<String, QJoinMetaData> joins = new LinkedHashMap<>();
private Map<String, QPossibleValueSource> possibleValueSources = new LinkedHashMap<>();
private Map<String, QProcessMetaData> processes = new LinkedHashMap<>();
private Map<String, QAppMetaData> apps = new LinkedHashMap<>();
private Map<String, QReportMetaData> reports = new LinkedHashMap<>();
private Map<String, QSecurityKeyType> securityKeyTypes = new LinkedHashMap<>();
private Map<String, QWidgetMetaDataInterface> widgets = new LinkedHashMap<>();
private Map<String, QQueueProviderMetaData> queueProviders = new LinkedHashMap<>();
private Map<String, QQueueMetaData> queues = new LinkedHashMap<>();
private Map<String, String> environmentValues = new LinkedHashMap<>();
private QPermissionRules defaultPermissionRules = QPermissionRules.defaultInstance();
// todo - lock down the object (no more changes allowed) after it's been validated?
@JsonIgnore
@ -249,16 +255,7 @@ public class QInstance
*******************************************************************************/
public void addBackend(QBackendMetaData backend)
{
addBackend(backend.getName(), backend);
}
/*******************************************************************************
**
*******************************************************************************/
public void addBackend(String name, QBackendMetaData backend)
{
String name = backend.getName();
if(!StringUtils.hasContent(name))
{
throw (new IllegalArgumentException("Attempted to add a backend without a name."));
@ -309,16 +306,7 @@ public class QInstance
*******************************************************************************/
public void addTable(QTableMetaData table)
{
addTable(table.getName(), table);
}
/*******************************************************************************
**
*******************************************************************************/
public void addTable(String name, QTableMetaData table)
{
String name = table.getName();
if(!StringUtils.hasContent(name))
{
throw (new IllegalArgumentException("Attempted to add a table without a name."));
@ -374,16 +362,7 @@ public class QInstance
*******************************************************************************/
public void addJoin(QJoinMetaData join)
{
addJoin(join.getName(), join);
}
/*******************************************************************************
**
*******************************************************************************/
public void addJoin(String name, QJoinMetaData join)
{
String name = join.getName();
if(!StringUtils.hasContent(name))
{
throw (new IllegalArgumentException("Attempted to add a join without a name."));
@ -439,16 +418,7 @@ public class QInstance
*******************************************************************************/
public void addPossibleValueSource(QPossibleValueSource possibleValueSource)
{
this.addPossibleValueSource(possibleValueSource.getName(), possibleValueSource);
}
/*******************************************************************************
**
*******************************************************************************/
public void addPossibleValueSource(String name, QPossibleValueSource possibleValueSource)
{
String name = possibleValueSource.getName();
if(!StringUtils.hasContent(name))
{
throw (new IllegalArgumentException("Attempted to add a possibleValueSource without a name."));
@ -515,16 +485,7 @@ public class QInstance
*******************************************************************************/
public void addProcess(QProcessMetaData process)
{
this.addProcess(process.getName(), process);
}
/*******************************************************************************
**
*******************************************************************************/
public void addProcess(String name, QProcessMetaData process)
{
String name = process.getName();
if(!StringUtils.hasContent(name))
{
throw (new IllegalArgumentException("Attempted to add a process without a name."));
@ -575,19 +536,10 @@ public class QInstance
*******************************************************************************/
public void addApp(QAppMetaData app)
{
this.addApp(app.getName(), app);
}
/*******************************************************************************
**
*******************************************************************************/
public void addApp(String name, QAppMetaData app)
{
String name = app.getName();
if(!StringUtils.hasContent(name))
{
throw (new IllegalArgumentException("Attempted to add an app without a name."));
throw (new IllegalArgumentException("Attempted to add a app without a name."));
}
if(this.apps.containsKey(name))
{
@ -635,19 +587,10 @@ public class QInstance
*******************************************************************************/
public void addReport(QReportMetaData report)
{
this.addReport(report.getName(), report);
}
/*******************************************************************************
**
*******************************************************************************/
public void addReport(String name, QReportMetaData report)
{
String name = report.getName();
if(!StringUtils.hasContent(name))
{
throw (new IllegalArgumentException("Attempted to add an report without a name."));
throw (new IllegalArgumentException("Attempted to add a report without a name."));
}
if(this.reports.containsKey(name))
{
@ -693,9 +636,14 @@ public class QInstance
/*******************************************************************************
**
*******************************************************************************/
public void addAutomationProvider(QAutomationProviderMetaData automationProvider)
public void addSecurityKeyType(QSecurityKeyType securityKeyType)
{
this.addAutomationProvider(automationProvider.getName(), automationProvider);
String name = securityKeyType.getName();
if(this.securityKeyTypes.containsKey(name))
{
throw (new IllegalArgumentException("Attempted to add a second securityKeyType with name: " + name));
}
this.securityKeyTypes.put(name, securityKeyType);
}
@ -703,8 +651,41 @@ public class QInstance
/*******************************************************************************
**
*******************************************************************************/
public void addAutomationProvider(String name, QAutomationProviderMetaData automationProvider)
public QSecurityKeyType getSecurityKeyType(String name)
{
return (this.securityKeyTypes.get(name));
}
/*******************************************************************************
** Getter for securityKeyTypes
**
*******************************************************************************/
public Map<String, QSecurityKeyType> getSecurityKeyTypes()
{
return securityKeyTypes;
}
/*******************************************************************************
** Setter for securityKeyTypes
**
*******************************************************************************/
public void setSecurityKeyTypes(Map<String, QSecurityKeyType> securityKeyTypes)
{
this.securityKeyTypes = securityKeyTypes;
}
/*******************************************************************************
**
*******************************************************************************/
public void addAutomationProvider(QAutomationProviderMetaData automationProvider)
{
String name = automationProvider.getName();
if(this.automationProviders.containsKey(name))
{
throw (new IllegalArgumentException("Attempted to add a second automationProvider with name: " + name));
@ -839,16 +820,7 @@ public class QInstance
*******************************************************************************/
public void addWidget(QWidgetMetaDataInterface widget)
{
this.addWidget(widget.getName(), widget);
}
/*******************************************************************************
**
*******************************************************************************/
public void addWidget(String name, QWidgetMetaDataInterface widget)
{
String name = widget.getName();
if(this.widgets.containsKey(name))
{
throw (new IllegalArgumentException("Attempted to add a second widget with name: " + name));
@ -873,19 +845,10 @@ public class QInstance
*******************************************************************************/
public void addQueueProvider(QQueueProviderMetaData queueProvider)
{
this.addQueueProvider(queueProvider.getName(), queueProvider);
}
/*******************************************************************************
**
*******************************************************************************/
public void addQueueProvider(String name, QQueueProviderMetaData queueProvider)
{
String name = queueProvider.getName();
if(!StringUtils.hasContent(name))
{
throw (new IllegalArgumentException("Attempted to add an queueProvider without a name."));
throw (new IllegalArgumentException("Attempted to add a queueProvider without a name."));
}
if(this.queueProviders.containsKey(name))
{
@ -933,19 +896,10 @@ public class QInstance
*******************************************************************************/
public void addQueue(QQueueMetaData queue)
{
this.addQueue(queue.getName(), queue);
}
/*******************************************************************************
**
*******************************************************************************/
public void addQueue(String name, QQueueMetaData queue)
{
String name = queue.getName();
if(!StringUtils.hasContent(name))
{
throw (new IllegalArgumentException("Attempted to add an queue without a name."));
throw (new IllegalArgumentException("Attempted to add a queue without a name."));
}
if(this.queues.containsKey(name))
{
@ -1008,4 +962,54 @@ public class QInstance
this.environmentValues = environmentValues;
}
/*******************************************************************************
** Getter for defaultPermissionRules
*******************************************************************************/
public QPermissionRules getDefaultPermissionRules()
{
return (this.defaultPermissionRules);
}
/*******************************************************************************
** Setter for defaultPermissionRules
*******************************************************************************/
public void setDefaultPermissionRules(QPermissionRules defaultPermissionRules)
{
this.defaultPermissionRules = defaultPermissionRules;
}
/*******************************************************************************
** Fluent setter for defaultPermissionRules
*******************************************************************************/
public QInstance withDefaultPermissionRules(QPermissionRules defaultPermissionRules)
{
this.defaultPermissionRules = defaultPermissionRules;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public Set<String> getAllowedSecurityKeyNames()
{
Set<String> rs = new LinkedHashSet<>();
for(QSecurityKeyType securityKeyType : CollectionUtils.nonNullMap(getSecurityKeyTypes()).values())
{
rs.add(securityKeyType.getName());
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
{
rs.add(securityKeyType.getAllAccessKeyName());
}
}
return (rs);
}
}

View File

@ -35,6 +35,7 @@ public class Auth0AuthenticationMetaData extends QAuthenticationMetaData
{
private String baseUrl;
private String clientId;
private String audience;
////////////////////////////////////////////////////////////////////////////////////////
// keep this secret, on the server - don't let it be serialized and sent to a client! //
@ -156,4 +157,36 @@ public class Auth0AuthenticationMetaData extends QAuthenticationMetaData
{
this.clientSecret = clientSecret;
}
/*******************************************************************************
** Getter for audience
*******************************************************************************/
public String getAudience()
{
return (this.audience);
}
/*******************************************************************************
** Setter for audience
*******************************************************************************/
public void setAudience(String audience)
{
this.audience = audience;
}
/*******************************************************************************
** Fluent setter for audience
*******************************************************************************/
public Auth0AuthenticationMetaData withAudience(String audience)
{
this.audience = audience;
return (this);
}
}

View File

@ -28,6 +28,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
/*******************************************************************************
@ -44,6 +45,8 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
protected Integer gridColumns;
protected QCodeReference codeReference;
private QPermissionRules permissionRules;
private List<WidgetDropdownData> dropdowns;
protected Map<String, Serializable> defaultValues = new LinkedHashMap<>();
@ -387,4 +390,37 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
return (this);
}
/*******************************************************************************
** Getter for permissionRules
*******************************************************************************/
@Override
public QPermissionRules getPermissionRules()
{
return (this.permissionRules);
}
/*******************************************************************************
** Setter for permissionRules
*******************************************************************************/
@Override
public void setPermissionRules(QPermissionRules permissionRules)
{
this.permissionRules = permissionRules;
}
/*******************************************************************************
** Fluent setter for permissionRules
*******************************************************************************/
public QWidgetMetaData withPermissionRules(QPermissionRules permissionRules)
{
this.permissionRules = permissionRules;
return (this);
}
}

View File

@ -25,13 +25,15 @@ package com.kingsrook.qqq.backend.core.model.metadata.dashboard;
import java.io.Serializable;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
/*******************************************************************************
** Interface for qqq widget meta data
**
*******************************************************************************/
public interface QWidgetMetaDataInterface
public interface QWidgetMetaDataInterface extends MetaDataWithPermissionRules
{
/*******************************************************************************
** Getter for name
@ -148,5 +150,17 @@ public interface QWidgetMetaDataInterface
*******************************************************************************/
QWidgetMetaData withDefaultValue(String key, Serializable value);
/*******************************************************************************
** Getter for permissionRules
*******************************************************************************/
QPermissionRules getPermissionRules();
/*******************************************************************************
** Setter for permissionRules
*******************************************************************************/
void setPermissionRules(QPermissionRules permissionRules);
}

View File

@ -34,6 +34,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -51,6 +52,8 @@ public class QFieldMetaData implements Cloneable
private boolean isRequired = false;
private boolean isEditable = true;
private FieldSecurityLock fieldSecurityLock;
///////////////////////////////////////////////////////////////////////////////////
// if we need "only edit on insert" or "only edit on update" in the future, //
// propose doing that in a secondary field, e.g., "onlyEditableOn=insert|update" //
@ -689,4 +692,35 @@ public class QFieldMetaData implements Cloneable
return (this);
}
/*******************************************************************************
** Getter for fieldSecurityLock
*******************************************************************************/
public FieldSecurityLock getFieldSecurityLock()
{
return (this.fieldSecurityLock);
}
/*******************************************************************************
** Setter for fieldSecurityLock
*******************************************************************************/
public void setFieldSecurityLock(FieldSecurityLock fieldSecurityLock)
{
this.fieldSecurityLock = fieldSecurityLock;
}
/*******************************************************************************
** Fluent setter for fieldSecurityLock
*******************************************************************************/
public QFieldMetaData withFieldSecurityLock(FieldSecurityLock fieldSecurityLock)
{
this.fieldSecurityLock = fieldSecurityLock;
return (this);
}
}

View File

@ -28,6 +28,7 @@ import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
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.utils.CollectionUtils;
@ -55,7 +56,7 @@ public class QFrontendAppMetaData
/*******************************************************************************
**
*******************************************************************************/
public QFrontendAppMetaData(QAppMetaData appMetaData)
public QFrontendAppMetaData(QAppMetaData appMetaData, MetaDataOutput metaDataOutput)
{
this.name = appMetaData.getName();
this.label = appMetaData.getLabel();
@ -65,14 +66,31 @@ public class QFrontendAppMetaData
this.iconName = appMetaData.getIcon().getName();
}
if(CollectionUtils.nullSafeHasContents(appMetaData.getWidgets()))
List<String> filteredWidgets = CollectionUtils.nonNullList(appMetaData.getWidgets()).stream().filter(n -> metaDataOutput.getWidgets().containsKey(n)).toList();
if(CollectionUtils.nullSafeHasContents(filteredWidgets))
{
this.widgets = appMetaData.getWidgets();
this.widgets = filteredWidgets;
}
if(CollectionUtils.nullSafeHasContents(appMetaData.getSections()))
List<QAppSection> filteredSections = new ArrayList<>();
for(QAppSection section : CollectionUtils.nonNullList(appMetaData.getSections()))
{
this.sections = appMetaData.getSections();
List<String> filteredTables = CollectionUtils.nonNullList(section.getTables()).stream().filter(n -> metaDataOutput.getTables().containsKey(n)).toList();
List<String> filteredProcesses = CollectionUtils.nonNullList(section.getProcesses()).stream().filter(n -> metaDataOutput.getProcesses().containsKey(n)).toList();
List<String> filteredReports = CollectionUtils.nonNullList(section.getReports()).stream().filter(n -> metaDataOutput.getReports().containsKey(n)).toList();
if(!filteredTables.isEmpty() || !filteredProcesses.isEmpty() || !filteredReports.isEmpty())
{
QAppSection clonedSection = section.clone();
clonedSection.setTables(filteredTables);
clonedSection.setProcesses(filteredProcesses);
clonedSection.setReports(filteredReports);
filteredSections.add(clonedSection);
}
}
if(CollectionUtils.nullSafeHasContents(filteredSections))
{
this.sections = filteredSections;
}
}

View File

@ -27,6 +27,8 @@ import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -49,6 +51,8 @@ public class QFrontendProcessMetaData
private List<QFrontendStepMetaData> frontendSteps;
private boolean hasPermission;
//////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! //
//////////////////////////////////////////////////////////////////////////////////
@ -58,7 +62,7 @@ public class QFrontendProcessMetaData
/*******************************************************************************
**
*******************************************************************************/
public QFrontendProcessMetaData(QProcessMetaData processMetaData, boolean includeSteps)
public QFrontendProcessMetaData(AbstractActionInput actionInput, QProcessMetaData processMetaData, boolean includeSteps)
{
this.name = processMetaData.getName();
this.label = processMetaData.getLabel();
@ -84,6 +88,8 @@ public class QFrontendProcessMetaData
{
this.iconName = processMetaData.getIcon().getName();
}
hasPermission = PermissionsHelper.hasProcessPermission(actionInput, name);
}
@ -163,4 +169,15 @@ public class QFrontendProcessMetaData
return iconName;
}
/*******************************************************************************
** Getter for hasPermission
**
*******************************************************************************/
public boolean getHasPermission()
{
return hasPermission;
}
}

View File

@ -24,6 +24,8 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
@ -41,6 +43,8 @@ public class QFrontendReportMetaData
private String iconName;
private boolean hasPermission;
//////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! //
//////////////////////////////////////////////////////////////////////////////////
@ -50,7 +54,7 @@ public class QFrontendReportMetaData
/*******************************************************************************
**
*******************************************************************************/
public QFrontendReportMetaData(QReportMetaData reportMetaData, boolean includeSteps)
public QFrontendReportMetaData(AbstractActionInput actionInput, QReportMetaData reportMetaData, boolean includeSteps)
{
this.name = reportMetaData.getName();
this.label = reportMetaData.getLabel();
@ -60,6 +64,8 @@ public class QFrontendReportMetaData
{
this.iconName = reportMetaData.getIcon().getName();
}
hasPermission = PermissionsHelper.hasReportPermission(actionInput, name);
}
@ -106,4 +112,15 @@ public class QFrontendReportMetaData
return iconName;
}
/*******************************************************************************
** Getter for hasPermission
**
*******************************************************************************/
public boolean getHasPermission()
{
return hasPermission;
}
}

View File

@ -30,6 +30,9 @@ import java.util.Set;
import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
@ -55,10 +58,13 @@ public class QFrontendTableMetaData
private Map<String, QFrontendFieldMetaData> fields;
private List<QFieldSection> sections;
private List<String> widgets;
private Set<String> capabilities;
private boolean readPermission;
private boolean insertPermission;
private boolean editPermission;
private boolean deletePermission;
//////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! //
//////////////////////////////////////////////////////////////////////////////////
@ -68,7 +74,7 @@ public class QFrontendTableMetaData
/*******************************************************************************
**
*******************************************************************************/
public QFrontendTableMetaData(QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFields)
public QFrontendTableMetaData(AbstractActionInput actionInput, QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFields)
{
this.name = tableMetaData.getName();
this.label = tableMetaData.getLabel();
@ -92,6 +98,11 @@ public class QFrontendTableMetaData
}
setCapabilities(backendForTable, tableMetaData);
readPermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.READ);
insertPermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.INSERT);
editPermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.EDIT);
deletePermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.DELETE);
}
@ -196,17 +207,6 @@ public class QFrontendTableMetaData
/*******************************************************************************
** Getter for widgets
**
*******************************************************************************/
public List<String> getWidgets()
{
return widgets;
}
/*******************************************************************************
** Getter for capabilities
**
@ -216,4 +216,47 @@ public class QFrontendTableMetaData
return capabilities;
}
/*******************************************************************************
** Getter for readPermission
**
*******************************************************************************/
public boolean getReadPermission()
{
return readPermission;
}
/*******************************************************************************
** Getter for insertPermission
**
*******************************************************************************/
public boolean getInsertPermission()
{
return insertPermission;
}
/*******************************************************************************
** Getter for editPermission
**
*******************************************************************************/
public boolean getEditPermission()
{
return editPermission;
}
/*******************************************************************************
** Getter for deletePermission
**
*******************************************************************************/
public boolean getDeletePermission()
{
return deletePermission;
}
}

View File

@ -24,6 +24,8 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
@ -42,6 +44,8 @@ public class QFrontendWidgetMetaData
private final Integer gridColumns;
private final boolean isCard;
private boolean hasPermission;
//////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! //
//////////////////////////////////////////////////////////////////////////////////
@ -51,7 +55,7 @@ public class QFrontendWidgetMetaData
/*******************************************************************************
**
*******************************************************************************/
public QFrontendWidgetMetaData(QWidgetMetaDataInterface widgetMetaData)
public QFrontendWidgetMetaData(AbstractActionInput actionInput, QWidgetMetaDataInterface widgetMetaData)
{
this.name = widgetMetaData.getName();
this.label = widgetMetaData.getLabel();
@ -59,6 +63,8 @@ public class QFrontendWidgetMetaData
this.icon = widgetMetaData.getIcon();
this.gridColumns = widgetMetaData.getGridColumns();
this.isCard = widgetMetaData.getIsCard();
hasPermission = PermissionsHelper.hasWidgetPermission(actionInput, name);
}
@ -127,4 +133,15 @@ public class QFrontendWidgetMetaData
return icon;
}
/*******************************************************************************
** Getter for hasPermission
**
*******************************************************************************/
public boolean getHasPermission()
{
return hasPermission;
}
}

View File

@ -24,6 +24,8 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
@ -34,11 +36,13 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
** MetaData definition of an App - an entity that organizes tables & processes
** and can be arranged hierarchically (e.g, apps can contain other apps).
*******************************************************************************/
public class QAppMetaData implements QAppChildMetaData
public class QAppMetaData implements QAppChildMetaData, MetaDataWithPermissionRules
{
private String name;
private String label;
private QPermissionRules permissionRules;
private List<QAppChildMetaData> children;
private String parentAppName;
@ -377,4 +381,35 @@ public class QAppMetaData implements QAppChildMetaData
return (this);
}
/*******************************************************************************
** Getter for permissionRules
*******************************************************************************/
public QPermissionRules getPermissionRules()
{
return (this.permissionRules);
}
/*******************************************************************************
** Setter for permissionRules
*******************************************************************************/
public void setPermissionRules(QPermissionRules permissionRules)
{
this.permissionRules = permissionRules;
}
/*******************************************************************************
** Fluent setter for permissionRules
*******************************************************************************/
public QAppMetaData withPermissionRules(QPermissionRules permissionRules)
{
this.permissionRules = permissionRules;
return (this);
}
}

View File

@ -29,7 +29,7 @@ import java.util.List;
/*******************************************************************************
** A section of apps/tables/processes - a logical grouping.
*******************************************************************************/
public class QAppSection
public class QAppSection implements Cloneable
{
private String name;
private String label;
@ -65,6 +65,25 @@ public class QAppSection
/*******************************************************************************
**
*******************************************************************************/
@Override
public QAppSection clone()
{
try
{
QAppSection clone = (QAppSection) super.clone();
return clone;
}
catch(CloneNotSupportedException e)
{
throw new AssertionError();
}
}
/*******************************************************************************
** Getter for name
**
@ -314,5 +333,4 @@ public class QAppSection
this.icon = icon;
return (this);
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.model.metadata.permissions;
/*******************************************************************************
**
*******************************************************************************/
public enum DenyBehavior
{
DISABLED,
HIDDEN
}

View File

@ -0,0 +1,54 @@
/*
* 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.model.metadata.permissions;
/*******************************************************************************
**
*******************************************************************************/
public interface MetaDataWithName
{
/*******************************************************************************
** Getter for name
*******************************************************************************/
String getName();
/*******************************************************************************
** Setter for name
*******************************************************************************/
void setName(String name);
/*******************************************************************************
** Getter for label
*******************************************************************************/
String getLabel();
/*******************************************************************************
** Setter for label
*******************************************************************************/
void setLabel(String label);
}

View File

@ -0,0 +1,41 @@
/*
* 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.model.metadata.permissions;
/*******************************************************************************
**
*******************************************************************************/
public interface MetaDataWithPermissionRules extends MetaDataWithName
{
/*******************************************************************************
** Getter for permissionRules
*******************************************************************************/
QPermissionRules getPermissionRules();
/*******************************************************************************
** Setter for permissionRules
*******************************************************************************/
void setPermissionRules(QPermissionRules permissionRules);
}

View File

@ -0,0 +1,34 @@
/*
* 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.model.metadata.permissions;
/*******************************************************************************
**
*******************************************************************************/
public enum PermissionLevel
{
NOT_PROTECTED,
HAS_ACCESS_PERMISSION,
READ_WRITE_PERMISSIONS,
READ_INSERT_EDIT_DELETE_PERMISSIONS,
}

View File

@ -0,0 +1,194 @@
/*
* 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.model.metadata.permissions;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
/*******************************************************************************
**
*******************************************************************************/
public class QPermissionRules implements Cloneable
{
private PermissionLevel level;
private DenyBehavior denyBehavior;
private String permissionBaseName;
private QCodeReference customPermissionChecker;
/*******************************************************************************
**
*******************************************************************************/
public static QPermissionRules defaultInstance()
{
return new QPermissionRules()
.withLevel(PermissionLevel.NOT_PROTECTED)
.withDenyBehavior(DenyBehavior.HIDDEN);
}
/*******************************************************************************
** Getter for level
*******************************************************************************/
public PermissionLevel getLevel()
{
return (this.level);
}
/*******************************************************************************
** Setter for level
*******************************************************************************/
public void setLevel(PermissionLevel level)
{
this.level = level;
}
/*******************************************************************************
** Fluent setter for level
*******************************************************************************/
public QPermissionRules withLevel(PermissionLevel level)
{
this.level = level;
return (this);
}
/*******************************************************************************
** Getter for denyBehavior
*******************************************************************************/
public DenyBehavior getDenyBehavior()
{
return (this.denyBehavior);
}
/*******************************************************************************
** Setter for denyBehavior
*******************************************************************************/
public void setDenyBehavior(DenyBehavior denyBehavior)
{
this.denyBehavior = denyBehavior;
}
/*******************************************************************************
** Fluent setter for denyBehavior
*******************************************************************************/
public QPermissionRules withDenyBehavior(DenyBehavior denyBehavior)
{
this.denyBehavior = denyBehavior;
return (this);
}
/*******************************************************************************
** Getter for permissionBaseName
*******************************************************************************/
public String getPermissionBaseName()
{
return (this.permissionBaseName);
}
/*******************************************************************************
** Setter for permissionBaseName
*******************************************************************************/
public void setPermissionBaseName(String permissionBaseName)
{
this.permissionBaseName = permissionBaseName;
}
/*******************************************************************************
** Fluent setter for permissionBaseName
*******************************************************************************/
public QPermissionRules withPermissionBaseName(String permissionBaseName)
{
this.permissionBaseName = permissionBaseName;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public QPermissionRules clone()
{
try
{
QPermissionRules clone = (QPermissionRules) super.clone();
return clone;
}
catch(CloneNotSupportedException e)
{
throw new AssertionError();
}
}
/*******************************************************************************
** Getter for customPermissionChecker
*******************************************************************************/
public QCodeReference getCustomPermissionChecker()
{
return (this.customPermissionChecker);
}
/*******************************************************************************
** Setter for customPermissionChecker
*******************************************************************************/
public void setCustomPermissionChecker(QCodeReference customPermissionChecker)
{
this.customPermissionChecker = customPermissionChecker;
}
/*******************************************************************************
** Fluent setter for customPermissionChecker
*******************************************************************************/
public QPermissionRules withCustomPermissionChecker(QCodeReference customPermissionChecker)
{
this.customPermissionChecker = customPermissionChecker;
return (this);
}
}

View File

@ -32,6 +32,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.scheduleing.QScheduleMetaData;
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
@ -40,13 +42,14 @@ import com.kingsrook.qqq.backend.core.processes.implementations.basepull.Basepul
** Meta-Data to define a process in a QQQ instance.
**
*******************************************************************************/
public class QProcessMetaData implements QAppChildMetaData
public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissionRules
{
private String name;
private String label;
private String tableName;
private boolean isHidden = false;
private BasepullConfiguration basepullConfiguration;
private QPermissionRules permissionRules;
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
@ -509,4 +512,35 @@ public class QProcessMetaData implements QAppChildMetaData
return (this);
}
/*******************************************************************************
** Getter for permissionRules
*******************************************************************************/
public QPermissionRules getPermissionRules()
{
return (this.permissionRules);
}
/*******************************************************************************
** Setter for permissionRules
*******************************************************************************/
public void setPermissionRules(QPermissionRules permissionRules)
{
this.permissionRules = permissionRules;
}
/*******************************************************************************
** Fluent setter for permissionRules
*******************************************************************************/
public QProcessMetaData withPermissionRules(QPermissionRules permissionRules)
{
this.permissionRules = permissionRules;
return (this);
}
}

View File

@ -27,17 +27,21 @@ import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
** Meta-data definition of a report generated by QQQ
*******************************************************************************/
public class QReportMetaData implements QAppChildMetaData
public class QReportMetaData implements QAppChildMetaData, MetaDataWithPermissionRules
{
private String name;
private String label;
private QPermissionRules permissionRules;
private String processName;
private List<QFieldMetaData> inputFields;
@ -372,4 +376,35 @@ public class QReportMetaData implements QAppChildMetaData
return (null);
}
/*******************************************************************************
** Getter for permissionRules
*******************************************************************************/
public QPermissionRules getPermissionRules()
{
return (this.permissionRules);
}
/*******************************************************************************
** Setter for permissionRules
*******************************************************************************/
public void setPermissionRules(QPermissionRules permissionRules)
{
this.permissionRules = permissionRules;
}
/*******************************************************************************
** Fluent setter for permissionRules
*******************************************************************************/
public QReportMetaData withPermissionRules(QPermissionRules permissionRules)
{
this.permissionRules = permissionRules;
return (this);
}
}

View File

@ -0,0 +1,154 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.security;
import java.io.Serializable;
import java.util.List;
/*******************************************************************************
** Define, for a field, a lock that controls if users can or cannot see the field.
*******************************************************************************/
public class FieldSecurityLock
{
private String securityKeyType;
private Behavior defaultBehavior = Behavior.DENY;
private List<Serializable> overrideValues;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public FieldSecurityLock()
{
}
/*******************************************************************************
**
*******************************************************************************/
public enum Behavior
{
ALLOW,
DENY
}
/*******************************************************************************
** Getter for securityKeyType
*******************************************************************************/
public String getSecurityKeyType()
{
return (this.securityKeyType);
}
/*******************************************************************************
** Setter for securityKeyType
*******************************************************************************/
public void setSecurityKeyType(String securityKeyType)
{
this.securityKeyType = securityKeyType;
}
/*******************************************************************************
** Fluent setter for securityKeyType
*******************************************************************************/
public FieldSecurityLock withSecurityKeyType(String securityKeyType)
{
this.securityKeyType = securityKeyType;
return (this);
}
/*******************************************************************************
** Getter for defaultBehavior
*******************************************************************************/
public Behavior getDefaultBehavior()
{
return (this.defaultBehavior);
}
/*******************************************************************************
** Setter for defaultBehavior
*******************************************************************************/
public void setDefaultBehavior(Behavior defaultBehavior)
{
this.defaultBehavior = defaultBehavior;
}
/*******************************************************************************
** Fluent setter for defaultBehavior
*******************************************************************************/
public FieldSecurityLock withDefaultBehavior(Behavior defaultBehavior)
{
this.defaultBehavior = defaultBehavior;
return (this);
}
/*******************************************************************************
** Getter for overrideValues
*******************************************************************************/
public List<Serializable> getOverrideValues()
{
return (this.overrideValues);
}
/*******************************************************************************
** Setter for overrideValues
*******************************************************************************/
public void setOverrideValues(List<Serializable> overrideValues)
{
this.overrideValues = overrideValues;
}
/*******************************************************************************
** Fluent setter for overrideValues
*******************************************************************************/
public FieldSecurityLock withOverrideValues(List<Serializable> overrideValues)
{
this.overrideValues = overrideValues;
return (this);
}
}

View File

@ -0,0 +1,137 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.security;
/*******************************************************************************
** Define a type of security key (e.g., a field associated with values), that
** can be used to control access to records and/or fields
*******************************************************************************/
public class QSecurityKeyType
{
private String name;
private String allAccessKeyName;
private String possibleValueSourceName;
/*******************************************************************************
**
*******************************************************************************/
public QSecurityKeyType()
{
}
/*******************************************************************************
** Getter for name
*******************************************************************************/
public String getName()
{
return (this.name);
}
/*******************************************************************************
** Setter for name
*******************************************************************************/
public void setName(String name)
{
this.name = name;
}
/*******************************************************************************
** Fluent setter for name
*******************************************************************************/
public QSecurityKeyType withName(String name)
{
this.name = name;
return (this);
}
/*******************************************************************************
** Getter for allAccessKeyName
*******************************************************************************/
public String getAllAccessKeyName()
{
return (this.allAccessKeyName);
}
/*******************************************************************************
** Setter for allAccessKeyName
*******************************************************************************/
public void setAllAccessKeyName(String allAccessKeyName)
{
this.allAccessKeyName = allAccessKeyName;
}
/*******************************************************************************
** Fluent setter for allAccessKeyName
*******************************************************************************/
public QSecurityKeyType withAllAccessKeyName(String allAccessKeyName)
{
this.allAccessKeyName = allAccessKeyName;
return (this);
}
/*******************************************************************************
** Getter for possibleValueSourceName
*******************************************************************************/
public String getPossibleValueSourceName()
{
return (this.possibleValueSourceName);
}
/*******************************************************************************
** Setter for possibleValueSourceName
*******************************************************************************/
public void setPossibleValueSourceName(String possibleValueSourceName)
{
this.possibleValueSourceName = possibleValueSourceName;
}
/*******************************************************************************
** Fluent setter for possibleValueSourceName
*******************************************************************************/
public QSecurityKeyType withPossibleValueSourceName(String possibleValueSourceName)
{
this.possibleValueSourceName = possibleValueSourceName;
return (this);
}
}

View File

@ -0,0 +1,185 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.security;
import java.util.List;
/*******************************************************************************
** Define (for a table) a lock that applies to records in the table - e.g.,
** a key type, and a field that has values for that key.
*
*******************************************************************************/
public class RecordSecurityLock
{
private String securityKeyType;
private String fieldName;
private List<String> joinChain; // todo - add validation in validator!!
private NullValueBehavior nullValueBehavior = NullValueBehavior.DENY;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public RecordSecurityLock()
{
}
/*******************************************************************************
**
*******************************************************************************/
public enum NullValueBehavior
{
ALLOW,
DENY
}
/*******************************************************************************
** Getter for securityKeyType
*******************************************************************************/
public String getSecurityKeyType()
{
return (this.securityKeyType);
}
/*******************************************************************************
** Setter for securityKeyType
*******************************************************************************/
public void setSecurityKeyType(String securityKeyType)
{
this.securityKeyType = securityKeyType;
}
/*******************************************************************************
** Fluent setter for securityKeyType
*******************************************************************************/
public RecordSecurityLock withSecurityKeyType(String securityKeyType)
{
this.securityKeyType = securityKeyType;
return (this);
}
/*******************************************************************************
** Getter for fieldName
*******************************************************************************/
public String getFieldName()
{
return (this.fieldName);
}
/*******************************************************************************
** Setter for fieldName
*******************************************************************************/
public void setFieldName(String fieldName)
{
this.fieldName = fieldName;
}
/*******************************************************************************
** Fluent setter for fieldName
*******************************************************************************/
public RecordSecurityLock withFieldName(String fieldName)
{
this.fieldName = fieldName;
return (this);
}
/*******************************************************************************
** Getter for nullValueBehavior
*******************************************************************************/
public NullValueBehavior getNullValueBehavior()
{
return (this.nullValueBehavior);
}
/*******************************************************************************
** Setter for nullValueBehavior
*******************************************************************************/
public void setNullValueBehavior(NullValueBehavior nullValueBehavior)
{
this.nullValueBehavior = nullValueBehavior;
}
/*******************************************************************************
** Fluent setter for nullValueBehavior
*******************************************************************************/
public RecordSecurityLock withNullValueBehavior(NullValueBehavior nullValueBehavior)
{
this.nullValueBehavior = nullValueBehavior;
return (this);
}
/*******************************************************************************
** Getter for joinChain
*******************************************************************************/
public List<String> getJoinChain()
{
return (this.joinChain);
}
/*******************************************************************************
** Setter for joinChain
*******************************************************************************/
public void setJoinChain(List<String> joinChain)
{
this.joinChain = joinChain;
}
/*******************************************************************************
** Fluent setter for joinChain
*******************************************************************************/
public RecordSecurityLock withJoinChain(List<String> joinChain)
{
this.joinChain = joinChain;
return (this);
}
}

View File

@ -41,6 +41,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf;
@ -49,7 +52,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf;
** Meta-Data to define a table in a QQQ instance.
**
*******************************************************************************/
public class QTableMetaData implements QAppChildMetaData, Serializable
public class QTableMetaData implements QAppChildMetaData, Serializable, MetaDataWithPermissionRules
{
private String name;
private String label;
@ -69,6 +72,9 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
private Map<String, QFieldMetaData> fields;
private List<UniqueKey> uniqueKeys;
private List<RecordSecurityLock> recordSecurityLocks;
private QPermissionRules permissionRules;
private QTableBackendDetails backendDetails;
private QTableAutomationDetails automationDetails;
@ -1041,7 +1047,12 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
/*******************************************************************************
** Test if a capability is enabled - checking both at the table level and
** at the backend level.
**
** If backend says disabled, then disable - UNLESS - the table says enable.
** If backend either doesn't specify, or says enable, return what the table says (if it says).
** else, return the default (of enabled).
*******************************************************************************/
public boolean isCapabilityEnabled(QBackendMetaData backend, Capability capability)
{
@ -1078,4 +1089,82 @@ public class QTableMetaData implements QAppChildMetaData, Serializable
return (hasCapability);
}
/*******************************************************************************
** Getter for recordSecurityLocks
*******************************************************************************/
public List<RecordSecurityLock> getRecordSecurityLocks()
{
return (this.recordSecurityLocks);
}
/*******************************************************************************
** Setter for recordSecurityLocks
*******************************************************************************/
public void setRecordSecurityLocks(List<RecordSecurityLock> recordSecurityLocks)
{
this.recordSecurityLocks = recordSecurityLocks;
}
/*******************************************************************************
** Fluent setter for recordSecurityLocks
*******************************************************************************/
public QTableMetaData withRecordSecurityLocks(List<RecordSecurityLock> recordSecurityLocks)
{
this.recordSecurityLocks = recordSecurityLocks;
return (this);
}
/*******************************************************************************
** Fluent setter for recordSecurityLocks
*******************************************************************************/
public QTableMetaData withRecordSecurityLock(RecordSecurityLock recordSecurityLock)
{
if(this.recordSecurityLocks == null)
{
this.recordSecurityLocks = new ArrayList<>();
}
this.recordSecurityLocks.add(recordSecurityLock);
return (this);
}
/*******************************************************************************
** Getter for permissionRules
*******************************************************************************/
public QPermissionRules getPermissionRules()
{
return (this.permissionRules);
}
/*******************************************************************************
** Setter for permissionRules
*******************************************************************************/
public void setPermissionRules(QPermissionRules permissionRules)
{
this.permissionRules = permissionRules;
}
/*******************************************************************************
** Fluent setter for permissionRules
*******************************************************************************/
public QTableMetaData withPermissionRules(QPermissionRules permissionRules)
{
this.permissionRules = permissionRules;
return (this);
}
}

View File

@ -23,9 +23,19 @@ package com.kingsrook.qqq.backend.core.model.session;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.core.utils.collections.MutableMap;
/*******************************************************************************
@ -37,6 +47,9 @@ public class QSession implements Serializable
private QUser user;
private String uuid;
private Map<String, List<Serializable>> securityKeyValues;
private Set<String> permissions;
// implementation-specific custom values
private Map<String, String> values;
@ -179,4 +192,264 @@ public class QSession implements Serializable
{
this.uuid = uuid;
}
/*******************************************************************************
** Getter for securityKeyValues
*******************************************************************************/
public Map<String, List<Serializable>> getSecurityKeyValues()
{
return (this.securityKeyValues);
}
/*******************************************************************************
** Getter for securityKeyValues - the list under a given key - never null.
*******************************************************************************/
public List<Serializable> getSecurityKeyValues(String keyName)
{
if(securityKeyValues == null)
{
return (new ArrayList<>());
}
return (Objects.requireNonNullElseGet(securityKeyValues.get(keyName), ArrayList::new));
}
/*******************************************************************************
** Getter for securityKeyValues - the list under a given key - as the expected tye - never null.
*******************************************************************************/
public List<Serializable> getSecurityKeyValues(String keyName, QFieldType type)
{
if(securityKeyValues == null)
{
return (new ArrayList<>());
}
List<Serializable> rawValues = securityKeyValues.get(keyName);
if(rawValues == null)
{
return (new ArrayList<>());
}
List<Serializable> valuesAsType = new ArrayList<>();
for(Serializable rawValue : rawValues)
{
valuesAsType.add(ValueUtils.getValueAsFieldType(type, rawValue));
}
return (valuesAsType);
}
/*******************************************************************************
** Test if this session has a given value for a given key
*******************************************************************************/
public boolean hasSecurityKeyValue(String keyName, Serializable value)
{
if(securityKeyValues == null)
{
return (false);
}
if(!securityKeyValues.containsKey(keyName))
{
return (false);
}
List<Serializable> values = securityKeyValues.get(keyName);
return (values != null && values.contains(value));
}
/*******************************************************************************
**
*******************************************************************************/
public boolean hasSecurityKeyValue(String keyName, Serializable value, QFieldType fieldType)
{
if(securityKeyValues == null)
{
return (false);
}
if(!securityKeyValues.containsKey(keyName))
{
return (false);
}
List<Serializable> values = securityKeyValues.get(keyName);
Serializable valueAsType = ValueUtils.getValueAsFieldType(fieldType, value);
for(Serializable keyValue : values)
{
Serializable keyValueAsType = ValueUtils.getValueAsFieldType(fieldType, keyValue);
if(keyValueAsType.equals(valueAsType))
{
return (true);
}
}
return (false);
}
/*******************************************************************************
** Setter for securityKeyValues
*******************************************************************************/
public void setSecurityKeyValues(Map<String, List<Serializable>> securityKeyValues)
{
this.securityKeyValues = new MutableMap<>(securityKeyValues);
}
/*******************************************************************************
** Fluent setter for securityKeyValues - replaces the map.
*******************************************************************************/
public QSession withSecurityKeyValues(Map<String, List<Serializable>> securityKeyValues)
{
this.securityKeyValues = new MutableMap<>(securityKeyValues);
return (this);
}
/*******************************************************************************
** Fluent setter for securityKeyValues - add a list of values for 1 key
*******************************************************************************/
public QSession withSecurityKeyValues(String keyName, List<Serializable> values)
{
if(values == null)
{
return (this);
}
if(securityKeyValues == null)
{
securityKeyValues = new HashMap<>();
}
securityKeyValues.computeIfAbsent(keyName, (k) -> new ArrayList<>());
try
{
securityKeyValues.get(keyName).addAll(values);
}
catch(UnsupportedOperationException uoe)
{
securityKeyValues.put(keyName, new ArrayList<>(securityKeyValues.get(keyName)));
securityKeyValues.get(keyName).addAll(values);
}
return (this);
}
/*******************************************************************************
** Fluent setter for securityKeyValues - add 1 value for 1 key.
*******************************************************************************/
public QSession withSecurityKeyValue(String keyName, Serializable value)
{
return (withSecurityKeyValues(keyName, List.of(value)));
}
/*******************************************************************************
** Clear the map of security key values in the session.
*******************************************************************************/
public void clearSecurityKeyValues()
{
if(securityKeyValues != null)
{
securityKeyValues.clear();
}
}
/*******************************************************************************
** Setter for permissions
**
*******************************************************************************/
public void setPermissions(Set<String> permissions)
{
this.permissions = permissions;
}
/*******************************************************************************
**
*******************************************************************************/
public QSession withPermission(String permission)
{
if(this.permissions == null)
{
this.permissions = new HashSet<>();
}
this.permissions.add(permission);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public QSession withPermissions(String... permissionNames)
{
if(this.permissions == null)
{
this.permissions = new HashSet<>();
}
Collections.addAll(this.permissions, permissionNames);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public QSession withPermissions(Collection<String> permissionNames)
{
if(this.permissions == null)
{
this.permissions = new HashSet<>();
}
this.permissions.addAll(permissionNames);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public boolean hasPermission(String permissionName)
{
return (permissions != null && permissions.contains(permissionName));
}
/*******************************************************************************
** Getter for permissions
*******************************************************************************/
public Set<String> getPermissions()
{
return (this.permissions);
}
}

View File

@ -73,4 +73,15 @@ public class QUser
{
this.fullName = fullName;
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String toString()
{
return ("QUser{" + idReference + "," + fullName + "}");
}
}

View File

@ -44,4 +44,14 @@ public interface QAuthenticationModuleInterface
**
*******************************************************************************/
boolean isSessionValid(QInstance instance, QSession session);
/*******************************************************************************
**
*******************************************************************************/
default boolean usesSessionIdCookie()
{
return (false);
}
}

View File

@ -28,8 +28,11 @@ import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.auth0.client.auth.AuthAPI;
import com.auth0.exception.Auth0Exception;
import com.auth0.json.auth.TokenHolder;
@ -53,8 +56,10 @@ import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModu
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
import com.kingsrook.qqq.backend.core.state.SimpleStateKey;
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONObject;
@ -70,11 +75,11 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static final int ID_TOKEN_VALIDATION_INTERVAL_SECONDS = 1800;
public static final String AUTH0_ID_TOKEN_KEY = "sessionId";
public static final String BASIC_AUTH_KEY = "basicAuthString";
public static final String AUTH0_ACCESS_TOKEN_KEY = "sessionId";
public static final String BASIC_AUTH_KEY = "basicAuthString";
public static final String TOKEN_NOT_PROVIDED_ERROR = "Id Token was not provided";
public static final String COULD_NOT_DECODE_ERROR = "Unable to decode id token";
public static final String TOKEN_NOT_PROVIDED_ERROR = "Access Token was not provided";
public static final String COULD_NOT_DECODE_ERROR = "Unable to decode access token";
public static final String EXPIRED_TOKEN_ERROR = "Token has expired";
public static final String INVALID_TOKEN_ERROR = "An invalid token was provided";
@ -102,8 +107,8 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
// decode the credentials from the header auth //
/////////////////////////////////////////////////
String base64Credentials = context.get(BASIC_AUTH_KEY).trim();
String idToken = getIdTokenFromBase64BasicAuthCredentials(auth, base64Credentials);
context.put(AUTH0_ID_TOKEN_KEY, idToken);
String accessToken = getAccessTokenFromBase64BasicAuthCredentials(metaData, auth, base64Credentials);
context.put(AUTH0_ACCESS_TOKEN_KEY, accessToken);
}
catch(Auth0Exception e)
{
@ -116,11 +121,11 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
}
}
//////////////////////////////////////////////////
// get the jwt id token from the context object //
//////////////////////////////////////////////////
String idToken = context.get(AUTH0_ID_TOKEN_KEY);
if(idToken == null)
//////////////////////////////////////////////////////
// get the jwt access token from the context object //
//////////////////////////////////////////////////////
String accessToken = context.get(AUTH0_ACCESS_TOKEN_KEY);
if(accessToken == null)
{
LOG.warn(TOKEN_NOT_PROVIDED_ERROR);
throw (new QAuthenticationException(TOKEN_NOT_PROVIDED_ERROR));
@ -135,7 +140,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
// try to build session to see if still valid //
// then call method to check more session validity //
/////////////////////////////////////////////////////
QSession qSession = buildQSessionFromToken(idToken);
QSession qSession = buildQSessionFromToken(accessToken, qInstance);
if(isSessionValid(qInstance, qSession))
{
return (qSession);
@ -145,7 +150,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
// if we make it here it means we have never validated this token or its been a long //
// enough duration so we need to re-verify the token //
///////////////////////////////////////////////////////////////////////////////////////
qSession = revalidateToken(qInstance, idToken);
qSession = revalidateToken(qInstance, accessToken);
////////////////////////////////////////////////////////////////////
// put now into state so we dont check until next interval passes //
@ -193,34 +198,34 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
/*******************************************************************************
**
*******************************************************************************/
private String getIdTokenFromBase64BasicAuthCredentials(AuthAPI auth, String base64Credentials) throws Auth0Exception
private String getAccessTokenFromBase64BasicAuthCredentials(Auth0AuthenticationMetaData metaData, AuthAPI auth, String base64Credentials) throws Auth0Exception
{
////////////////////////////////////////////////////////////////////////////////
// look for a fresh idToken in the state provider for this set of credentials //
////////////////////////////////////////////////////////////////////////////////
SimpleStateKey<String> idTokenStateKey = new SimpleStateKey<>(base64Credentials + ":idToken");
SimpleStateKey<String> timestampStateKey = new SimpleStateKey<>(base64Credentials + ":timestamp");
StateProviderInterface stateProvider = getStateProvider();
Optional<String> cachedIdToken = stateProvider.get(String.class, idTokenStateKey);
Optional<Instant> cachedTimestamp = stateProvider.get(Instant.class, timestampStateKey);
if(cachedIdToken.isPresent() && cachedTimestamp.isPresent())
////////////////////////////////////////////////////////////////////////////////////
// look for a fresh accessToken in the state provider for this set of credentials //
////////////////////////////////////////////////////////////////////////////////////
SimpleStateKey<String> accessTokenStateKey = new SimpleStateKey<>(base64Credentials + ":accessToken");
SimpleStateKey<String> timestampStateKey = new SimpleStateKey<>(base64Credentials + ":timestamp");
StateProviderInterface stateProvider = getStateProvider();
Optional<String> cachedAccessToken = stateProvider.get(String.class, accessTokenStateKey);
Optional<Instant> cachedTimestamp = stateProvider.get(Instant.class, timestampStateKey);
if(cachedAccessToken.isPresent() && cachedTimestamp.isPresent())
{
if(cachedTimestamp.get().isAfter(Instant.now().minus(1, ChronoUnit.MINUTES)))
{
return cachedIdToken.get();
return cachedAccessToken.get();
}
}
//////////////////////////////////////////////////////////////////////////////
// not found in cache, make request to auth0 and cache the returned idToken //
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
// not found in cache, make request to auth0 and cache the returned accessToken //
//////////////////////////////////////////////////////////////////////////////////
byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
String credentials = new String(credDecoded, StandardCharsets.UTF_8);
String idToken = getIdTokenFromAuth0(auth, credentials);
stateProvider.put(idTokenStateKey, idToken);
String accessToken = getAccessTokenFromAuth0(metaData, auth, credentials);
stateProvider.put(accessTokenStateKey, accessToken);
stateProvider.put(timestampStateKey, Instant.now());
return (idToken);
return (accessToken);
}
@ -228,16 +233,17 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
/*******************************************************************************
**
*******************************************************************************/
protected String getIdTokenFromAuth0(AuthAPI auth, String credentials) throws Auth0Exception
protected String getAccessTokenFromAuth0(Auth0AuthenticationMetaData metaData, AuthAPI auth, String credentials) throws Auth0Exception
{
/////////////////////////////////////
// call auth0 with a login request //
/////////////////////////////////////
TokenHolder result = auth.login(credentials.split(":")[0], credentials.split(":")[1].toCharArray())
.setScope("openid email nickname")
.setAudience(metaData.getAudience())
.execute();
return (result.getIdToken());
return (result.getAccessToken());
}
@ -303,11 +309,11 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
** makes request to check if a token is still valid and build new qSession if it is
**
*******************************************************************************/
private QSession revalidateToken(QInstance qInstance, String idToken) throws JwkException
private QSession revalidateToken(QInstance qInstance, String accessToken) throws JwkException
{
Auth0AuthenticationMetaData metaData = (Auth0AuthenticationMetaData) qInstance.getAuthentication();
DecodedJWT jwt = JWT.decode(idToken);
DecodedJWT jwt = JWT.decode(accessToken);
JwkProvider provider = new UrlJwkProvider(metaData.getBaseUrl());
Jwk jwk = provider.get(jwt.getKeyId());
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
@ -318,9 +324,9 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
///////////////////////////////////
// make call to verify the token //
///////////////////////////////////
verifier.verify(idToken);
verifier.verify(accessToken);
return (buildQSessionFromToken(idToken));
return (buildQSessionFromToken(accessToken, qInstance));
}
@ -329,52 +335,181 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
** extracts info from token creating a QSession
**
*******************************************************************************/
private QSession buildQSessionFromToken(String idToken) throws JwkException
private QSession buildQSessionFromToken(String accessToken, QInstance qInstance) throws JwkException
{
////////////////////////////////////
// decode and extract the payload //
////////////////////////////////////
DecodedJWT jwt = JWT.decode(idToken);
DecodedJWT jwt = JWT.decode(accessToken);
Base64.Decoder decoder = Base64.getUrlDecoder();
String payloadString = new String(decoder.decode(jwt.getPayload()));
JSONObject payload = new JSONObject(payloadString);
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// create user object. look for multiple possible keys in the jwt payload where the name & email may be //
///////////////////////////////////////////////////////////////////////////////////////////////////////////
QUser qUser = new QUser();
if(payload.has("name"))
qUser.setFullName("Unknown");
for(String key : List.of("name", "com.kingsrook.qqq.name"))
{
qUser.setFullName(payload.getString("name"));
}
else
{
qUser.setFullName("Unknown");
}
if(payload.has("email"))
{
qUser.setIdReference(payload.getString("email"));
}
else
{
if(payload.has("sub"))
if(payload.has(key))
{
qUser.setIdReference(payload.getString("sub"));
qUser.setFullName(payload.getString(key));
break;
}
}
for(String key : List.of("email", "com.kingsrook.qqq.email", "sub"))
{
if(payload.has(key))
{
qUser.setIdReference(payload.getString(key));
break;
}
}
/////////////////////////////////////////////////////////
// create session object - link to access token & user //
/////////////////////////////////////////////////////////
QSession qSession = new QSession();
qSession.setIdReference(idToken);
qSession.setIdReference(accessToken);
qSession.setUser(qUser);
/////////////////////////////////////////////////
// set permissions in the session from the JWT //
/////////////////////////////////////////////////
setPermissionsInSessionFromJwtPayload(payload, qSession);
///////////////////////////////////////////////////
// set security keys in the session from the JWT //
///////////////////////////////////////////////////
setSecurityKeysInSessionFromJwtPayload(qInstance, payload, qSession);
return (qSession);
}
/*******************************************************************************
**
*******************************************************************************/
static void setPermissionsInSessionFromJwtPayload(JSONObject payload, QSession qSession)
{
HashSet<String> permissions = new HashSet<>();
if(payload.has("permissions"))
{
try
{
JSONArray jwtPermissions = payload.getJSONArray("permissions");
for(int i = 0; i < jwtPermissions.length(); i++)
{
permissions.add(jwtPermissions.optString(i));
}
}
catch(Exception e)
{
LOG.error("Error getting permissions from JWT", e);
}
}
qSession.setPermissions(permissions);
}
/*******************************************************************************
**
*******************************************************************************/
static void setSecurityKeysInSessionFromJwtPayload(QInstance qInstance, JSONObject payload, QSession qSession)
{
for(String payloadKey : List.of("com.kingsrook.qqq.app_metadata", "com.kingsrook.qqq.client_metadata"))
{
if(!payload.has(payloadKey))
{
continue;
}
try
{
JSONObject appMetadata = payload.getJSONObject(payloadKey);
Set<String> allowedSecurityKeyNames = qInstance.getAllowedSecurityKeyNames();
//////////////////////////////////////////////////////////////////////////////////
// for users, they will have a map of securityKeyValues (in their app_metadata) //
//////////////////////////////////////////////////////////////////////////////////
JSONObject securityKeyValues = appMetadata.optJSONObject("securityKeyValues");
if(securityKeyValues != null)
{
for(String keyName : securityKeyValues.keySet())
{
setSecurityKeyValuesFromToken(allowedSecurityKeyNames, qSession, keyName, securityKeyValues, keyName);
}
}
else
{
//////////////////////////////////////////////////////////////////////////////////////////////////
// for system-logins, there will be keys prefixed by securityKeyValues: (under client_metadata) //
//////////////////////////////////////////////////////////////////////////////////////////////////
for(String appMetaDataKey : appMetadata.keySet())
{
if(appMetaDataKey.startsWith("securityKeyValues:"))
{
String securityKeyName = appMetaDataKey.replace("securityKeyValues:", "");
setSecurityKeyValuesFromToken(allowedSecurityKeyNames, qSession, securityKeyName, appMetadata, appMetaDataKey);
}
}
}
}
catch(Exception e)
{
LOG.error("Error getting securityKey values from app_metadata from JWT", e);
}
}
}
/*******************************************************************************
**
*******************************************************************************/
private static void setSecurityKeyValuesFromToken(Set<String> allowedSecurityKeyNames, QSession qSession, String securityKeyName, JSONObject securityKeyValues, String jsonKey)
{
if(!allowedSecurityKeyNames.contains(securityKeyName))
{
QUser user = qSession.getUser();
LOG.warn("Unrecognized security key name [" + securityKeyName + "] when creating session for user [" + user + "]. Allowed key names are: " + allowedSecurityKeyNames);
return;
}
JSONArray valueArray = securityKeyValues.optJSONArray(jsonKey);
if(valueArray != null)
{
// todo - types?
for(int i = 0; i < valueArray.length(); i++)
{
Object optValue = valueArray.opt(i);
if(optValue != null)
{
qSession.withSecurityKeyValue(securityKeyName, ValueUtils.getValueAsString(optValue));
}
}
}
else
{
String value = securityKeyValues.optString(jsonKey);
if(value != null)
{
qSession.withSecurityKeyValue(securityKeyName, value);
}
}
}
/*******************************************************************************
** Load an instance of the appropriate state provider
**
*******************************************************************************/
public static StateProviderInterface getStateProvider()
private static StateProviderInterface getStateProvider()
{
// TODO - read this from somewhere in meta data eh?
return (InMemoryStateProvider.getInstance());

View File

@ -51,8 +51,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.authentication.TableBasedAu
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.model.session.QUser;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
import com.kingsrook.qqq.backend.core.state.AbstractStateKey;
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
import com.kingsrook.qqq.backend.core.state.SimpleStateKey;
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import org.apache.logging.log4j.LogManager;
@ -87,6 +87,17 @@ public class TableBasedAuthenticationModule implements QAuthenticationModuleInte
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean usesSessionIdCookie()
{
return (true);
}
/*******************************************************************************
**
*******************************************************************************/
@ -203,7 +214,7 @@ public class TableBasedAuthenticationModule implements QAuthenticationModuleInte
// put now into state so we dont check until next interval passes //
///////////////////////////////////////////////////////////////////
StateProviderInterface spi = getStateProvider();
SessionIdStateKey key = new SessionIdStateKey(qSession.getIdReference());
SimpleStateKey<String> key = new SimpleStateKey<>(qSession.getIdReference());
spi.put(key, Instant.now());
return (qSession);
@ -251,7 +262,7 @@ public class TableBasedAuthenticationModule implements QAuthenticationModuleInte
}
StateProviderInterface stateProvider = getStateProvider();
SessionIdStateKey key = new SessionIdStateKey(session.getIdReference());
SimpleStateKey<String> key = new SimpleStateKey<>(session.getIdReference());
Optional<Instant> lastTimeCheckedOptional = stateProvider.get(Instant.class, key);
if(lastTimeCheckedOptional.isPresent())
{
@ -392,81 +403,6 @@ public class TableBasedAuthenticationModule implements QAuthenticationModuleInte
/*******************************************************************************
**
*******************************************************************************/
public static class SessionIdStateKey extends AbstractStateKey
{
private final String key;
/*******************************************************************************
** Constructor.
**
*******************************************************************************/
SessionIdStateKey(String key)
{
this.key = key;
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String toString()
{
return (this.key);
}
/*******************************************************************************
** Make the key give a unique string to identify itself.
*
*******************************************************************************/
@Override
public String getUniqueIdentifier()
{
return (this.key);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if(o == null || getClass() != o.getClass())
{
return false;
}
SessionIdStateKey that = (SessionIdStateKey) o;
return key.equals(that.key);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public int hashCode()
{
return key.hashCode();
}
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -146,6 +146,7 @@ public class MemoryRecordStore
}
BackendQueryFilterUtils.sortRecordList(input.getFilter(), records);
records = BackendQueryFilterUtils.applySkipAndLimit(input, records);
return (records);
}

View File

@ -82,6 +82,7 @@ public class BackendQueryFilterUtils
case IS_NOT_BLANK -> !testBlank(criterion, value);
case CONTAINS -> testContains(criterion, fieldName, value);
case NOT_CONTAINS -> !testContains(criterion, fieldName, value);
case IS_NULL_OR_IN -> testBlank(criterion, value) || testIn(criterion, value);
case STARTS_WITH -> testStartsWith(criterion, fieldName, value);
case NOT_STARTS_WITH -> !testStartsWith(criterion, fieldName, value);
case ENDS_WITH -> testEndsWith(criterion, fieldName, value);
@ -438,13 +439,13 @@ public class BackendQueryFilterUtils
{
continue;
}
else if(isGreaterThan(valueA, valueB) && orderBy.getIsAscending())
else if(isGreaterThan(valueA, valueB))
{
return (-1);
return (orderBy.getIsAscending() ? -1 : 1);
}
else
else // Less Than
{
return (1);
return (orderBy.getIsAscending() ? 1 : -1);
}
}

View File

@ -0,0 +1,374 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. 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.utils.collections;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Supplier;
import com.kingsrook.qqq.backend.core.utils.lambdas.VoidVoidMethod;
/*******************************************************************************
** Object to wrap a List, so that in case a caller provided an immutable List,
** you can safely perform mutating operations on it (in which case, it'll get
** replaced by an actual mutable list).
*******************************************************************************/
public class MutableList<T> implements List<T>
{
private List<T> sourceList;
private Class<? extends List<T>> mutableTypeIfNeeded;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public MutableList(List<T> sourceList)
{
this(sourceList, (Class) ArrayList.class);
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public MutableList(List<T> sourceList, Class<? extends List<T>> mutableTypeIfNeeded)
{
this.sourceList = sourceList;
this.mutableTypeIfNeeded = mutableTypeIfNeeded;
}
/*******************************************************************************
**
*******************************************************************************/
private void replaceSourceListWithMutableCopy()
{
try
{
List<T> replacementList = mutableTypeIfNeeded.getConstructor().newInstance();
replacementList.addAll(sourceList);
sourceList = replacementList;
}
catch(Exception e)
{
throw (new IllegalStateException("The mutable type provided for this MutableList [" + mutableTypeIfNeeded.getName() + "] could not be instantiated."));
}
}
/*******************************************************************************
**
*******************************************************************************/
private <T> T doMutableOperationForValue(Supplier<T> supplier)
{
try
{
return (supplier.get());
}
catch(UnsupportedOperationException uoe)
{
replaceSourceListWithMutableCopy();
return (supplier.get());
}
}
/*******************************************************************************
**
*******************************************************************************/
private void doMutableOperationForVoid(VoidVoidMethod method)
{
try
{
method.run();
}
catch(UnsupportedOperationException uoe)
{
replaceSourceListWithMutableCopy();
method.run();
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public int size()
{
return (sourceList.size());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean isEmpty()
{
return (sourceList.isEmpty());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean contains(Object o)
{
return (sourceList.contains(o));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Iterator<T> iterator()
{
return (sourceList.iterator());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Object[] toArray()
{
return (sourceList.toArray());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public <T1> T1[] toArray(T1[] a)
{
return (sourceList.toArray(a));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean add(T t)
{
return (doMutableOperationForValue(() -> sourceList.add(t)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean remove(Object o)
{
return (doMutableOperationForValue(() -> sourceList.remove(o)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean containsAll(Collection<?> c)
{
return (sourceList.containsAll(c));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean addAll(Collection<? extends T> c)
{
return (doMutableOperationForValue(() -> sourceList.addAll(c)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean addAll(int index, Collection<? extends T> c)
{
return (doMutableOperationForValue(() -> sourceList.addAll(index, c)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean removeAll(Collection<?> c)
{
return (doMutableOperationForValue(() -> sourceList.removeAll(c)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean retainAll(Collection<?> c)
{
return (doMutableOperationForValue(() -> sourceList.retainAll(c)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void clear()
{
doMutableOperationForVoid(() -> sourceList.clear());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public T get(int index)
{
return (sourceList.get(index));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public T set(int index, T element)
{
return (doMutableOperationForValue(() -> sourceList.set(index, element)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void add(int index, T element)
{
doMutableOperationForVoid(() -> sourceList.add(index, element));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public T remove(int index)
{
return (doMutableOperationForValue(() -> sourceList.remove(index)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public int indexOf(Object o)
{
return (sourceList.indexOf(o));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public int lastIndexOf(Object o)
{
return (sourceList.lastIndexOf(o));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public ListIterator<T> listIterator()
{
return (sourceList.listIterator());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public ListIterator<T> listIterator(int index)
{
return (sourceList.listIterator(index));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<T> subList(int fromIndex, int toIndex)
{
return (sourceList.subList(fromIndex, toIndex));
}
}

View File

@ -0,0 +1,252 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. 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.utils.collections;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import com.kingsrook.qqq.backend.core.utils.lambdas.VoidVoidMethod;
/*******************************************************************************
** Object to wrap a Map, so that in case a caller provided an immutable Map,
** you can safely perform mutating operations on it (in which case, it'll get
** replaced by an actual mutable Map).
*******************************************************************************/
public class MutableMap<K, V> implements Map<K, V>
{
private Map<K, V> sourceMap;
private Class<? extends Map<K, V>> mutableTypeIfNeeded;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public MutableMap(Map<K, V> sourceMap)
{
this(sourceMap, (Class) HashMap.class);
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public MutableMap(Map<K, V> sourceMap, Class<? extends Map<K, V>> mutableTypeIfNeeded)
{
this.sourceMap = sourceMap;
this.mutableTypeIfNeeded = mutableTypeIfNeeded;
}
/*******************************************************************************
**
*******************************************************************************/
private void replaceSourceMapWithMutableCopy()
{
try
{
Map<K, V> replacementMap = mutableTypeIfNeeded.getConstructor().newInstance();
replacementMap.putAll(sourceMap);
sourceMap = replacementMap;
}
catch(Exception e)
{
throw (new IllegalStateException("The mutable type provided for this MutableMap [" + mutableTypeIfNeeded.getName() + "] could not be instantiated."));
}
}
/*******************************************************************************
**
*******************************************************************************/
private <T> T doMutableOperationForValue(Supplier<T> supplier)
{
try
{
return (supplier.get());
}
catch(UnsupportedOperationException uoe)
{
replaceSourceMapWithMutableCopy();
return (supplier.get());
}
}
/*******************************************************************************
**
*******************************************************************************/
private void doMutableOperationForVoid(VoidVoidMethod method)
{
try
{
method.run();
}
catch(UnsupportedOperationException uoe)
{
replaceSourceMapWithMutableCopy();
method.run();
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public int size()
{
return (sourceMap.size());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean isEmpty()
{
return (sourceMap.isEmpty());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean containsKey(Object key)
{
return (sourceMap.containsKey(key));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean containsValue(Object value)
{
return (sourceMap.containsValue(value));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public V get(Object key)
{
return (sourceMap.get(key));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public V put(K key, V value)
{
return (doMutableOperationForValue(() -> sourceMap.put(key, value)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public V remove(Object key)
{
return (doMutableOperationForValue(() -> sourceMap.remove(key)));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void putAll(Map<? extends K, ? extends V> m)
{
doMutableOperationForVoid(() -> sourceMap.putAll(m));
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void clear()
{
doMutableOperationForVoid(() -> sourceMap.clear());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Set<K> keySet()
{
return (sourceMap.keySet());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Collection<V> values()
{
return (sourceMap.values());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Set<Entry<K, V>> entrySet()
{
return (sourceMap.entrySet());
}
}

View File

@ -0,0 +1,37 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. 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.utils.lambdas;
/*******************************************************************************
**
*******************************************************************************/
@FunctionalInterface
public interface VoidVoidMethod
{
/*******************************************************************************
**
*******************************************************************************/
void run();
}