mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-20 14:10:44 +00:00
Compare commits
1 Commits
snapshot-f
...
snapshot-f
Author | SHA1 | Date | |
---|---|---|---|
dfb0f3ddf1 |
@ -84,7 +84,7 @@ public class MetaDataAction
|
|||||||
}
|
}
|
||||||
|
|
||||||
QBackendMetaData backendForTable = metaDataInput.getInstance().getBackendForTable(tableName);
|
QBackendMetaData backendForTable = metaDataInput.getInstance().getBackendForTable(tableName);
|
||||||
tables.put(tableName, new QFrontendTableMetaData(metaDataInput, backendForTable, table, false, false));
|
tables.put(tableName, new QFrontendTableMetaData(backendForTable, table, false, false));
|
||||||
treeNodes.put(tableName, new AppTreeNode(table));
|
treeNodes.put(tableName, new AppTreeNode(table));
|
||||||
}
|
}
|
||||||
metaDataOutput.setTables(tables);
|
metaDataOutput.setTables(tables);
|
||||||
|
@ -54,7 +54,7 @@ public class TableMetaDataAction
|
|||||||
throw (new QNotFoundException("Table [" + tableMetaDataInput.getTableName() + "] was not found."));
|
throw (new QNotFoundException("Table [" + tableMetaDataInput.getTableName() + "] was not found."));
|
||||||
}
|
}
|
||||||
QBackendMetaData backendForTable = tableMetaDataInput.getInstance().getBackendForTable(table.getName());
|
QBackendMetaData backendForTable = tableMetaDataInput.getInstance().getBackendForTable(table.getName());
|
||||||
tableMetaDataOutput.setTable(new QFrontendTableMetaData(tableMetaDataInput, backendForTable, table, true, true));
|
tableMetaDataOutput.setTable(new QFrontendTableMetaData(backendForTable, table, true, true));
|
||||||
|
|
||||||
// todo post-customization - can do whatever w/ the result if you want
|
// todo post-customization - can do whatever w/ the result if you want
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ public class PermissionsHelper
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static void checkTablePermissionThrowing(AbstractTableActionInput tableActionInput, TablePermissionSubType permissionSubType) throws QPermissionDeniedException
|
public static void checkTablePermissionThrowing(AbstractTableActionInput tableActionInput, TablePermissionSubType permissionSubType) throws QPermissionDeniedException
|
||||||
{
|
{
|
||||||
checkTablePermissionThrowing(tableActionInput, tableActionInput.getTableName(), permissionSubType);
|
checkTablePermissionThrowing(tableActionInput.getTableName(), permissionSubType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ public class PermissionsHelper
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private static void checkTablePermissionThrowing(AbstractActionInput actionInput, String tableName, TablePermissionSubType permissionSubType) throws QPermissionDeniedException
|
private static void checkTablePermissionThrowing(String tableName, TablePermissionSubType permissionSubType) throws QPermissionDeniedException
|
||||||
{
|
{
|
||||||
warnAboutPermissionSubTypeForTables(permissionSubType);
|
warnAboutPermissionSubTypeForTables(permissionSubType);
|
||||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||||
@ -99,11 +99,11 @@ public class PermissionsHelper
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static boolean hasTablePermission(AbstractActionInput actionInput, String tableName, TablePermissionSubType permissionSubType)
|
public static boolean hasTablePermission(String tableName, TablePermissionSubType permissionSubType)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
checkTablePermissionThrowing(actionInput, tableName, permissionSubType);
|
checkTablePermissionThrowing(tableName, permissionSubType);
|
||||||
return (true);
|
return (true);
|
||||||
}
|
}
|
||||||
catch(QPermissionDeniedException e)
|
catch(QPermissionDeniedException e)
|
||||||
|
@ -914,7 +914,7 @@ public class QInstanceValidator
|
|||||||
}
|
}
|
||||||
|
|
||||||
assertCondition(fieldSecurityLock.getDefaultBehavior() != null, prefix + "has a fieldSecurityLock that is missing a defaultBehavior");
|
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");
|
assertCondition(CollectionUtils.nullSafeHasContents(fieldSecurityLock.getKeyValueBehaviors()), prefix + "has a fieldSecurityLock that is missing keyValueBehaviors");
|
||||||
}
|
}
|
||||||
|
|
||||||
for(FieldAdornment adornment : CollectionUtils.nonNullList(field.getAdornments()))
|
for(FieldAdornment adornment : CollectionUtils.nonNullList(field.getAdornments()))
|
||||||
|
@ -35,16 +35,17 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
|||||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
|
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
|
||||||
import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType;
|
import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
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.QBackendMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QSupplementalTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QSupplementalTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
|
|
||||||
|
|
||||||
@ -87,12 +88,14 @@ public class QFrontendTableMetaData
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public QFrontendTableMetaData(AbstractActionInput actionInput, QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFullMetaData, boolean includeJoins)
|
public QFrontendTableMetaData(QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFullMetaData, boolean includeJoins)
|
||||||
{
|
{
|
||||||
this.name = tableMetaData.getName();
|
this.name = tableMetaData.getName();
|
||||||
this.label = tableMetaData.getLabel();
|
this.label = tableMetaData.getLabel();
|
||||||
this.isHidden = tableMetaData.getIsHidden();
|
this.isHidden = tableMetaData.getIsHidden();
|
||||||
|
|
||||||
|
QSession qSession = QContext.getQSession();
|
||||||
|
|
||||||
if(includeFullMetaData)
|
if(includeFullMetaData)
|
||||||
{
|
{
|
||||||
this.primaryKeyField = tableMetaData.getPrimaryKeyField();
|
this.primaryKeyField = tableMetaData.getPrimaryKeyField();
|
||||||
@ -100,7 +103,21 @@ public class QFrontendTableMetaData
|
|||||||
for(String fieldName : tableMetaData.getFields().keySet())
|
for(String fieldName : tableMetaData.getFields().keySet())
|
||||||
{
|
{
|
||||||
QFieldMetaData field = tableMetaData.getField(fieldName);
|
QFieldMetaData field = tableMetaData.getField(fieldName);
|
||||||
if(!field.getIsHidden())
|
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
// apply field security lock behaviors, if applicable //
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
boolean isDenied = false;
|
||||||
|
if(field.getFieldSecurityLock() != null)
|
||||||
|
{
|
||||||
|
FieldSecurityLock.Behavior behavior = field.getFieldSecurityLock().getBehaviorForSession(qSession);
|
||||||
|
if(FieldSecurityLock.Behavior.DENY.equals(behavior))
|
||||||
|
{
|
||||||
|
isDenied = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!field.getIsHidden() && !isDenied)
|
||||||
{
|
{
|
||||||
this.fields.put(fieldName, new QFrontendFieldMetaData(field));
|
this.fields.put(fieldName, new QFrontendFieldMetaData(field));
|
||||||
}
|
}
|
||||||
@ -124,7 +141,7 @@ public class QFrontendTableMetaData
|
|||||||
QTableMetaData joinTable = qInstance.getTable(exposedJoin.getJoinTable());
|
QTableMetaData joinTable = qInstance.getTable(exposedJoin.getJoinTable());
|
||||||
frontendExposedJoin.setLabel(exposedJoin.getLabel());
|
frontendExposedJoin.setLabel(exposedJoin.getLabel());
|
||||||
frontendExposedJoin.setIsMany(exposedJoin.getIsMany());
|
frontendExposedJoin.setIsMany(exposedJoin.getIsMany());
|
||||||
frontendExposedJoin.setJoinTable(new QFrontendTableMetaData(actionInput, backendForTable, joinTable, includeFullMetaData, false));
|
frontendExposedJoin.setJoinTable(new QFrontendTableMetaData(backendForTable, joinTable, includeFullMetaData, false));
|
||||||
for(String joinName : exposedJoin.getJoinPath())
|
for(String joinName : exposedJoin.getJoinPath())
|
||||||
{
|
{
|
||||||
frontendExposedJoin.addJoin(qInstance.getJoin(joinName));
|
frontendExposedJoin.addJoin(qInstance.getJoin(joinName));
|
||||||
@ -161,16 +178,16 @@ public class QFrontendTableMetaData
|
|||||||
|
|
||||||
setCapabilities(backendForTable, tableMetaData);
|
setCapabilities(backendForTable, tableMetaData);
|
||||||
|
|
||||||
readPermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.READ);
|
readPermission = PermissionsHelper.hasTablePermission(tableMetaData.getName(), TablePermissionSubType.READ);
|
||||||
insertPermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.INSERT);
|
insertPermission = PermissionsHelper.hasTablePermission(tableMetaData.getName(), TablePermissionSubType.INSERT);
|
||||||
editPermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.EDIT);
|
editPermission = PermissionsHelper.hasTablePermission(tableMetaData.getName(), TablePermissionSubType.EDIT);
|
||||||
deletePermission = PermissionsHelper.hasTablePermission(actionInput, tableMetaData.getName(), TablePermissionSubType.DELETE);
|
deletePermission = PermissionsHelper.hasTablePermission(tableMetaData.getName(), TablePermissionSubType.DELETE);
|
||||||
|
|
||||||
QBackendMetaData backend = actionInput.getInstance().getBackend(tableMetaData.getBackendName());
|
QBackendMetaData backend = QContext.getQInstance().getBackend(tableMetaData.getBackendName());
|
||||||
if(backend != null && backend.getUsesVariants())
|
if(backend != null && backend.getUsesVariants())
|
||||||
{
|
{
|
||||||
usesVariants = true;
|
usesVariants = true;
|
||||||
variantTableLabel = actionInput.getInstance().getTable(backend.getVariantOptionsTableName()).getLabel();
|
variantTableLabel = QContext.getQInstance().getTable(backend.getVariantOptionsTableName()).getLabel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,17 +23,38 @@ package com.kingsrook.qqq.backend.core.model.metadata.security;
|
|||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Define, for a field, a lock that controls if users can or cannot see the field.
|
** Define, for a field, a lock that controls if users can or cannot see the field.
|
||||||
|
**
|
||||||
|
** The lock has a defaultBehavior, which is how the field should be treated, well,
|
||||||
|
** by default.
|
||||||
|
** The lock also references a securityKeyType; whose values, when looked up in
|
||||||
|
** the lock's keyValueBehaviors map, change the default behavior.
|
||||||
|
**
|
||||||
|
** For example, consider a lock with a keyType of 'internalOrExternalUser' (with
|
||||||
|
** possible values of 'internal' and 'external'), a defaultBehavior of DENY,
|
||||||
|
** and a keyValueBehaviors map containing internal => ALLOW. If a session has
|
||||||
|
** no security key of the internalOrExternalUser type, or a key with the value of
|
||||||
|
** 'external', then the lock's behavior will be the default (DENY). However,
|
||||||
|
** a key value of 'internal' would trigger the behavior specified for that key
|
||||||
|
** (ALLOW).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class FieldSecurityLock
|
public class FieldSecurityLock
|
||||||
{
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(FieldSecurityLock.class);
|
||||||
|
|
||||||
private String securityKeyType;
|
private String securityKeyType;
|
||||||
private Behavior defaultBehavior = Behavior.DENY;
|
private Behavior defaultBehavior = Behavior.DENY;
|
||||||
private List<Serializable> overrideValues;
|
|
||||||
|
private Map<Serializable, Behavior> keyValueBehaviors;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -89,7 +110,6 @@ public class FieldSecurityLock
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for defaultBehavior
|
** Getter for defaultBehavior
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -122,33 +142,82 @@ public class FieldSecurityLock
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for overrideValues
|
** Getter for keyValueBehaviors
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public List<Serializable> getOverrideValues()
|
public Map<Serializable, Behavior> getKeyValueBehaviors()
|
||||||
{
|
{
|
||||||
return (this.overrideValues);
|
return (this.keyValueBehaviors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Setter for overrideValues
|
** Setter for keyValueBehaviors
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public void setOverrideValues(List<Serializable> overrideValues)
|
public void setKeyValueBehaviors(Map<Serializable, Behavior> keyValueBehaviors)
|
||||||
{
|
{
|
||||||
this.overrideValues = overrideValues;
|
this.keyValueBehaviors = keyValueBehaviors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Fluent setter for overrideValues
|
** Fluent setter for keyValueBehaviors
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public FieldSecurityLock withOverrideValues(List<Serializable> overrideValues)
|
public FieldSecurityLock withKeyValueBehaviors(Map<Serializable, Behavior> keyValueBehaviors)
|
||||||
{
|
{
|
||||||
this.overrideValues = overrideValues;
|
this.keyValueBehaviors = keyValueBehaviors;
|
||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for a single keyValueBehavior
|
||||||
|
*******************************************************************************/
|
||||||
|
public FieldSecurityLock withKeyValueBehavior(Serializable keyValue, Behavior behavior)
|
||||||
|
{
|
||||||
|
if(this.keyValueBehaviors == null)
|
||||||
|
{
|
||||||
|
this.keyValueBehaviors = new HashMap<>();
|
||||||
|
}
|
||||||
|
this.keyValueBehaviors.put(keyValue, behavior);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Behavior getBehaviorForSession(QSession session)
|
||||||
|
{
|
||||||
|
if(session != null && session.getSecurityKeyValues(this.securityKeyType) != null)
|
||||||
|
{
|
||||||
|
QSecurityKeyType securityKeyType = QContext.getQInstance().getSecurityKeyType(this.securityKeyType);
|
||||||
|
|
||||||
|
for(Serializable securityKeyValue : session.getSecurityKeyValues(this.securityKeyType))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(securityKeyType.getValueType() != null)
|
||||||
|
{
|
||||||
|
securityKeyValue = ValueUtils.getValueAsFieldType(securityKeyType.getValueType(), securityKeyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(keyValueBehaviors.containsKey(securityKeyValue))
|
||||||
|
{
|
||||||
|
return keyValueBehaviors.get(securityKeyValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error getting field behavior", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDefaultBehavior();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.security;
|
|||||||
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -37,6 +38,8 @@ public class QSecurityKeyType implements TopLevelMetaDataInterface
|
|||||||
private String nullValueBehaviorKeyName;
|
private String nullValueBehaviorKeyName;
|
||||||
private String possibleValueSourceName;
|
private String possibleValueSourceName;
|
||||||
|
|
||||||
|
private QFieldType valueType;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -151,6 +154,7 @@ public class QSecurityKeyType implements TopLevelMetaDataInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for nullValueBehaviorKeyName
|
** Getter for nullValueBehaviorKeyName
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -181,4 +185,34 @@ public class QSecurityKeyType implements TopLevelMetaDataInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for valueType
|
||||||
|
*******************************************************************************/
|
||||||
|
public QFieldType getValueType()
|
||||||
|
{
|
||||||
|
return (this.valueType);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for valueType
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setValueType(QFieldType valueType)
|
||||||
|
{
|
||||||
|
this.valueType = valueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for valueType
|
||||||
|
*******************************************************************************/
|
||||||
|
public QSecurityKeyType withValueType(QFieldType valueType)
|
||||||
|
{
|
||||||
|
this.valueType = valueType;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -152,10 +152,10 @@ class PermissionsHelperTest extends BaseTest
|
|||||||
|
|
||||||
AbstractTableActionInput actionInput = new InsertInput().withTableName(TABLE_NAME);
|
AbstractTableActionInput actionInput = new InsertInput().withTableName(TABLE_NAME);
|
||||||
|
|
||||||
assertTrue(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.READ));
|
assertTrue(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.READ));
|
||||||
assertFalse(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.INSERT));
|
assertFalse(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.INSERT));
|
||||||
assertFalse(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.EDIT));
|
assertFalse(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.EDIT));
|
||||||
assertFalse(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.DELETE));
|
assertFalse(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.DELETE));
|
||||||
PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.READ);
|
PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.READ);
|
||||||
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.INSERT)).isInstanceOf(QPermissionDeniedException.class);
|
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.INSERT)).isInstanceOf(QPermissionDeniedException.class);
|
||||||
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.EDIT)).isInstanceOf(QPermissionDeniedException.class);
|
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.EDIT)).isInstanceOf(QPermissionDeniedException.class);
|
||||||
@ -169,10 +169,10 @@ class PermissionsHelperTest extends BaseTest
|
|||||||
|
|
||||||
AbstractTableActionInput actionInput = new InsertInput().withTableName(TABLE_NAME);
|
AbstractTableActionInput actionInput = new InsertInput().withTableName(TABLE_NAME);
|
||||||
|
|
||||||
assertFalse(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.READ));
|
assertFalse(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.READ));
|
||||||
assertTrue(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.INSERT));
|
assertTrue(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.INSERT));
|
||||||
assertTrue(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.EDIT));
|
assertTrue(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.EDIT));
|
||||||
assertTrue(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.DELETE));
|
assertTrue(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.DELETE));
|
||||||
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.READ)).isInstanceOf(QPermissionDeniedException.class);
|
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.READ)).isInstanceOf(QPermissionDeniedException.class);
|
||||||
PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.INSERT);
|
PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.INSERT);
|
||||||
PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.EDIT);
|
PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.EDIT);
|
||||||
@ -244,10 +244,10 @@ class PermissionsHelperTest extends BaseTest
|
|||||||
|
|
||||||
AbstractTableActionInput actionInput = new InsertInput().withTableName(TABLE_NAME);
|
AbstractTableActionInput actionInput = new InsertInput().withTableName(TABLE_NAME);
|
||||||
|
|
||||||
assertTrue(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.READ));
|
assertTrue(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.READ));
|
||||||
assertFalse(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.INSERT));
|
assertFalse(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.INSERT));
|
||||||
assertFalse(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.EDIT));
|
assertFalse(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.EDIT));
|
||||||
assertFalse(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.DELETE));
|
assertFalse(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.DELETE));
|
||||||
PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.READ);
|
PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.READ);
|
||||||
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.INSERT)).isInstanceOf(QPermissionDeniedException.class);
|
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.INSERT)).isInstanceOf(QPermissionDeniedException.class);
|
||||||
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.EDIT)).isInstanceOf(QPermissionDeniedException.class);
|
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.EDIT)).isInstanceOf(QPermissionDeniedException.class);
|
||||||
@ -261,10 +261,10 @@ class PermissionsHelperTest extends BaseTest
|
|||||||
|
|
||||||
AbstractTableActionInput actionInput = new InsertInput().withTableName(TABLE_NAME);
|
AbstractTableActionInput actionInput = new InsertInput().withTableName(TABLE_NAME);
|
||||||
|
|
||||||
assertFalse(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.READ));
|
assertFalse(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.READ));
|
||||||
assertTrue(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.INSERT));
|
assertTrue(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.INSERT));
|
||||||
assertFalse(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.EDIT));
|
assertFalse(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.EDIT));
|
||||||
assertFalse(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, TablePermissionSubType.DELETE));
|
assertFalse(PermissionsHelper.hasTablePermission(TABLE_NAME, TablePermissionSubType.DELETE));
|
||||||
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.READ)).isInstanceOf(QPermissionDeniedException.class);
|
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.READ)).isInstanceOf(QPermissionDeniedException.class);
|
||||||
PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.INSERT);
|
PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.INSERT);
|
||||||
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.EDIT)).isInstanceOf(QPermissionDeniedException.class);
|
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, TablePermissionSubType.EDIT)).isInstanceOf(QPermissionDeniedException.class);
|
||||||
@ -581,7 +581,7 @@ class PermissionsHelperTest extends BaseTest
|
|||||||
|
|
||||||
for(TablePermissionSubType permissionSubType : TablePermissionSubType.values())
|
for(TablePermissionSubType permissionSubType : TablePermissionSubType.values())
|
||||||
{
|
{
|
||||||
assertTrue(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, permissionSubType), "Expected to have permission " + TABLE_NAME + ":" + permissionSubType);
|
assertTrue(PermissionsHelper.hasTablePermission(TABLE_NAME, permissionSubType), "Expected to have permission " + TABLE_NAME + ":" + permissionSubType);
|
||||||
PermissionsHelper.checkTablePermissionThrowing(actionInput, permissionSubType);
|
PermissionsHelper.checkTablePermissionThrowing(actionInput, permissionSubType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,7 +600,7 @@ class PermissionsHelperTest extends BaseTest
|
|||||||
|
|
||||||
for(TablePermissionSubType permissionSubType : TablePermissionSubType.values())
|
for(TablePermissionSubType permissionSubType : TablePermissionSubType.values())
|
||||||
{
|
{
|
||||||
assertFalse(PermissionsHelper.hasTablePermission(actionInput, TABLE_NAME, permissionSubType));
|
assertFalse(PermissionsHelper.hasTablePermission(TABLE_NAME, permissionSubType));
|
||||||
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, permissionSubType))
|
assertThatThrownBy(() -> PermissionsHelper.checkTablePermissionThrowing(actionInput, permissionSubType))
|
||||||
.isExactlyInstanceOf(QPermissionDeniedException.class);
|
.isExactlyInstanceOf(QPermissionDeniedException.class);
|
||||||
}
|
}
|
||||||
|
@ -1922,8 +1922,8 @@ public class QInstanceValidatorTest extends BaseTest
|
|||||||
assertValidationFailureReasons((qInstance -> lockExtractor.apply(qInstance).setSecurityKeyType(" ")), "missing a securityKeyType");
|
assertValidationFailureReasons((qInstance -> lockExtractor.apply(qInstance).setSecurityKeyType(" ")), "missing a securityKeyType");
|
||||||
assertValidationFailureReasons((qInstance -> lockExtractor.apply(qInstance).setSecurityKeyType("notAKeyType")), "unrecognized securityKeyType");
|
assertValidationFailureReasons((qInstance -> lockExtractor.apply(qInstance).setSecurityKeyType("notAKeyType")), "unrecognized securityKeyType");
|
||||||
assertValidationFailureReasons((qInstance -> lockExtractor.apply(qInstance).setDefaultBehavior(null)), "missing a defaultBehavior");
|
assertValidationFailureReasons((qInstance -> lockExtractor.apply(qInstance).setDefaultBehavior(null)), "missing a defaultBehavior");
|
||||||
assertValidationFailureReasons((qInstance -> lockExtractor.apply(qInstance).setOverrideValues(null)), "missing overrideValues");
|
assertValidationFailureReasons((qInstance -> lockExtractor.apply(qInstance).setKeyValueBehaviors(null)), "missing keyValueBehaviors");
|
||||||
assertValidationFailureReasons((qInstance -> lockExtractor.apply(qInstance).setOverrideValues(Collections.emptyList())), "missing overrideValues");
|
assertValidationFailureReasons((qInstance -> lockExtractor.apply(qInstance).setKeyValueBehaviors(Collections.emptyMap())), "missing keyValueBehaviors");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. 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.frontend;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
|
||||||
|
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.model.session.QSession;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for QFrontendTableMetaData
|
||||||
|
*******************************************************************************/
|
||||||
|
class QFrontendTableMetaDataTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testFieldLocks()
|
||||||
|
{
|
||||||
|
QContext.getQInstance().addSecurityKeyType(new QSecurityKeyType()
|
||||||
|
.withName("allowedToSeeFirstName")
|
||||||
|
.withValueType(QFieldType.BOOLEAN));
|
||||||
|
|
||||||
|
FieldSecurityLock fieldSecurityLock = new FieldSecurityLock()
|
||||||
|
.withSecurityKeyType("allowedToSeeFirstName")
|
||||||
|
.withDefaultBehavior(FieldSecurityLock.Behavior.DENY)
|
||||||
|
.withKeyValueBehavior(true, FieldSecurityLock.Behavior.ALLOW);
|
||||||
|
|
||||||
|
QTableMetaData table = QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||||
|
table.getField("firstName").withFieldSecurityLock(fieldSecurityLock);
|
||||||
|
|
||||||
|
Supplier<QFrontendTableMetaData> run = () -> new QFrontendTableMetaData(QContext.getQInstance().getBackendForTable(TestUtils.TABLE_NAME_PERSON_MEMORY), table, true, false);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
// default session (no key) should NOT get to see firstName //
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
assertFalse(run.get().getFields().containsKey("firstName"));
|
||||||
|
|
||||||
|
/////////////////////////////////////
|
||||||
|
// with the key=true, then allowed //
|
||||||
|
/////////////////////////////////////
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue("allowedToSeeFirstName", true));
|
||||||
|
assertTrue(run.get().getFields().containsKey("firstName"));
|
||||||
|
|
||||||
|
////////////////////////////////////////
|
||||||
|
// try a string version of the key... //
|
||||||
|
////////////////////////////////////////
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue("allowedToSeeFirstName", "true"));
|
||||||
|
assertTrue(run.get().getFields().containsKey("firstName"));
|
||||||
|
|
||||||
|
////////////////////////////
|
||||||
|
// try unrecognized value //
|
||||||
|
////////////////////////////
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue("allowedToSeeFirstName", "nope"));
|
||||||
|
assertFalse(run.get().getFields().containsKey("firstName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* QQQ - Low-code Application Framework for Engineers.
|
||||||
|
* Copyright (C) 2021-2024. 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 com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for FieldSecurityLock
|
||||||
|
*******************************************************************************/
|
||||||
|
class FieldSecurityLockTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testGetBehaviorForSession()
|
||||||
|
{
|
||||||
|
QContext.getQInstance().addSecurityKeyType(new QSecurityKeyType()
|
||||||
|
.withName("foo")
|
||||||
|
.withValueType(QFieldType.STRING));
|
||||||
|
|
||||||
|
FieldSecurityLock fieldSecurityLock = new FieldSecurityLock()
|
||||||
|
.withSecurityKeyType("foo")
|
||||||
|
.withDefaultBehavior(FieldSecurityLock.Behavior.DENY)
|
||||||
|
.withKeyValueBehavior("bar", FieldSecurityLock.Behavior.ALLOW)
|
||||||
|
.withKeyValueBehavior("baz", FieldSecurityLock.Behavior.ALLOW)
|
||||||
|
.withKeyValueBehavior("boo", FieldSecurityLock.Behavior.DENY);
|
||||||
|
|
||||||
|
QContext.getQInstance().getTable(TestUtils.TABLE_NAME_PERSON_MEMORY)
|
||||||
|
.getField("firstName").withFieldSecurityLock(fieldSecurityLock);
|
||||||
|
|
||||||
|
////////////////////////////
|
||||||
|
// no key value = default //
|
||||||
|
////////////////////////////
|
||||||
|
assertEquals(FieldSecurityLock.Behavior.DENY, fieldSecurityLock.getBehaviorForSession(new QSession()));
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// values specified get the behavior specified //
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
assertEquals(FieldSecurityLock.Behavior.ALLOW, fieldSecurityLock.getBehaviorForSession(new QSession().withSecurityKeyValue("foo", "bar")));
|
||||||
|
assertEquals(FieldSecurityLock.Behavior.ALLOW, fieldSecurityLock.getBehaviorForSession(new QSession().withSecurityKeyValue("foo", "baz")));
|
||||||
|
assertEquals(FieldSecurityLock.Behavior.DENY, fieldSecurityLock.getBehaviorForSession(new QSession().withSecurityKeyValue("foo", "boo")));
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
// unrecognized values get default behavior //
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
assertEquals(FieldSecurityLock.Behavior.DENY, fieldSecurityLock.getBehaviorForSession(new QSession().withSecurityKeyValue("foo", "huh")));
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// if multiple key values, the first one wins. //
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
assertEquals(FieldSecurityLock.Behavior.ALLOW, fieldSecurityLock.getBehaviorForSession(new QSession().withSecurityKeyValue("foo", "bar").withSecurityKeyValue("foo", "boo")));
|
||||||
|
assertEquals(FieldSecurityLock.Behavior.DENY, fieldSecurityLock.getBehaviorForSession(new QSession().withSecurityKeyValue("foo", "boo").withSecurityKeyValue("foo", "foo")));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -660,7 +660,7 @@ public class TestUtils
|
|||||||
.withField(new QFieldMetaData("total", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY).withFieldSecurityLock(new FieldSecurityLock()
|
.withField(new QFieldMetaData("total", QFieldType.DECIMAL).withDisplayFormat(DisplayFormat.CURRENCY).withFieldSecurityLock(new FieldSecurityLock()
|
||||||
.withSecurityKeyType(SECURITY_KEY_TYPE_INTERNAL_OR_EXTERNAL)
|
.withSecurityKeyType(SECURITY_KEY_TYPE_INTERNAL_OR_EXTERNAL)
|
||||||
.withDefaultBehavior(FieldSecurityLock.Behavior.DENY)
|
.withDefaultBehavior(FieldSecurityLock.Behavior.DENY)
|
||||||
.withOverrideValues(List.of("internal"))
|
.withKeyValueBehavior("internal", FieldSecurityLock.Behavior.ALLOW)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user