Merge pull request #91 from valueonag/feat/cost-control
fixed automation bug rbac set
This commit is contained in:
commit
a0a76b1337
4 changed files with 46 additions and 13 deletions
|
|
@ -42,8 +42,10 @@ class AutomationObjects:
|
||||||
# Initialize database with proper configuration
|
# Initialize database with proper configuration
|
||||||
self._initializeDatabase()
|
self._initializeDatabase()
|
||||||
|
|
||||||
# Initialize RBAC (dbApp = self.db since we use poweron_app)
|
# Initialize RBAC - AccessRules are in poweron_app, not poweron_automation!
|
||||||
self.rbac = RbacClass(self.db, dbApp=self.db)
|
from modules.security.rootAccess import getRootDbAppConnector
|
||||||
|
dbApp = getRootDbAppConnector()
|
||||||
|
self.rbac = RbacClass(self.db, dbApp=dbApp)
|
||||||
|
|
||||||
# Update database context
|
# Update database context
|
||||||
self.db.updateContext(self.userId)
|
self.db.updateContext(self.userId)
|
||||||
|
|
@ -100,7 +102,9 @@ class AutomationObjects:
|
||||||
record = self.db.getRecordset(model, {"id": recordId})
|
record = self.db.getRecordset(model, {"id": recordId})
|
||||||
if record:
|
if record:
|
||||||
return record[0].get("_createdBy") == self.userId
|
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
|
return False
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ class EventManager:
|
||||||
"""
|
"""
|
||||||
queue = self._queues.get(context_id)
|
queue = self._queues.get(context_id)
|
||||||
if not queue:
|
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
|
return
|
||||||
|
|
||||||
event = {
|
event = {
|
||||||
|
|
|
||||||
|
|
@ -102,21 +102,30 @@ class RbacClass:
|
||||||
allRulesWithPriority = self._getRulesForRoleIds(roleIds, context, mandateId, featureInstanceId)
|
allRulesWithPriority = self._getRulesForRoleIds(roleIds, context, mandateId, featureInstanceId)
|
||||||
|
|
||||||
# Für jede Rolle die spezifischste Regel finden
|
# Für jede Rolle die spezifischste Regel finden
|
||||||
|
# Priority order: 1) Role priority (instance > mandate > global), 2) Item specificity (exact > prefix > generic)
|
||||||
rolePermissions = {}
|
rolePermissions = {}
|
||||||
for priority, rule in allRulesWithPriority:
|
for priority, rule in allRulesWithPriority:
|
||||||
# Find most specific rule for this item
|
# Find most specific rule for this item
|
||||||
if self._ruleMatchesItem(rule, item):
|
if self._ruleMatchesItem(rule, item):
|
||||||
roleId = rule.roleId
|
roleId = rule.roleId
|
||||||
# Speichere mit Priorität (höhere Priorität überschreibt)
|
# Calculate item specificity: 0=generic (null), 1=prefix match, 2=exact match
|
||||||
if roleId not in rolePermissions or priority > rolePermissions[roleId][0]:
|
itemSpecificity = self._getItemSpecificity(rule, item)
|
||||||
rolePermissions[roleId] = (priority, rule)
|
|
||||||
|
# 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
|
# Find highest priority among matching rules (role priority only, specificity already handled per role)
|
||||||
highestPriority = max((p for p, _ in rolePermissions.values()), default=0)
|
highestPriority = max((p for p, _, _ in rolePermissions.values()), default=0)
|
||||||
|
|
||||||
# Combine permissions ONLY from rules with highest priority
|
# Combine permissions ONLY from rules with highest priority
|
||||||
# This ensures instance-specific rules (Priority 3) override global rules (Priority 1)
|
# 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
|
# Only use rules with highest priority
|
||||||
if priority < highestPriority:
|
if priority < highestPriority:
|
||||||
continue
|
continue
|
||||||
|
|
@ -433,6 +442,23 @@ class RbacClass:
|
||||||
|
|
||||||
return False
|
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]:
|
def findMostSpecificRule(self, rules: List[AccessRule], item: str) -> Optional[AccessRule]:
|
||||||
"""
|
"""
|
||||||
Find the most specific rule for an item (longest matching prefix wins).
|
Find the most specific rule for an item (longest matching prefix wins).
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,9 @@ def createAutomationEventHandler(automationId: str, eventUser):
|
||||||
logger.error(f"Automation {automationId} has no creator user")
|
logger.error(f"Automation {automationId} has no creator user")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Get mandate context from automation definition
|
||||||
|
automationMandateId = getattr(automation, "mandateId", None)
|
||||||
|
|
||||||
# Get creator user from database using services
|
# Get creator user from database using services
|
||||||
eventServices = getServices(eventUser, None)
|
eventServices = getServices(eventUser, None)
|
||||||
creatorUser = eventServices.interfaceDbApp.getUser(creatorUserId)
|
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}")
|
logger.error(f"Creator user {creatorUserId} not found for automation {automationId}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get services for creator user (provides access to interfaces)
|
# Get services for creator user WITH mandate context from automation
|
||||||
creatorServices = getServices(creatorUser, None)
|
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
|
# executeAutomation is in same module, so we can call it directly
|
||||||
await executeAutomation(automationId, creatorServices)
|
await executeAutomation(automationId, creatorServices)
|
||||||
logger.info(f"Successfully executed automation {automationId} as user {creatorUserId}")
|
logger.info(f"Successfully executed automation {automationId} as user {creatorUserId}")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue