doc platform
|
|
@ -1 +0,0 @@
|
||||||
<mxfile host="Electron" modified="2025-12-07T12:20:24.090Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="cr0QP01cfDrbV7QtecK0" version="20.3.0" type="device"><diagram name="Module Dependencies" id="module-dependencies">7Zpbc9o6EMc/DXOe2pHxBfKYQC+Z0860k/Zc+tIR0hq7CIvKcoB++koggS+iTVrHcOA4D0G7siz/f9JqZbvnj+arVwIvkrecAuv1EV31/HGv3/e8KFD/tGVtLeHV1jIVKTW2veEu/QbGiIy1SCnklYqScybTRdVIeJYBkRUbFoIvq9VizqpXXeApNAx3BLOm9e+UysRYA4T2jteQThN7aWQ9c2xrG0OeYMqXJZP/ouePBOdy+2u+GgHT8llhtue9PODd9UxAJh9ywugr//yJfi7e/3kl3/8lrpMP3tdn/W0r95gV5o6Nklzkuqltz+Xa6iF4kVHQLXo9/2aZpBLuFpho71INAWVL5JwZd5wyNuKMi825Png0hIGy51LwGZQ8V9HAx9HOY5Xu6zZ4Ju/M9T1b3g4TL1Bl038QElYHhfF2cquRCnwOUqxVFXPCDqYZpEFgyssScUsxKcGOjA2bQTbdNb3HoH4YEo+g4jWofLz9I28VBsUwjIkLRkSGMIlPBUa/QxY4JVxAQ3trbnc2xDFMAFwAJgOPeOQ3ADTUdjA5CGAfwdY1Il0gyEHcpwTyBoS9o+Wg5JOhukEHhht0FZY8j8Ggy6WW0OZoB8+wPj/8DumkmQQRYxefsqtdQhFGKI5dhPwBQqNRG4TizdEOofCY82f2NhjLCN++ng3XHwmhkyIunoUNHkBVlmOKGc90GKoi4kImfMozzN5wvjDGLyDl2qiIC8mr2JRiYv2PKiBb+FcXnoe2OF6VneO1KVWxUohxweSmFr3WGZwyThgnMydnVellymwXDtLLeSHILpKQQqRyKyuSWExBuoKP1uiHvAUwLNP7aqb4e8Gv3LNK8LOOy1iDomMGuRiwLIQjxO0dlx3gGhlC1CGdJRezmOl9XR1PyXPZKUKDT6fTRyktHZPHmttFg1CIwDl1EPLGNyc3deq5QZczR69nUYPLT3D8Qh7gXKTrK3l13f5SzBeWCBbk4St5eb0ur+TlnVz367i+4vBClK5vCY6jttd8YvJflrsu6mkNbe/cxvah/cBpjG2r7pnIXU2vT2n7tRE7uBCxa3n0kdQOz0rtmqanFbX7g0vR+hTiiN1xnYna5U1dWepqfDmS1OeV+x2S+jSSEf+8kpFDYldzwiNJ7V+I1MeN1s4X8E3pKZZ4rr9vaf0ZVhzqP9czrGhztPEMa3v0nuQNfeh4RT/o9HOJZs6eJ+p26P+k6k8bj03qAfm+aiVd5KC1TPBCGwnjBf05rCcQLHAM7cihV/BUejVz9ut3tz+QrAOJhrXZ31QodCjkP14hVdx/HLfxlT4y9F98Bw==</diagram></mxfile>
|
|
||||||
683
appdoc/doc_workflow_actions_rbac_concept.md
Normal file
|
|
@ -0,0 +1,683 @@
|
||||||
|
# Workflow Actions RBAC Integration Concept
|
||||||
|
|
||||||
|
## Übersicht
|
||||||
|
|
||||||
|
Dieses Dokument beschreibt das Konzept für die Umstrukturierung der Workflow Actions, um:
|
||||||
|
1. **RBAC-Integration** zu ermöglichen (Schutz von Actions über RESOURCE-Context)
|
||||||
|
2. **Strukturierte Parameter-Definitionen** statt Docstrings zu verwenden
|
||||||
|
3. **UI-Rendering-Typen** für Parameter zu definieren
|
||||||
|
4. **Keine Duplikation** von Parameter-Definitionen zu haben
|
||||||
|
5. **Plug-and-Play** Funktionalität beizubehalten
|
||||||
|
|
||||||
|
## Architektur-Konzept
|
||||||
|
|
||||||
|
### Grundprinzip: Deklarative Action-Definition
|
||||||
|
|
||||||
|
Ähnlich wie bei `aicore` Models, wo eine Struktur definiert wird und die Funktion ein Attribut ist, werden Actions jetzt deklarativ definiert:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MethodOutlook(MethodBase):
|
||||||
|
def __init__(self, services):
|
||||||
|
super().__init__(services)
|
||||||
|
self.name = "outlook"
|
||||||
|
self.description = "Handle Microsoft Outlook email operations"
|
||||||
|
|
||||||
|
# Actions werden deklarativ definiert
|
||||||
|
self._actions = {
|
||||||
|
"readEmails": ActionDefinition(
|
||||||
|
actionId="outlook.readEmails", # Für RBAC: RESOURCE context
|
||||||
|
description="Read emails from Outlook mailbox",
|
||||||
|
parameters={
|
||||||
|
"connectionReference": ActionParameter(
|
||||||
|
name="connectionReference",
|
||||||
|
type=str,
|
||||||
|
frontendType="select",
|
||||||
|
frontendOptions="user.connection",
|
||||||
|
required=True,
|
||||||
|
description="Microsoft connection label"
|
||||||
|
),
|
||||||
|
"query": ActionParameter(
|
||||||
|
name="query",
|
||||||
|
type=str,
|
||||||
|
frontendType="text",
|
||||||
|
required=False,
|
||||||
|
description="Search query for emails"
|
||||||
|
),
|
||||||
|
"folder": ActionParameter(
|
||||||
|
name="folder",
|
||||||
|
type=str,
|
||||||
|
frontendType="select",
|
||||||
|
frontendOptions="outlook.folder",
|
||||||
|
required=False,
|
||||||
|
description="Folder name (e.g., 'Inbox', 'Drafts')"
|
||||||
|
),
|
||||||
|
"limit": ActionParameter(
|
||||||
|
name="limit",
|
||||||
|
type=int,
|
||||||
|
frontendType="number",
|
||||||
|
required=False,
|
||||||
|
default=50,
|
||||||
|
description="Maximum number of emails to return"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
execute=self._executeReadEmails # Funktion als Attribut
|
||||||
|
),
|
||||||
|
"sendEmail": ActionDefinition(
|
||||||
|
actionId="outlook.sendEmail",
|
||||||
|
description="Send email via Outlook",
|
||||||
|
parameters={...},
|
||||||
|
execute=self._executeSendEmail
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _executeReadEmails(self, parameters: Dict[str, Any]) -> ActionResult:
|
||||||
|
"""Execute function - keine Parameter-Definition mehr hier"""
|
||||||
|
# Implementation...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Globale Frontend-Type-Definition
|
||||||
|
|
||||||
|
**WICHTIG**: Frontend-Types werden zentral in `modules/shared/frontendTypes.py` definiert, nicht redundant pro Action.
|
||||||
|
|
||||||
|
Die globale `FrontendType` Enum enthält:
|
||||||
|
- **Standard Types**: `text`, `textarea`, `number`, `select`, `multiselect`, `checkbox`, `date`, `datetime`, `email`, `timestamp`, `json`, `multilingual`, `file`
|
||||||
|
- **Custom Types für Actions**: `userConnection`, `documentReference`, `workflowAction`
|
||||||
|
|
||||||
|
Custom-Types unterstützen dynamische Option-Listen über API-Endpoints:
|
||||||
|
- `userConnection` → `/api/options/user.connection` (Connections des aktuellen Users)
|
||||||
|
- `documentReference` → `/api/options/workflow.documentReference` (Document-Referenzen aus Workflow-Context)
|
||||||
|
- `workflowAction` → `/api/options/workflow.action` (Verfügbare Actions aus Workflow-Context)
|
||||||
|
|
||||||
|
## Datenmodelle
|
||||||
|
|
||||||
|
### ActionParameter
|
||||||
|
|
||||||
|
**WICHTIG**: Frontend-Types werden global definiert in `modules/shared/frontendTypes.py` und nicht redundant in Actions.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Optional, Any, Union, List, Dict
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from modules.shared.frontendTypes import FrontendType # Globale Definition
|
||||||
|
|
||||||
|
class ActionParameter(BaseModel):
|
||||||
|
"""Parameter definition for an action"""
|
||||||
|
name: str = Field(description="Parameter name")
|
||||||
|
type: str = Field(description="Python type as string (e.g., 'str', 'int', 'bool', 'List[str]')")
|
||||||
|
frontendType: FrontendType = Field(description="UI rendering type (from global FrontendType enum)")
|
||||||
|
frontendOptions: Optional[Union[str, List[Dict[str, Any]]]] = Field(
|
||||||
|
None,
|
||||||
|
description="Options for select/multiselect/custom types. String reference (e.g., 'user.connection') or static list. For custom types like userConnection, this is automatically set to the API endpoint."
|
||||||
|
)
|
||||||
|
required: bool = Field(False, description="Whether parameter is required")
|
||||||
|
default: Optional[Any] = Field(None, description="Default value")
|
||||||
|
description: str = Field("", description="Parameter description")
|
||||||
|
validation: Optional[Dict[str, Any]] = Field(
|
||||||
|
None,
|
||||||
|
description="Validation rules (e.g., {'min': 1, 'max': 100})"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Custom Frontend Types**:
|
||||||
|
- `FrontendType.USER_CONNECTION`: User connection selector - dynamische Options von `/api/options/user.connection`
|
||||||
|
- `FrontendType.DOCUMENT_REFERENCE`: Document reference selector - dynamische Options aus Workflow-Context
|
||||||
|
- `FrontendType.WORKFLOW_ACTION`: Workflow action selector - dynamische Options aus verfügbaren Actions
|
||||||
|
|
||||||
|
Für Custom-Types wird `frontendOptions` automatisch auf den entsprechenden API-Endpoint gesetzt (z.B. `"user.connection"`).
|
||||||
|
|
||||||
|
### ActionDefinition
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Dict, Callable, Awaitable
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
class ActionDefinition(BaseModel):
|
||||||
|
"""Complete definition of an action"""
|
||||||
|
actionId: str = Field(
|
||||||
|
description="Unique action identifier for RBAC (format: 'module.actionName', e.g., 'outlook.readEmails')"
|
||||||
|
)
|
||||||
|
description: str = Field(description="Action description")
|
||||||
|
parameters: Dict[str, ActionParameter] = Field(
|
||||||
|
default_factory=dict,
|
||||||
|
description="Parameter definitions"
|
||||||
|
)
|
||||||
|
execute: Callable[[Dict[str, Any]], Awaitable[ActionResult]] = Field(
|
||||||
|
description="Execution function - async function that takes parameters dict and returns ActionResult"
|
||||||
|
)
|
||||||
|
# Optional metadata
|
||||||
|
category: Optional[str] = Field(None, description="Action category for grouping")
|
||||||
|
tags: List[str] = Field(default_factory=list, description="Tags for search/filtering")
|
||||||
|
```
|
||||||
|
|
||||||
|
## MethodBase Erweiterung
|
||||||
|
|
||||||
|
### Neue MethodBase Struktur
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MethodBase:
|
||||||
|
"""Base class for all methods"""
|
||||||
|
|
||||||
|
def __init__(self, services: Any):
|
||||||
|
self.services = services
|
||||||
|
self.name: str
|
||||||
|
self.description: str
|
||||||
|
self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
|
||||||
|
|
||||||
|
# Actions werden als Dictionary definiert
|
||||||
|
self._actions: Dict[str, ActionDefinition] = {}
|
||||||
|
|
||||||
|
# Nach Initialisierung: Actions registrieren
|
||||||
|
self._registerActions()
|
||||||
|
|
||||||
|
def _registerActions(self):
|
||||||
|
"""Register all actions defined in _actions"""
|
||||||
|
# Kann überschrieben werden für dynamische Registrierung
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def actions(self) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Dynamically collect all actions from _actions dictionary.
|
||||||
|
Returns format compatible with existing system.
|
||||||
|
"""
|
||||||
|
result = {}
|
||||||
|
for actionName, actionDef in self._actions.items():
|
||||||
|
# RBAC-Check: Prüfe ob Action für aktuellen User verfügbar ist
|
||||||
|
if not self._checkActionPermission(actionDef.actionId):
|
||||||
|
continue # Skip if user doesn't have permission
|
||||||
|
|
||||||
|
# Konvertiere ActionDefinition zu altem Format für Kompatibilität
|
||||||
|
result[actionName] = {
|
||||||
|
'description': actionDef.description,
|
||||||
|
'parameters': self._convertParametersToOldFormat(actionDef.parameters),
|
||||||
|
'method': self._createActionWrapper(actionDef)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _checkActionPermission(self, actionId: str) -> bool:
|
||||||
|
"""
|
||||||
|
Check if current user has permission to execute this action.
|
||||||
|
Uses RBAC RESOURCE context.
|
||||||
|
"""
|
||||||
|
if not hasattr(self.services, 'rbac') or not self.services.rbac:
|
||||||
|
# Fallback: Allow if RBAC not available (backward compatibility)
|
||||||
|
return True
|
||||||
|
|
||||||
|
currentUser = self.services.chat.getCurrentUser()
|
||||||
|
if not currentUser:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# RBAC-Check: RESOURCE context, item = actionId
|
||||||
|
permissions = self.services.rbac.getUserPermissions(
|
||||||
|
user=currentUser,
|
||||||
|
context=AccessRuleContext.RESOURCE,
|
||||||
|
item=actionId
|
||||||
|
)
|
||||||
|
|
||||||
|
return permissions.view
|
||||||
|
|
||||||
|
def _convertParametersToOldFormat(self, parameters: Dict[str, ActionParameter]) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""Convert ActionParameter dict to old format for compatibility"""
|
||||||
|
result = {}
|
||||||
|
for paramName, param in parameters.items():
|
||||||
|
result[paramName] = {
|
||||||
|
'type': param.type,
|
||||||
|
'required': param.required,
|
||||||
|
'description': param.description,
|
||||||
|
'default': param.default,
|
||||||
|
'frontendType': param.frontendType.value,
|
||||||
|
'frontendOptions': param.frontendOptions
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _createActionWrapper(self, actionDef: ActionDefinition):
|
||||||
|
"""Create wrapper function that matches old action signature"""
|
||||||
|
async def wrapper(parameters: Dict[str, Any], *args, **kwargs):
|
||||||
|
# Parameter-Validierung basierend auf ActionParameter definitions
|
||||||
|
validatedParams = self._validateParameters(parameters, actionDef.parameters)
|
||||||
|
|
||||||
|
# Execute action
|
||||||
|
return await actionDef.execute(validatedParams, *args, **kwargs)
|
||||||
|
|
||||||
|
wrapper.is_action = True
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
def _validateParameters(self, parameters: Dict[str, Any], paramDefs: Dict[str, ActionParameter]) -> Dict[str, Any]:
|
||||||
|
"""Validate parameters against definitions"""
|
||||||
|
validated = {}
|
||||||
|
|
||||||
|
for paramName, paramDef in paramDefs.items():
|
||||||
|
value = parameters.get(paramName)
|
||||||
|
|
||||||
|
# Check required
|
||||||
|
if paramDef.required and value is None:
|
||||||
|
raise ValueError(f"Required parameter '{paramName}' is missing")
|
||||||
|
|
||||||
|
# Use default if not provided
|
||||||
|
if value is None and paramDef.default is not None:
|
||||||
|
value = paramDef.default
|
||||||
|
|
||||||
|
# Type validation
|
||||||
|
if value is not None:
|
||||||
|
value = self._validateType(value, paramDef.type)
|
||||||
|
|
||||||
|
# Custom validation rules
|
||||||
|
if paramDef.validation and value is not None:
|
||||||
|
self._applyValidationRules(value, paramDef.validation)
|
||||||
|
|
||||||
|
validated[paramName] = value
|
||||||
|
|
||||||
|
return validated
|
||||||
|
|
||||||
|
def _validateType(self, value: Any, expectedType: type) -> Any:
|
||||||
|
"""Validate and convert value to expected type"""
|
||||||
|
# Type validation logic...
|
||||||
|
if expectedType == int:
|
||||||
|
return int(value)
|
||||||
|
elif expectedType == str:
|
||||||
|
return str(value)
|
||||||
|
# ... weitere Typen
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _applyValidationRules(self, value: Any, rules: Dict[str, Any]):
|
||||||
|
"""Apply custom validation rules"""
|
||||||
|
if 'min' in rules and value < rules['min']:
|
||||||
|
raise ValueError(f"Value must be >= {rules['min']}")
|
||||||
|
if 'max' in rules and value > rules['max']:
|
||||||
|
raise ValueError(f"Value must be <= {rules['max']}")
|
||||||
|
# ... weitere Validierungsregeln
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migrationsstrategie
|
||||||
|
|
||||||
|
### Schritt 1: Neue Datenmodelle erstellen
|
||||||
|
|
||||||
|
**Datei**: `gateway/modules/datamodels/datamodelWorkflowActions.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import Optional, Any, Union, List, Dict, Callable, Awaitable
|
||||||
|
from enum import Enum
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from modules.datamodels.datamodelChat import ActionResult
|
||||||
|
from modules.shared.frontendTypes import FrontendType # Globale Definition verwenden
|
||||||
|
|
||||||
|
class ActionParameter(BaseModel):
|
||||||
|
"""Parameter definition for an action"""
|
||||||
|
name: str
|
||||||
|
type: str # String representation of type: "str", "int", "bool", "List[str]", etc.
|
||||||
|
frontendType: FrontendType
|
||||||
|
frontendOptions: Optional[Union[str, List[Dict[str, Any]]]] = None
|
||||||
|
required: bool = False
|
||||||
|
default: Optional[Any] = None
|
||||||
|
description: str = ""
|
||||||
|
validation: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
|
class ActionDefinition(BaseModel):
|
||||||
|
"""Complete definition of an action"""
|
||||||
|
actionId: str # Format: "module.actionName" (e.g., "outlook.readEmails")
|
||||||
|
description: str
|
||||||
|
parameters: Dict[str, ActionParameter] = Field(default_factory=dict)
|
||||||
|
execute: Optional[Callable] = None # Will be set dynamically
|
||||||
|
category: Optional[str] = None
|
||||||
|
tags: List[str] = Field(default_factory=list)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schritt 2: MethodBase erweitern
|
||||||
|
|
||||||
|
**Datei**: `gateway/modules/workflows/methods/methodBase.py`
|
||||||
|
|
||||||
|
- Neue `_actions` Dictionary Property
|
||||||
|
- RBAC-Check Integration
|
||||||
|
- Parameter-Validierung
|
||||||
|
- Kompatibilität mit bestehendem System
|
||||||
|
|
||||||
|
### Schritt 3: Beispiel-Migration
|
||||||
|
|
||||||
|
**Vorher** (methodOutlook.py):
|
||||||
|
```python
|
||||||
|
@action
|
||||||
|
async def readEmails(self, parameters: Dict[str, Any]) -> ActionResult:
|
||||||
|
"""
|
||||||
|
GENERAL:
|
||||||
|
- Purpose: Read emails from Outlook mailbox
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- connectionReference (str, required): Microsoft connection label.
|
||||||
|
- query (str, optional): Search query for emails.
|
||||||
|
- folder (str, optional): Folder name.
|
||||||
|
- limit (int, optional): Maximum number of emails. Default: 50.
|
||||||
|
"""
|
||||||
|
# Implementation...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nachher** (methodOutlook.py):
|
||||||
|
```python
|
||||||
|
def __init__(self, services):
|
||||||
|
super().__init__(services)
|
||||||
|
self.name = "outlook"
|
||||||
|
self.description = "Handle Microsoft Outlook email operations"
|
||||||
|
|
||||||
|
self._actions = {
|
||||||
|
"readEmails": ActionDefinition(
|
||||||
|
actionId="outlook.readEmails",
|
||||||
|
description="Read emails from Outlook mailbox",
|
||||||
|
parameters={
|
||||||
|
"connectionReference": ActionParameter(
|
||||||
|
name="connectionReference",
|
||||||
|
type="str",
|
||||||
|
frontendType=FrontendType.USER_CONNECTION, # Custom type - automatisch API-Endpoint
|
||||||
|
required=True,
|
||||||
|
description="Microsoft connection label"
|
||||||
|
),
|
||||||
|
"query": ActionParameter(
|
||||||
|
name="query",
|
||||||
|
type="str",
|
||||||
|
frontendType=FrontendType.TEXT,
|
||||||
|
required=False,
|
||||||
|
description="Search query for emails"
|
||||||
|
),
|
||||||
|
"folder": ActionParameter(
|
||||||
|
name="folder",
|
||||||
|
type="str",
|
||||||
|
frontendType=FrontendType.SELECT,
|
||||||
|
frontendOptions="outlook.folder",
|
||||||
|
required=False,
|
||||||
|
description="Folder name (e.g., 'Inbox', 'Drafts')"
|
||||||
|
),
|
||||||
|
"limit": ActionParameter(
|
||||||
|
name="limit",
|
||||||
|
type="int",
|
||||||
|
frontendType=FrontendType.NUMBER,
|
||||||
|
required=False,
|
||||||
|
default=50,
|
||||||
|
description="Maximum number of emails to return",
|
||||||
|
validation={"min": 1, "max": 1000}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
execute=self._executeReadEmails
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _executeReadEmails(self, parameters: Dict[str, Any]) -> ActionResult:
|
||||||
|
"""Execute function - keine Parameter-Definition mehr hier"""
|
||||||
|
# Implementation bleibt gleich...
|
||||||
|
```
|
||||||
|
|
||||||
|
## RBAC-Integration
|
||||||
|
|
||||||
|
### Action-IDs Format
|
||||||
|
|
||||||
|
Actions werden im RBAC-System als RESOURCE-Context Items behandelt:
|
||||||
|
|
||||||
|
- **Format**: `{moduleName}.{actionName}`
|
||||||
|
- **Beispiele**:
|
||||||
|
- `outlook.readEmails`
|
||||||
|
- `outlook.sendEmail`
|
||||||
|
- `sharepoint.uploadDocument`
|
||||||
|
- `ai.process`
|
||||||
|
|
||||||
|
### RBAC-Regeln für Actions
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"roleLabel": "user",
|
||||||
|
"context": "RESOURCE",
|
||||||
|
"item": "outlook.readEmails",
|
||||||
|
"view": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"roleLabel": "admin",
|
||||||
|
"context": "RESOURCE",
|
||||||
|
"item": "outlook",
|
||||||
|
"view": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Hierarchie**: Spezifische Action-Regeln überschreiben generische Module-Regeln.
|
||||||
|
|
||||||
|
### Bootstrap: Default RBAC Rules für Actions
|
||||||
|
|
||||||
|
In `interfaceBootstrap.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def initRbacRules(db: DatabaseConnector) -> None:
|
||||||
|
# ... existing rules ...
|
||||||
|
|
||||||
|
# Action Rules (RESOURCE context)
|
||||||
|
createActionRules(db)
|
||||||
|
|
||||||
|
def createActionRules(db: DatabaseConnector):
|
||||||
|
"""Create default RBAC rules for workflow actions"""
|
||||||
|
|
||||||
|
# SysAdmin: Access to all actions
|
||||||
|
db.recordCreate(AccessRule(
|
||||||
|
roleLabel="sysadmin",
|
||||||
|
context=AccessRuleContext.RESOURCE,
|
||||||
|
item=None, # All resources
|
||||||
|
view=True
|
||||||
|
))
|
||||||
|
|
||||||
|
# Admin: Access to all actions
|
||||||
|
db.recordCreate(AccessRule(
|
||||||
|
roleLabel="admin",
|
||||||
|
context=AccessRuleContext.RESOURCE,
|
||||||
|
item=None,
|
||||||
|
view=True
|
||||||
|
))
|
||||||
|
|
||||||
|
# User: Access to specific actions only
|
||||||
|
userActions = [
|
||||||
|
"outlook.readEmails",
|
||||||
|
"outlook.sendEmail",
|
||||||
|
"sharepoint.readDocuments",
|
||||||
|
"ai.process"
|
||||||
|
]
|
||||||
|
|
||||||
|
for actionId in userActions:
|
||||||
|
db.recordCreate(AccessRule(
|
||||||
|
roleLabel="user",
|
||||||
|
context=AccessRuleContext.RESOURCE,
|
||||||
|
item=actionId,
|
||||||
|
view=True
|
||||||
|
))
|
||||||
|
|
||||||
|
# Viewer: Read-only actions
|
||||||
|
viewerActions = [
|
||||||
|
"outlook.readEmails",
|
||||||
|
"sharepoint.readDocuments"
|
||||||
|
]
|
||||||
|
|
||||||
|
for actionId in viewerActions:
|
||||||
|
db.recordCreate(AccessRule(
|
||||||
|
roleLabel="viewer",
|
||||||
|
context=AccessRuleContext.RESOURCE,
|
||||||
|
item=actionId,
|
||||||
|
view=True
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Vorteile
|
||||||
|
|
||||||
|
### 1. Keine Duplikation
|
||||||
|
- Parameter werden nur einmal definiert (in `ActionDefinition`)
|
||||||
|
- Keine Docstring-Parsing mehr nötig
|
||||||
|
- Type-Safety durch Pydantic Models
|
||||||
|
|
||||||
|
### 2. RBAC-Integration
|
||||||
|
- Jede Action hat eine eindeutige ID für RBAC
|
||||||
|
- Granulare Kontrolle pro Action möglich
|
||||||
|
- Hierarchische Regeln (Module → Action)
|
||||||
|
|
||||||
|
### 3. UI-Rendering
|
||||||
|
- Frontend-Typen explizit definiert
|
||||||
|
- Options-Referenzen für dynamische Optionen
|
||||||
|
- Validierung auf Backend-Ebene
|
||||||
|
|
||||||
|
### 4. Plug-and-Play
|
||||||
|
- Actions bleiben als separate Method-Klassen
|
||||||
|
- Einfache Erweiterung durch neue Method-Klassen
|
||||||
|
- Kompatibilität mit bestehendem System
|
||||||
|
|
||||||
|
### 5. Type Safety
|
||||||
|
- Pydantic Models für Validierung
|
||||||
|
- Type-Hints für bessere IDE-Unterstützung
|
||||||
|
- Runtime-Validierung
|
||||||
|
|
||||||
|
## Migration Timeline
|
||||||
|
|
||||||
|
### Phase 1: Foundation (Woche 1)
|
||||||
|
- ✅ Datenmodelle erstellen (`datamodelWorkflowActions.py`)
|
||||||
|
- ✅ MethodBase erweitern
|
||||||
|
- ✅ RBAC-Integration in MethodBase
|
||||||
|
|
||||||
|
### Phase 2: Beispiel-Migration (Woche 2)
|
||||||
|
- 📝 Ein Method-Beispiel migrieren (z.B. `methodAi.py`)
|
||||||
|
- 📝 Tests schreiben
|
||||||
|
- 📝 Dokumentation aktualisieren
|
||||||
|
|
||||||
|
### Phase 3: Vollständige Migration (Woche 3-4)
|
||||||
|
- 📝 Alle Methods migrieren
|
||||||
|
- 📝 RBAC-Regeln in Bootstrap erstellen
|
||||||
|
- 📝 Frontend-Integration
|
||||||
|
|
||||||
|
### Phase 4: Testing & Cleanup (Woche 5)
|
||||||
|
- 📝 Unit Tests
|
||||||
|
- 📝 Integration Tests
|
||||||
|
- 📝 Performance Tests
|
||||||
|
- 📝 Alte Docstring-Parsing-Logik entfernen
|
||||||
|
|
||||||
|
## Offene Fragen
|
||||||
|
|
||||||
|
1. **Backward Compatibility**: Sollen alte Actions ohne `_actions` Dictionary weiterhin funktionieren?
|
||||||
|
- **Antwort**: Ja, MethodBase prüft zuerst `_actions`, dann fallback auf `@action` Decorator
|
||||||
|
|
||||||
|
2. **Parameter-Validierung**: Soll Validierung strikt sein oder tolerant?
|
||||||
|
- **Antwort**: Konfigurierbar pro Action
|
||||||
|
|
||||||
|
3. **Action-Discovery**: Sollen Actions zur Laufzeit registriert werden können?
|
||||||
|
- **Antwort**: Ja, über `_registerActions()` Methode
|
||||||
|
|
||||||
|
4. **Frontend-Integration**: Wie werden Actions im Frontend angezeigt?
|
||||||
|
- **Antwort**: API-Endpoint `/api/workflows/actions` liefert strukturierte Action-Definitionen
|
||||||
|
|
||||||
|
## API-Endpunkte
|
||||||
|
|
||||||
|
### GET /api/workflows/actions
|
||||||
|
Liefert alle verfügbaren Actions für den aktuellen User (gefiltert nach RBAC):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"module": "outlook",
|
||||||
|
"actionId": "outlook.readEmails",
|
||||||
|
"name": "readEmails",
|
||||||
|
"description": "Read emails from Outlook mailbox",
|
||||||
|
"parameters": {
|
||||||
|
"connectionReference": {
|
||||||
|
"type": "str",
|
||||||
|
"frontendType": "userConnection",
|
||||||
|
"frontendOptions": "user.connection", # Automatisch für Custom-Types
|
||||||
|
"required": true,
|
||||||
|
"description": "Microsoft connection label"
|
||||||
|
},
|
||||||
|
"documentList": {
|
||||||
|
"type": "List[str]",
|
||||||
|
"frontendType": "documentReference",
|
||||||
|
"frontendOptions": "workflow.documentReference", # Automatisch für Custom-Types
|
||||||
|
"required": false,
|
||||||
|
"description": "Document list reference(s) from previous actions"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /api/workflows/actions/{module}
|
||||||
|
Liefert Actions für ein spezifisches Modul.
|
||||||
|
|
||||||
|
### POST /api/workflows/actions/{module}/{action}/execute
|
||||||
|
Führt eine Action aus (mit RBAC-Check).
|
||||||
|
|
||||||
|
## Custom Frontend Types für Actions
|
||||||
|
|
||||||
|
### Verfügbare Custom Types
|
||||||
|
|
||||||
|
1. **`FrontendType.USER_CONNECTION`**
|
||||||
|
- **API-Endpoint**: `/api/options/user.connection`
|
||||||
|
- **Beschreibung**: Zeigt alle aktiven Connections des aktuellen Users
|
||||||
|
- **Verwendung**: Für Parameter wie `connectionReference` in Outlook/SharePoint Actions
|
||||||
|
- **Beispiel**:
|
||||||
|
```python
|
||||||
|
ActionParameter(
|
||||||
|
name="connectionReference",
|
||||||
|
type="str",
|
||||||
|
frontendType=FrontendType.USER_CONNECTION,
|
||||||
|
required=True,
|
||||||
|
description="Microsoft connection label"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **`FrontendType.DOCUMENT_REFERENCE`**
|
||||||
|
- **API-Endpoint**: `/api/options/workflow.documentReference` (zu implementieren)
|
||||||
|
- **Beschreibung**: Zeigt verfügbare Document-Referenzen aus dem aktuellen Workflow-Context
|
||||||
|
- **Verwendung**: Für Parameter wie `documentList` in Actions, die auf vorherige Action-Ergebnisse verweisen
|
||||||
|
- **Beispiel**:
|
||||||
|
```python
|
||||||
|
ActionParameter(
|
||||||
|
name="documentList",
|
||||||
|
type="List[str]",
|
||||||
|
frontendType=FrontendType.DOCUMENT_REFERENCE,
|
||||||
|
required=False,
|
||||||
|
description="Document list reference(s) from previous actions"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **`FrontendType.WORKFLOW_ACTION`**
|
||||||
|
- **API-Endpoint**: `/api/options/workflow.action` (zu implementieren)
|
||||||
|
- **Beschreibung**: Zeigt verfügbare Actions aus dem Workflow-Context
|
||||||
|
- **Verwendung**: Für Parameter, die auf andere Actions verweisen
|
||||||
|
|
||||||
|
### Custom Types erweitern
|
||||||
|
|
||||||
|
Neue Custom-Types können über `frontendTypes.py` registriert werden:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from modules.shared.frontendTypes import FrontendType, registerCustomType
|
||||||
|
|
||||||
|
# Neuer Custom-Type hinzufügen
|
||||||
|
FrontendType.SHAREPOINT_FOLDER = "sharepointFolder"
|
||||||
|
|
||||||
|
# Registrieren
|
||||||
|
registerCustomType(
|
||||||
|
frontendType=FrontendType.SHAREPOINT_FOLDER,
|
||||||
|
optionsApiEndpoint="sharepoint.folder",
|
||||||
|
description={
|
||||||
|
"en": "SharePoint Folder",
|
||||||
|
"fr": "Dossier SharePoint",
|
||||||
|
"de": "SharePoint-Ordner"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend-Integration
|
||||||
|
|
||||||
|
Das Frontend muss:
|
||||||
|
1. Custom-Types erkennen (z.B. `frontendType === "userConnection"`)
|
||||||
|
2. Automatisch Options von `/api/options/{optionsName}` laden
|
||||||
|
3. Die Options als Select/Multiselect rendern
|
||||||
|
|
||||||
|
**Beispiel Frontend-Logik**:
|
||||||
|
```typescript
|
||||||
|
if (param.frontendType === 'userConnection') {
|
||||||
|
// Automatisch Options von /api/options/user.connection laden
|
||||||
|
const options = await fetch(`/api/options/${param.frontendOptions}`);
|
||||||
|
// Als Select rendern
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
BIN
platform_overview.zip
Normal file
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |