From 2cec1b78e1cf40767f11d3464424401a8f6ce0f7 Mon Sep 17 00:00:00 2001
From: Simon Leipold <s.leipold@adito.de>
Date: Tue, 24 Aug 2021 07:38:21 +0000
Subject: [PATCH] #1077127 show inherited permissions in overview

---
 .../PermissionOverview_entity.aod             |  20 +-
 .../entityfields/entity/titleProcess.js       |   9 -
 .../entityfields/entity_name/stateProcess.js  |   9 +
 .../recordcontainers/jdito/contentProcess.js  | 404 +++++++++++++++---
 .../PermissionOverviewFilter_view.aod         |  59 +--
 process/Permission_lib/process.js             |  66 ++-
 6 files changed, 467 insertions(+), 100 deletions(-)
 delete mode 100644 entity/PermissionOverview_entity/entityfields/entity/titleProcess.js
 create mode 100644 entity/PermissionOverview_entity/entityfields/entity_name/stateProcess.js

diff --git a/entity/PermissionOverview_entity/PermissionOverview_entity.aod b/entity/PermissionOverview_entity/PermissionOverview_entity.aod
index 10bcfbb8d7..4c7b54bcf0 100644
--- a/entity/PermissionOverview_entity/PermissionOverview_entity.aod
+++ b/entity/PermissionOverview_entity/PermissionOverview_entity.aod
@@ -17,8 +17,9 @@
       <name>#PROVIDER</name>
     </entityProvider>
     <entityField>
-      <name>ENTITY</name>
-      <titleProcess>%aditoprj%/entity/PermissionOverview_entity/entityfields/entity/titleProcess.js</titleProcess>
+      <name>ENTITY_NAME</name>
+      <title>Entity</title>
+      <stateProcess>%aditoprj%/entity/PermissionOverview_entity/entityfields/entity_name/stateProcess.js</stateProcess>
     </entityField>
     <entityField>
       <name>ACTION_VIEW</name>
@@ -75,6 +76,13 @@
       <name>#PROVIDER_AGGREGATES</name>
       <useAggregates v="true" />
     </entityProvider>
+    <entityField>
+      <name>ROLE_NAME</name>
+      <title>Role</title>
+    </entityField>
+    <entityField>
+      <name>TREE_TABLE_PARENT</name>
+    </entityField>
   </entityFields>
   <recordContainers>
     <jDitoRecordContainer>
@@ -88,7 +96,10 @@
           <name>UID.value</name>
         </jDitoRecordFieldMapping>
         <jDitoRecordFieldMapping>
-          <name>ENTITY.value</name>
+          <name>ENTITY_NAME.value</name>
+        </jDitoRecordFieldMapping>
+        <jDitoRecordFieldMapping>
+          <name>ROLE_NAME.value</name>
         </jDitoRecordFieldMapping>
         <jDitoRecordFieldMapping>
           <name>ACTION_VIEW.value</name>
@@ -105,6 +116,9 @@
         <jDitoRecordFieldMapping>
           <name>ACTION_DELETE.value</name>
         </jDitoRecordFieldMapping>
+        <jDitoRecordFieldMapping>
+          <name>TREE_TABLE_PARENT.value</name>
+        </jDitoRecordFieldMapping>
       </recordFieldMappings>
     </jDitoRecordContainer>
   </recordContainers>
diff --git a/entity/PermissionOverview_entity/entityfields/entity/titleProcess.js b/entity/PermissionOverview_entity/entityfields/entity/titleProcess.js
deleted file mode 100644
index 5973b8dab2..0000000000
--- a/entity/PermissionOverview_entity/entityfields/entity/titleProcess.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import("system.result");
-import("system.vars");
-import("system.translate");
-
-if (vars.exists("$param.RoleName_param") && vars.get("$param.RoleName_param")) {
-    result.string(translate.text("Entity"));
-} else if (vars.exists("$param.EntityId_param") && vars.get("$param.EntityId_param")) {
-    result.string(translate.text("Role"));
-}
\ No newline at end of file
diff --git a/entity/PermissionOverview_entity/entityfields/entity_name/stateProcess.js b/entity/PermissionOverview_entity/entityfields/entity_name/stateProcess.js
new file mode 100644
index 0000000000..f0d65c81fb
--- /dev/null
+++ b/entity/PermissionOverview_entity/entityfields/entity_name/stateProcess.js
@@ -0,0 +1,9 @@
+import("system.vars");
+import("system.result");
+import("system.neon");
+
+// entity was opened - dont show entity name in table
+if (vars.exists("$param.EntityId_param") && vars.get("$param.EntityId_param"))
+{
+    result.string(neon.COMPONENTSTATE_INVISIBLE);
+}
\ No newline at end of file
diff --git a/entity/PermissionOverview_entity/recordcontainers/jdito/contentProcess.js b/entity/PermissionOverview_entity/recordcontainers/jdito/contentProcess.js
index f31922237d..36adb5c827 100644
--- a/entity/PermissionOverview_entity/recordcontainers/jdito/contentProcess.js
+++ b/entity/PermissionOverview_entity/recordcontainers/jdito/contentProcess.js
@@ -7,22 +7,20 @@ import("system.result");
 import("system.db");
 import("Permission_lib");
 
-var res = [];
+var overview = [];
 var roleName = "";
 var entityName = "";
 let alias = SqlUtils.getSystemAlias();
 
 var entitiesMetaData = project.getDataModels(project.DATAMODEL_KIND_ENTITY);
-var entitiesUsePermFlagSet = []; // array, which contains ids of entities with usePermission flag set
+var entitiesUsePermFlagSet = []; // array, which contains ids (names) of entities with usePermission flag set
 
-// gets all names of the entites which have the 'usePermission'-flag set (positive list)
-for each (let entityMetaData in entitiesMetaData) 
-{
-    if (entityMetaData[6] == "true") 
-    {
-        entitiesUsePermFlagSet.push(entityMetaData[0]);
-    }
-}
+// gets all names of the entites which have the 'usePermission'-flag set (entitiesMetaData[6] is true if usePermission is set)
+entitiesUsePermFlagSet = entitiesMetaData.filter(function (pEntityMetaDataEntry) {
+    return pEntityMetaDataEntry[6] == "true";
+}).map(function (pEntity) {
+    return pEntity[0];
+});
 
 var rolesOrEntitiesSelect = new SqlBuilder(alias).where();
 var rolesOrEntities = [];
@@ -47,49 +45,168 @@ rolesOrEntities = rolesOrEntitiesSelect
     .arrayColumn();
 
 var entityPermSetId = "";
-for each (var entry in rolesOrEntities)
+for each (var element in rolesOrEntities)
 { // entry contains either a role or an entity, depending on which param exists
-    var overview = {
-        uid: util.getNewUUID(), 
-        entry: entry, 
-        view: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
-        create: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
-        read: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
-        update: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
-        "delete": PermissionEnums.FORBIDDEN_ACTION_ICON()
-    };
-    
-    if (vars.exists("$param.RoleName_param") && vars.get("$param.RoleName_param"))
+    var overviewElement;
+    if (roleName)
     {
-        entityPermSetId = PermissionUtil.getSetRoot(roleName, entry);
-    } 
-    else if (vars.exists("$param.EntityId_param") && vars.get("$param.EntityId_param"))
+        overviewElement = {
+            uid: util.getNewUUID(), 
+            entity: element,
+            role: roleName,
+            view: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+            create: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+            read: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+            update: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+            "delete": PermissionEnums.FORBIDDEN_ACTION_ICON(),
+            parent: null
+        };
+    }
+    else if (entityName)
     {
-        entityPermSetId = PermissionUtil.getSetRoot(entry, entityName);
+        overviewElement = {
+            uid: util.getNewUUID(), 
+            entity: entityName,
+            role: element,
+            view: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+            create: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+            read: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+            update: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+            "delete": PermissionEnums.FORBIDDEN_ACTION_ICON(),
+            parent: null
+        }; 
     }
 
+    entityPermSetId = PermissionUtil.getSetRoot(overviewElement.role, overviewElement.entity);
+    
     if (entityPermSetId)
     {
         var recordPermSetId = PermissionUtil.getRecordSetOfEntitySet(entityPermSetId);
         
         // entity permissions
-        overview = prepareOverview(entityPermSetId, overview);
+        overviewElement = prepareOverviewElement(entityPermSetId, overviewElement);
 
         // record permissions
-        overview = prepareOverview(recordPermSetId, overview);
+        overviewElement = prepareOverviewElement(recordPermSetId, overviewElement);
 
-        res.push([overview.uid, overview.entry, overview.view, overview.create, overview.read, overview.update, overview["delete"]]);
+        overview.push([overviewElement.uid, overviewElement.entity, overviewElement.role, overviewElement.view, overviewElement.create, overviewElement.read, 
+            overviewElement.update, overviewElement["delete"], overviewElement.parent]);
+    }
+}
+
+// add permissions which are received through hierarchical inheritance (only hierarchies of custom roles)
+// role opened:
+if (roleName && roleName.startsWith("CUSTOM_"))
+{
+    // get child roles of the opened role - parent roles dont matter here
+    var childRoles = PermissionUtil.getChildRoles(roleName);
+    for each (let childRole in childRoles)
+    {
+        var childRoleName = childRole[1];
+        var permSets = PermissionUtil.getPermittedSetsOfRole(childRoleName, true);
+        
+        for each (var set in permSets)
+        {
+            overviewElement = {
+                uid: util.getNewUUID(), 
+                entity: set.entity,
+                role: set.role,
+                view: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+                create: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+                read: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+                update: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+                "delete": PermissionEnums.FORBIDDEN_ACTION_ICON(),
+                parent: null
+            }; 
+            
+            overviewElement = prepareOverviewElement(set.setid, overviewElement);
+            overview = mergeElementIntoOverviewByEntity(overview, overviewElement);
+        }  
+    }
+    
+    // loop over permissions created for this role and 
+    // merge them into the corresponding overview element
+    for each (let element in overview)
+    {
+        element = overviewElementArrayToObject(element);
+        if (element.role == roleName)
+        {
+            let overviewParentElement = getOverviewParentElementByEntity(overview, element.entity);
+            if (overviewParentElement) 
+            {
+                overview = mergeElementIntoOverviewByEntity(overview, element);
+                element.parent = overviewParentElement.uid;
+                overview[getIndexOfElement(overview, element)] = overviewElementObjectToArray(element);
+            }
+        }
+    }
+}
+// entity opened:
+else if (entityName)
+{
+    for each (let element in overview)
+    {
+        element = overviewElementArrayToObject(element);
+        // get parents of permitted role
+        var parents = PermissionUtil.getHierarchyParents(element.role);
+        
+        // loop over parents
+        // add overview element for every new parent
+        // add child to parent in overview
+        // merge actions of child element into parent element
+        for each (let parent in parents)
+        {
+            if (!overviewHasParentByRole(overview, parent))
+            {
+                overview = addOverviewParentByRole(overview, parent);
+            }
+            
+            let overviewParentElement = getOverviewParentElementByRole(overview, parent);
+            let idxOfParentElement = getIndexOfElement(overview, overviewParentElement);
+            
+            // merge child actions into parent
+            let mergedElement = getMaxPermittedElement(overviewParentElement, element);
+            overview[idxOfParentElement] = overviewElementObjectToArray(mergedElement);  
+            
+            // add child to parent in overview tree table
+            // create a copy of the child element with a new uid and link to the parent
+            // thats necessary because the "main" element must also be displayed in the overview, not only as a child of another role
+            element.parent = overviewParentElement.uid;
+            element.uid = util.getNewUUID();
+            overview.push(overviewElementObjectToArray(element));
+        }
+    }
+    
+    for each (let element in overview)
+    {
+        element = overviewElementArrayToObject(element);
+        // handle permissions created for this entity which are existing without inheritance
+        let overviewParentElement = getOverviewParentElementByRole(overview, element.role);
+        if (overviewParentElement && element.entity != null)
+        {
+            // merge parent with child
+            let mergedElement = getMaxPermittedElement(overviewParentElement, element);
+            overview[getIndexOfElement(overview, overviewParentElement)] = overviewElementObjectToArray(mergedElement);
+            // remove "base" element which has a parent with the same name
+            // this happens if a role is a parent role and therfore has children and also has own permissions
+            overview.splice(getIndexOfElement(overview, element), 1);
+            // and link child to the corresponding parent
+            element.parent = overviewParentElement.uid;
+            element.uid = util.getNewUUID();
+            overview.push(overviewElementObjectToArray(element));
+        }
     }
 }
 
 var order = vars.get("$local.order");
 var columnOrder = {
-    "ENTITY.value" : 1,
-    "ACTION_VIEW.value" : 2,
-    "ACTION_CREATE.value" : 3,
-    "ACTION_READ.value" : 4,
-    "ACTION_UPDATE.value" : 5,
-    "ACTION_DELETE.value" : 6
+    "ENTITY_NAME.value" : 1,
+    "ROLE_NAME.value": 2,
+    "ACTION_VIEW.value" : 3,
+    "ACTION_CREATE.value" : 4,
+    "ACTION_READ.value" : 5,
+    "ACTION_UPDATE.value" : 6,
+    "ACTION_DELETE.value" : 7
 };
 var sortOrder = [];
 for (let field in order)
@@ -103,26 +220,28 @@ for (let field in order)
 if (!sortOrder.length) //sort by entity or role by default
     sortOrder = [1, false];
 
-ArrayUtils.sortMulti(res, sortOrder);
+ArrayUtils.sortMulti(overview, sortOrder);
 
-result.object(res);
+result.object(overview);
 
-function prepareOverview(pPermSetId, pOverview) {
-    if (!pPermSetId) { return pOverview; }
+function prepareOverviewElement(pPermSetId, pOverviewElement) {
+    if (!pPermSetId || !pOverviewElement) {
+        return null;
+    }
     
-    var conditionalPermActionId = [];
-    var overview = pOverview;
+    var conditionalPermActionIds = [];
+    var overviewElement = pOverviewElement;
 
     var permId = PermissionUtil.getPermissionWithoutCond(pPermSetId);
-    if (permId && permId != "")
+    if (permId)
     {
-        var defaultPermActionId = PermissionUtil.getActions([permId]);
+        var defaultPermActionIds = PermissionUtil.getActions([permId]);
     
         if (PermissionUtil.getCondType(permId) == 1)
         {
-            for each (let actionSets in defaultPermActionId)
+            for each (let actionId in defaultPermActionIds)
             {
-                overview[PermissionUtil.resolveActionId(actionSets)] = PermissionEnums.PERMITTED_ACTION_ICON();
+                overviewElement[PermissionUtil.resolveActionId(actionId)] = PermissionEnums.PERMITTED_ACTION_ICON();
             }
         }       
     }
@@ -131,19 +250,200 @@ function prepareOverview(pPermSetId, pOverview) {
     
     if (permIds && permIds.length > 0)
     {
-        for each (let permId in permIds)
-        {
-            conditionalPermActionId.push(PermissionUtil.getActions([permId]));
-        }
+        conditionalPermActionIds = permIds.map(function (permId) {
+            return PermissionUtil.getActions([permId]);
+        });
 
-        for each (let actionSets in conditionalPermActionId)
+        for each (let actionIds in conditionalPermActionIds)
         {
-            for each (let action in actionSets)
+            for each (let actionId in actionIds)
             {
-                overview[PermissionUtil.resolveActionId(action)] = PermissionEnums.RESTRICTED_ACTION_ICON();
+                overviewElement[PermissionUtil.resolveActionId(actionId)] = PermissionEnums.RESTRICTED_ACTION_ICON();
             }
         }
     }
 
+    return overviewElement;
+}
+
+function mergeElementIntoOverviewByEntity(pOverview, pNewElement)
+{
+    var overview = pOverview;
+   
+    if (!overviewHasParentByEntity(overview, pNewElement.entity))
+    {
+        overview = addOverviewParentByEntity(overview, pNewElement.entity);
+    }
+
+    var overviewParentElement = getOverviewParentElementByEntity(overview, pNewElement.entity);
+    var idxOfParentElement = getIndexOfElement(overview, overviewParentElement);
+    var mergedElement = getMaxPermittedElement(overviewParentElement, pNewElement);
+    
+    // merged element has to be replaced
+    overview[idxOfParentElement] = overviewElementObjectToArray(mergedElement);
+    // new element has to be added to the overview to the right tree table parent
+    // if a element with the same entity-role-combination already exists merge their entries with each other
+    // this happens because this function gets called for every permission set which have entity or record access type
+    // for entity access type view and create actions getting added
+    // for record access type read, update and delete actions getting added
+    // so for each overview entry there can be two permission sets
+    var overviewElementWithSameEntityRoleCombination = getOverviewElementWithSameEntityRoleCombination(overview, pNewElement.entity, pNewElement.role);
+    if (overviewElementWithSameEntityRoleCombination)
+    {
+        var idxOfOverviewElementWithSameEntityRoleCombination = getIndexOfElement(overview, overviewElementWithSameEntityRoleCombination);
+        mergedElement = getMaxPermittedElement(overviewElementWithSameEntityRoleCombination, pNewElement);
+        overview[idxOfOverviewElementWithSameEntityRoleCombination] = overviewElementObjectToArray(mergedElement);
+    }
+    else
+    {
+        pNewElement.parent = overviewParentElement.uid;
+        overview.push(overviewElementObjectToArray(pNewElement));        
+    }
+    
+    return overview;
+}
+
+function getMaxPermittedElement(pElementA, pElementB)
+{
+    // element is the result element
+    // every property gets overwritten - pElementA is only used to get the object structure
+    var element = pElementA;
+
+    element.view = getActionIconByValue(Math.max(getValueOfActionIcon(pElementA.view), getValueOfActionIcon(pElementB.view)));
+    element.create = getActionIconByValue(Math.max(getValueOfActionIcon(pElementA.create), getValueOfActionIcon(pElementB.create)));
+    element.read = getActionIconByValue(Math.max(getValueOfActionIcon(pElementA.read), getValueOfActionIcon(pElementB.read)));
+    element.update = getActionIconByValue(Math.max(getValueOfActionIcon(pElementA.update), getValueOfActionIcon(pElementB.update)));
+    element["delete"] = getActionIconByValue(Math.max(getValueOfActionIcon(pElementA["delete"]), getValueOfActionIcon(pElementB["delete"])));
+    
+    return element;
+}
+
+function getValueOfActionIcon(pActionIcon)
+{
+    if (pActionIcon == PermissionEnums.PERMITTED_ACTION_ICON()) return 2;
+    if (pActionIcon == PermissionEnums.RESTRICTED_ACTION_ICON()) return 1;
+    if (pActionIcon == PermissionEnums.FORBIDDEN_ACTION_ICON()) return 0;
+    return -1;
+}
+
+function getActionIconByValue(pValue)
+{
+    if (pValue == 2) return PermissionEnums.PERMITTED_ACTION_ICON();
+    if (pValue == 1) return PermissionEnums.RESTRICTED_ACTION_ICON();
+    if (pValue == 0) return PermissionEnums.FORBIDDEN_ACTION_ICON();
+    return PermissionEnums.FORBIDDEN_ACTION_ICON();
+}
+
+function overviewElementArrayToObject(pOverviewElementArray)
+{
+    if (pOverviewElementArray)
+    {
+        return {
+            uid: pOverviewElementArray[0], 
+            entity: pOverviewElementArray[1],
+            role: pOverviewElementArray[2],
+            view: pOverviewElementArray[3], 
+            create: pOverviewElementArray[4], 
+            read: pOverviewElementArray[5], 
+            update: pOverviewElementArray[6], 
+            "delete": pOverviewElementArray[7],
+            parent: pOverviewElementArray[8]
+        }; 
+    }
+    
+    return null;
+}
+
+function overviewElementObjectToArray(pOverviewElementObject)
+{
+    return [pOverviewElementObject.uid, pOverviewElementObject.entity, pOverviewElementObject.role, pOverviewElementObject.view,
+    pOverviewElementObject.create, pOverviewElementObject.read, pOverviewElementObject.update, pOverviewElementObject["delete"], pOverviewElementObject.parent];
+}
+
+function overviewHasParentByEntity(pOverview, pEntity)
+{
+    return pOverview.some(function (element) {
+        element = overviewElementArrayToObject(element);
+        return element.entity == pEntity && element.role == null;
+    });
+}
+
+function overviewHasParentByRole(pOverview, pRole)
+{
+    return pOverview.some(function (element) {
+        element = overviewElementArrayToObject(element);
+        return element.role == pRole && element.entity == null;
+    });
+}
+
+function addOverviewParentByEntity(pOverview, pEntity)
+{
+    let overview = pOverview;
+    // build new empty parent overview element and push it into overview
+    let overviewElementParent = {
+        uid: util.getNewUUID(), 
+        entity: pEntity,
+        role: null,
+        view: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+        create: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+        read: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+        update: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+        "delete": PermissionEnums.FORBIDDEN_ACTION_ICON(),
+        parent: null
+    }; 
+    overview.push(overviewElementObjectToArray(overviewElementParent));
+    return overview;
+}
+
+function addOverviewParentByRole(pOverview, pRole)
+{
+    let overview = pOverview;
+    // build new empty parent overview element and push it into overview
+    let overviewElementParent = {
+        uid: util.getNewUUID(), 
+        entity: null,
+        role: pRole,
+        view: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+        create: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+        read: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+        update: PermissionEnums.FORBIDDEN_ACTION_ICON(), 
+        "delete": PermissionEnums.FORBIDDEN_ACTION_ICON(),
+        parent: null
+    }; 
+    overview.push(overviewElementObjectToArray(overviewElementParent));
     return overview;
+}
+
+function getOverviewParentElementByEntity(pOverview, pEntity)
+{
+    return overviewElementArrayToObject(pOverview.find(function (element) {
+        element = overviewElementArrayToObject(element);
+        return element.entity == pEntity && element.role == null;
+    }));
+}
+
+function getOverviewParentElementByRole(pOverview, pRole)
+{
+    return overviewElementArrayToObject(pOverview.find(function (element) {
+        element = overviewElementArrayToObject(element);
+        return element.role == pRole && element.entity == null;
+    }));
+}
+
+function getIndexOfElement(pOverview, pElement)
+{
+    if (!pOverview || !pElement) return -1;
+    
+    return pOverview.findIndex(function (element) {
+        element = overviewElementArrayToObject(element);
+        return element.uid == pElement.uid;
+    });
+}
+
+function getOverviewElementWithSameEntityRoleCombination(pOverview, pEntity, pRole)
+{
+    return overviewElementArrayToObject(pOverview.find(function (element) {
+        element = overviewElementArrayToObject(element);
+        return element.entity == pEntity && element.role == pRole;
+    }));
 }
\ No newline at end of file
diff --git a/neonView/PermissionOverviewFilter_view/PermissionOverviewFilter_view.aod b/neonView/PermissionOverviewFilter_view/PermissionOverviewFilter_view.aod
index 62df56ab75..dd5f97725b 100644
--- a/neonView/PermissionOverviewFilter_view/PermissionOverviewFilter_view.aod
+++ b/neonView/PermissionOverviewFilter_view/PermissionOverviewFilter_view.aod
@@ -10,41 +10,44 @@
     </groupLayout>
   </layout>
   <children>
-    <tableViewTemplate>
-      <name>Table</name>
-      <iconField>#ICON</iconField>
-      <titleField>ENTITY</titleField>
-      <entityField>#ENTITY</entityField>
+    <treeTableViewTemplate>
+      <name>overview</name>
+      <parentField>TREE_TABLE_PARENT</parentField>
+      <expandRootItems v="false" />
       <columns>
-        <neonTableColumn>
-          <name>2d83af69-cafe-471c-92de-0e6e66ba51aa</name>
+        <neonTreeTableColumn>
+          <name>71148aa5-1b0a-4881-b54a-2b79cd736ea3</name>
           <entityField>#ICON</entityField>
-        </neonTableColumn>
-        <neonTableColumn>
-          <name>16c4d649-ec91-415d-9768-78d0581ad44a</name>
-          <entityField>ENTITY</entityField>
-        </neonTableColumn>
-        <neonTableColumn>
-          <name>f3e96f53-fb65-4b98-a189-cead544c17d8</name>
+        </neonTreeTableColumn>
+        <neonTreeTableColumn>
+          <name>87f92931-0b77-42b8-8d9d-727c79ccb025</name>
+          <entityField>ENTITY_NAME</entityField>
+        </neonTreeTableColumn>
+        <neonTreeTableColumn>
+          <name>05aa1587-2e9a-43aa-ad35-665ca4127e2f</name>
+          <entityField>ROLE_NAME</entityField>
+        </neonTreeTableColumn>
+        <neonTreeTableColumn>
+          <name>3ae98105-1435-49a8-b83b-44d1bc76865d</name>
           <entityField>ACTION_VIEW</entityField>
-        </neonTableColumn>
-        <neonTableColumn>
-          <name>d21161a7-51e8-4c83-9c92-b6a0d24ddc62</name>
+        </neonTreeTableColumn>
+        <neonTreeTableColumn>
+          <name>1d8ef317-8178-466a-99fd-7bf6957d5e12</name>
           <entityField>ACTION_CREATE</entityField>
-        </neonTableColumn>
-        <neonTableColumn>
-          <name>c5679586-7f99-4ea5-99f4-94422e70c999</name>
+        </neonTreeTableColumn>
+        <neonTreeTableColumn>
+          <name>d6e8bba6-ee62-4473-aeaa-22b5225518e6</name>
           <entityField>ACTION_READ</entityField>
-        </neonTableColumn>
-        <neonTableColumn>
-          <name>0699fd7b-d4ac-4ef8-ac19-1cd249ce3251</name>
+        </neonTreeTableColumn>
+        <neonTreeTableColumn>
+          <name>8b4513c0-f731-486c-bbd9-ffd29f4d109c</name>
           <entityField>ACTION_UPDATE</entityField>
-        </neonTableColumn>
-        <neonTableColumn>
-          <name>f7c3479e-e635-44f1-afc0-45570c4fffe9</name>
+        </neonTreeTableColumn>
+        <neonTreeTableColumn>
+          <name>716710a5-4219-464a-bcec-a6eb27948eb2</name>
           <entityField>ACTION_DELETE</entityField>
-        </neonTableColumn>
+        </neonTreeTableColumn>
       </columns>
-    </tableViewTemplate>
+    </treeTableViewTemplate>
   </children>
 </neonView>
diff --git a/process/Permission_lib/process.js b/process/Permission_lib/process.js
index 1f2162f154..0273d9b02d 100644
--- a/process/Permission_lib/process.js
+++ b/process/Permission_lib/process.js
@@ -24,25 +24,25 @@ PermissionEnums.ACCESSTYPE_RECORD = function () {
 }
 PermissionEnums.ACTION_VIEW = function() {
     return "view"
-    };
+};
 PermissionEnums.ACTION_CREATE = function() {
     return "create"
-    };
+};
 PermissionEnums.ACTION_READ = function() {
     return "read"
-    };
+};
 PermissionEnums.ACTION_UPDATE = function() {
     return "update"
-    };
+};
 PermissionEnums.ACTION_DELETE = function() {
     return "delete"
-    };
+};
 PermissionEnums.CONDITIONTYPE_DEFAULT = function() {
     return "default"
-    };
+};
 PermissionEnums.CONDITIONTYPE_CONDITIONAL = function() {
     return "conditional"
-    };
+};
 PermissionEnums.PERMITTED_ACTION_ICON = function () {
     return "VAADIN:CIRCLE";
 }
@@ -755,7 +755,7 @@ function PermissionUtil () {}
     }
     
     /**
-     * Returns all child role ids and names of the given role.
+     * Returns all child role ids and names of the given role. The ID is not the ID of the role itself but the ID of the hierarchical link.
      * 
      * @param {String} pRoleName parent role name, mandatory
      * 
@@ -933,6 +933,56 @@ function PermissionUtil () {}
         .arrayColumn();
     }
     
+    /**
+     * Gets the role hierarchy of custom roles. A result entry contains an id, the parent role and the linked child role.
+     * 
+     * @result {Object[]} returns an array of objects with the following properties: {id, parent, child}
+     */
+    PermissionUtil.getRoleHierarchy = function()
+    {
+        var selectHierarchy = newSelect("ID, PARENT_ROLE, CHILD_ROLE", alias)
+        .from("ASYS_ROLES_CHILDREN")
+        .table();
+        
+        return selectHierarchy.map(function (entry) {
+            return {
+                id: entry[0],
+                parent: entry[1],
+                child: entry[2]
+            } 
+        });
+    }
+    
+    /**
+     * Gets all permitted sets of the given role.
+     * 
+     * @param pRoleName the name of the role.
+     * 
+     * @param pReturnAsObject true if the result should be returned as object. As default it returns an array.
+     * 
+     * @return {Array[]} returns an string array with all permitted sets and their data. [0]: setid, [1] parentsetid, [2]: entity, [3]: role, [4]: field, [5]: accesstype. Object property names are the same.
+     */
+    PermissionUtil.getPermittedSetsOfRole = function(pRoleName, pReturnAsObject)
+    {
+        var permittedSetsArray = newSelect("ASYS_PERMISSIONSETID, ASYS_PERMISSIONSET_ID, ENTITY_ID, ROLE_ID, FIELD_ID, ACCESSTYPE", alias)
+        .from("ASYS_PERMISSIONSET")
+        .where("ASYS_PERMISSIONSET.ROLE_ID", pRoleName)
+        .table();
+        
+        if (!pReturnAsObject) return permittedSetsArray;
+        
+        return permittedSetsArray.map(function (set) {
+            return {
+                setid: set[0],
+                parentsetid: set[1],
+                entity: set[2],
+                role: set[3],
+                field: set[4],
+                accesstype: set[5]
+            };
+        });
+    }
+    
 } //end of block
 
 
-- 
GitLab