Module tree - strom modulů
V administračním rozhraní Edee.one se používá navigace pomocí stromů umístěných v různých částech systému. Specifické stromy zobrazují strukturu stránek, souborů nebo kategorií produktů. Ostatní pak jsou položky získané z jednotlivých modulů.
Strom modulu je získán z běžícího modulu (modul implementující ModuleDescriptorProvider)
Obecně modul poskytuje:
- security descriptor - ten umožní nastavení oprávnění na uzly ve stromu (zaregistruje
SecurityModuleodpovídající modulu v sitemap) - tree descriptor - jeden nebo více stromů zařazených do konkrétních sekcí (slouží také pro definici objektů pro nastavení oprávnění)
- každá sekce modulu pak strukturu uzlů
- jednotlivé uzly specifikují unikátní identifikátor, lokalizovaný titulek, akce pro klik, nebo do konktextového menu, požadavky na oprávnění (definují položky na které lze nastavit oprávnění) a ikonu
- součástí definice uzlu může být i seznam podřízených uzlů
V administraci existují následující sekce (stav od verze 6):
content- doplňkový strom pro funkce přímo spojené s editací obsahu, zobrazený pod stromem stránek a souborů v záložce "Obsah"extensions- hlavní strom modulů obsahuje jednotlivé moduly a jejich hlavní funkce - v záložce "Rozšíření"system- strom obsahující systémové funkce, doplňkové nástroje nebo přehledy - v záložce "Nastavení"internal- speciální sekce primárně určena pro doplňkové nastavení v administraci zabezpečeníeshop- separátní sekce modulů specifických pro záložku "E-shop"
Postup pro specifikaci vlastního stromu
- modul musí implementovat
ModuleDescriptorProvider - instance descriptoru je typicky dostupná v kontextu modulu jako vlastní třída extendující
AbstractModuleDescriptor - abstratní předek zajistí registraci do security CPS, existuje rozšíření
RefreshableModuleDescriptorpro modifikaci stromu za běhu, např po odstranění vybraných entit - descriptor vrací třídu
ModuleDescription, které drží instanceSecurityDescriptoraTreeDescriptor
Volitelně lze definovat položku ve stromě pouze na úrovni stránky modulu a metadata
<moduleTree>(viz dále)
Definice stromu modulu
Strom lze definovat v XML Spring konfiguraci kontextu spojeného s modulem pomocí mtd rozšíření.
Reálný příklad (zápis getModuleName() lze použít pokud je předpoklad vícenásobného použití stejného modulu):
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mtd="http://www.fg.cz/schema/mtd" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-5.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.fg.cz/schema/mtd http://www.fg.cz/schema/mtd/mtd.xsd">
<context:annotation-config/>
<!-- třída implementující ModuleDescriptor --> <bean id="moduleDescriptor" class="com.fg.export.cps.ExportModuleTree"/>
<bean id="moduleDescription" class="com.fg.edee.integration.descriptor.schema.ModuleDescription"> <property name="securityDescriptor"> <!-- specifikace security modulu nutného pro nastavení zabezpečení --> <mtd:securityDescriptor> <mtd:securityModule moduleId="#{ getModuleName() }" moduleDescription="Export."/> </mtd:securityDescriptor> </property> <property name="treeDescriptor"> <!-- specifikace stromu - specifický treeNode zařazený v dané sekci --> <mtd:treeDescriptor> <mtd:treeSection id="extensions" localize="true"> <mtd:treeNode> <mtd:identifier id="#{ getModuleName() + '-export' }" title="export.title" moduleClass="#{ getClass().getName() }" moduleName="#{ getModuleName() }" localize="true"/> <mtd:state expandability="closed"/> <mtd:onClickAction type="openWindow" url="#{ '/' + getModuleName() + '/reports' }"/> <mtd:security rights="W" moduleId="#{ getModuleName() }"/> <mtd:attributes icon="edee-icon-page" dropTarget="myTreeDnD"/> <!-- kontextové menu uzlu --> <mtd:contextMenu> <mtd:contextMenuItem> <mtd:identifier id="create" title="report.create" moduleClass="#{ getClass().getName() }" moduleName="#{ getModuleName() }" localize="true"/> <mtd:onClickAction type="openWindow" url="#{ '/' + getModuleName() + '/create' }"/> <mtd:security rights="A" moduleId="#{ getModuleName() }"> <mtd:securityIdentifier identifierId="report" identifierName="report"/> </mtd:security> <mtd:attributes icon="icon-edee-new icon-orange"/> </mtd:contextMenuItem> </mtd:contextMenu> </mtd:treeNode> </mtd:treeSection> </mtd:treeDescriptor> </property> </bean></beans>Strom modulu lze definovat i z java kódu, případně obohatit statický strom daty z databáze.
Příklad z com.fg.edee.security.cps.descriptor.SecurityAdminModuleDescriptor.buildTreeNodes
ModuleTreeNode treeNode = new RamjetTreeNodeBuilderChain(parent, rjManager, moduleRootNode, cpsModule) .start() .identifier(entityId, entity.getName(), false) .state(ModuleTreeNodeState.T.closed, false, true, false) .onClickAction(OnClickAction.T.openWindow, nodeAbsId, null, paramName, entityId) .security("W") .securityIdentifier(paramName, null, entityId) .attributes(ImmutableMap.<String, Object>of( "icon", nodeIcon, "dropTarget", "myTreeDnD" )) .startContextMenu() .nextContextMenuItem() .identifier(ITEM_IDENTIFIER_ID, itemTitle, true) .state(ModuleTreeNodeState.T.closed, false, true, false) .onClickAction(OnClickAction.T.ajaxCall, itemAbsId, ITEM_ACTION_EVENT_NAME, paramName, entityId) .security("D") .securityIdentifier(paramName, null, entityId) .attributes(ImmutableMap.<String, Object>of("icon", REMOVE_ICON)) .stopContextMenu() .end();Export ramjet stránky do existujícího stromu
Od verze 6 je možné pouze definicí vhodného metadata na stránce v administraci zajistit, že daná stránka se zařadí do stromu modulu do existující sekce.
Definice je prováděna na základě metadata moduleTree. Stránka s tímto nastavením je
vložena do daného uzlu, pokud existuje.
Možné parametry:
targetNodeId- id cílového uzlu v existujícím stromutitle- titulek, resp. lokalizační klíčmoduleamoduleClass- název a třída modulu slouží k načtení správného lokalizačního balíku - klíč z title je očekáván v kontextu daného modulupriority- číslo určující pořadí v dané části uzlu, vyšší priorita je nahořeicon- ikona uzlusecurity- uzel přebírá security z nadřazeného tagu, je podporována ale pouze nastavení dle acl a modulemode- od verze 6.1.1 lze uzel ve stromu zcela nahradit (hodnotareplace- výchozí jeappend)
Příklad administrační stránky z mail modulu
<?xml version="1.0" encoding="utf-8"?><gui> <guidelinePage id="serverStatus"> <metadata> <security> <acl>O</acl> <module>mail</module> </security> <moduleTree> <targetNodeId>edee-diagnostics</targetNodeId> <title>edee.mail.title</title> <module>mail</module> <moduleClass>com.fg.mail.cps.MailModule</moduleClass> <priority>82</priority> <iconClass>icon-edee-email icon-blue</iconClass> </moduleTree> </metadata> <structure> ... </structure> </guidelinePage></gui> Bez použití security descriptoru může vyvstat nutnost pro ruční registraci security modulu, pro získání možnosti omezení práv pomocí autorizace.
// na úrovni modulu@Overrideprotected void setupModuleAfterSpringInitialization(Map config) throws CpsException { super.setupModuleAfterSpringInitialization(config); getModuleSupport().getSecuritySupport().registerSecurityModule( new SecurityModule("bulkoperations", "bulkoperations"));}Priority ve stromu
Na úrovni uzlů lze definovat priority. To je vhodné především pro dodatečné modifikace.
Priorita je definována jako celé číslo (integer), nejvyšší hodnota je jako první. Pro položky bez specifikace, jsou priority doplněny automaticky v sekvenci od 5 000 (resp. 10 000 na hlavní úrovni) po stovkách.
Možné problémy z praxe:
- pokud budete dynamicky přidávat druhou úroveň (větve) stromu, pak v první úrovni nesmí být v tagu security zanořen tag securityIdentifier, jinak nebudete moci v dynamicky definovaných položkách nastavovat vlastní ID securityIdentifieru a následně na tyto položky nastavovat práva. Když v tomto případě vlastní ID nastavíte, pak dostanete při startu aplikace chybu. To je záludné, protože u čistě XML konfigurace je toto možné.
- při vytváření další úrovně je pak nutné vkládat do securityIdentifieru navíc položku itemId
- pozor při testování nastavení oprávnění na konkrétní uzel - i když jste nastavili uživateli oprávnění na nějakou položku stromu, tak pak jako cílový uživatel tuto položku nevidíte, pokud není nastaveni VIEW oprávnění na celý modul ale s omezenou dědičností.
- pokud budete chtít provádět kontrolu oprávnění i v aplikaci (např. pro načtení dat do filtru), pak se podívejte na příklad uvedený níže. Proměnná securityId pak odpovídá hodnotě, kterou jste vložili jako ID do securityIdentifieru.
private static final String SEC_MODULE = "mycustommodule";
@AutowiredSecurityService securityService;
public boolean isAuthorized(MyEntity entity, SecurityRight right) { final SecurityModule securityModule = securityService.resolveModule(SEC_MODULE); final User user = LoggedUserHolder.getLoggedInUser(); return securityService.isAuthorized( right, securityModule, Objects.requireNonNull(entity.getPrimaryKey()).toString(), null, entity.getLang(), user );}Použití stromu
Pro zobrazení stromu v administraci slouží komponenta tree případně selectableTree
pomocí dataprovideru ModuleTreeDataProvider, který využívá podobu stromu sestavenou v
ModuleDescriptorResolver rozšířenou dále o AdditionalModuleTreeNodeManager.
