From 555c9429fb23b2f886108c1333edc07a20831604 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Wed, 4 Feb 2026 01:07:42 +0100
Subject: [PATCH] fixed automation bug rbac set
---
.../automation/interfaceFeatureAutomation.py | 10 +++--
modules/features/chatbot/streaming/events.py | 2 +-
modules/security/rbac.py | 38 ++++++++++++++++---
modules/workflows/automation/mainWorkflow.py | 9 +++--
4 files changed, 46 insertions(+), 13 deletions(-)
diff --git a/modules/features/automation/interfaceFeatureAutomation.py b/modules/features/automation/interfaceFeatureAutomation.py
index 34102f5e..2bbf56e0 100644
--- a/modules/features/automation/interfaceFeatureAutomation.py
+++ b/modules/features/automation/interfaceFeatureAutomation.py
@@ -42,8 +42,10 @@ class AutomationObjects:
# Initialize database with proper configuration
self._initializeDatabase()
- # Initialize RBAC (dbApp = self.db since we use poweron_app)
- self.rbac = RbacClass(self.db, dbApp=self.db)
+ # Initialize RBAC - AccessRules are in poweron_app, not poweron_automation!
+ from modules.security.rootAccess import getRootDbAppConnector
+ dbApp = getRootDbAppConnector()
+ self.rbac = RbacClass(self.db, dbApp=dbApp)
# Update database context
self.db.updateContext(self.userId)
@@ -100,7 +102,9 @@ class AutomationObjects:
record = self.db.getRecordset(model, {"id": recordId})
if record:
return record[0].get("_createdBy") == self.userId
- return True
+ else:
+ return False # Record not found = no access
+ return True # No recordId needed (e.g., for CREATE)
return False
# =========================================================================
diff --git a/modules/features/chatbot/streaming/events.py b/modules/features/chatbot/streaming/events.py
index 732752ec..7a65205e 100644
--- a/modules/features/chatbot/streaming/events.py
+++ b/modules/features/chatbot/streaming/events.py
@@ -85,7 +85,7 @@ class EventManager:
"""
queue = self._queues.get(context_id)
if not queue:
- logger.warning(f"No queue found for workflow {context_id}, event not emitted")
+ # DEBUG level: This is normal for background workflows without active SSE listener
return
event = {
diff --git a/modules/security/rbac.py b/modules/security/rbac.py
index 5d20d1fb..11d6e11d 100644
--- a/modules/security/rbac.py
+++ b/modules/security/rbac.py
@@ -102,21 +102,30 @@ class RbacClass:
allRulesWithPriority = self._getRulesForRoleIds(roleIds, context, mandateId, featureInstanceId)
# Für jede Rolle die spezifischste Regel finden
+ # Priority order: 1) Role priority (instance > mandate > global), 2) Item specificity (exact > prefix > generic)
rolePermissions = {}
for priority, rule in allRulesWithPriority:
# Find most specific rule for this item
if self._ruleMatchesItem(rule, item):
roleId = rule.roleId
- # Speichere mit Priorität (höhere Priorität überschreibt)
- if roleId not in rolePermissions or priority > rolePermissions[roleId][0]:
- rolePermissions[roleId] = (priority, rule)
+ # Calculate item specificity: 0=generic (null), 1=prefix match, 2=exact match
+ itemSpecificity = self._getItemSpecificity(rule, item)
+
+ # Compare: first by role priority, then by item specificity
+ if roleId not in rolePermissions:
+ rolePermissions[roleId] = (priority, itemSpecificity, rule)
+ else:
+ existingPriority, existingSpecificity, _ = rolePermissions[roleId]
+ # Higher role priority wins, or same priority but higher specificity
+ if priority > existingPriority or (priority == existingPriority and itemSpecificity > existingSpecificity):
+ rolePermissions[roleId] = (priority, itemSpecificity, rule)
- # Find highest priority among matching rules
- highestPriority = max((p for p, _ in rolePermissions.values()), default=0)
+ # Find highest priority among matching rules (role priority only, specificity already handled per role)
+ highestPriority = max((p for p, _, _ in rolePermissions.values()), default=0)
# Combine permissions ONLY from rules with highest priority
# This ensures instance-specific rules (Priority 3) override global rules (Priority 1)
- for roleId, (priority, rule) in rolePermissions.items():
+ for roleId, (priority, specificity, rule) in rolePermissions.items():
# Only use rules with highest priority
if priority < highestPriority:
continue
@@ -433,6 +442,23 @@ class RbacClass:
return False
+ def _getItemSpecificity(self, rule: AccessRule, item: str) -> int:
+ """
+ Calculate the specificity of a rule's item match.
+
+ Returns:
+ 0 = generic rule (item=None)
+ 1 = prefix match
+ 2 = exact match
+ """
+ if rule.item is None:
+ return 0 # Generic rule - lowest specificity
+ if rule.item == item:
+ return 2 # Exact match - highest specificity
+ if item and item.startswith(rule.item + "."):
+ return 1 # Prefix match - medium specificity
+ return 0 # No match (shouldn't happen if _ruleMatchesItem was true)
+
def findMostSpecificRule(self, rules: List[AccessRule], item: str) -> Optional[AccessRule]:
"""
Find the most specific rule for an item (longest matching prefix wins).
diff --git a/modules/workflows/automation/mainWorkflow.py b/modules/workflows/automation/mainWorkflow.py
index 291f3120..e7c6839e 100644
--- a/modules/workflows/automation/mainWorkflow.py
+++ b/modules/workflows/automation/mainWorkflow.py
@@ -306,6 +306,9 @@ def createAutomationEventHandler(automationId: str, eventUser):
logger.error(f"Automation {automationId} has no creator user")
return
+ # Get mandate context from automation definition
+ automationMandateId = getattr(automation, "mandateId", None)
+
# Get creator user from database using services
eventServices = getServices(eventUser, None)
creatorUser = eventServices.interfaceDbApp.getUser(creatorUserId)
@@ -313,10 +316,10 @@ def createAutomationEventHandler(automationId: str, eventUser):
logger.error(f"Creator user {creatorUserId} not found for automation {automationId}")
return
- # Get services for creator user (provides access to interfaces)
- creatorServices = getServices(creatorUser, None)
+ # Get services for creator user WITH mandate context from automation
+ creatorServices = getServices(creatorUser, automationMandateId)
- # Execute automation with creator user's context
+ # Execute automation with creator user's context and mandate
# executeAutomation is in same module, so we can call it directly
await executeAutomation(automationId, creatorServices)
logger.info(f"Successfully executed automation {automationId} as user {creatorUserId}")