Merge pull request #91 from valueonag/feat/cost-control

fixed automation bug rbac set
This commit is contained in:
Patrick Motsch 2026-02-04 01:09:03 +01:00 committed by GitHub
commit a0a76b1337
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 46 additions and 13 deletions

View file

@ -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
# =========================================================================

View file

@ -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 = {

View file

@ -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).

View file

@ -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}")