fixes
This commit is contained in:
parent
1b55db4581
commit
f15ed2e380
6 changed files with 135 additions and 23 deletions
|
|
@ -119,13 +119,12 @@ class AutomationObjects:
|
||||||
def _enrichAutomationsWithUserAndMandate(self, automations: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
def _enrichAutomationsWithUserAndMandate(self, automations: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Batch enrich automations with user names and mandate names for display.
|
Batch enrich automations with user names and mandate names for display.
|
||||||
Uses AppObjects interface to fetch users and mandates with proper access control.
|
Uses direct DB lookup (no RBAC) because this is purely cosmetic enrichment —
|
||||||
|
the user already has RBAC-verified access to the automations themselves.
|
||||||
"""
|
"""
|
||||||
if not automations:
|
if not automations:
|
||||||
return automations
|
return automations
|
||||||
|
|
||||||
from modules.interfaces.interfaceDbApp import getInterface as getAppInterface
|
|
||||||
|
|
||||||
# Collect all unique user IDs and mandate IDs
|
# Collect all unique user IDs and mandate IDs
|
||||||
userIds = set()
|
userIds = set()
|
||||||
mandateIds = set()
|
mandateIds = set()
|
||||||
|
|
@ -139,22 +138,33 @@ class AutomationObjects:
|
||||||
if mandateId:
|
if mandateId:
|
||||||
mandateIds.add(mandateId)
|
mandateIds.add(mandateId)
|
||||||
|
|
||||||
# Use AppObjects interface to fetch users (respects access control)
|
# Use root DB connector for display-only lookups (no RBAC needed)
|
||||||
appInterface = getAppInterface(self.currentUser)
|
try:
|
||||||
usersMap = {}
|
from modules.datamodels.datamodelUam import UserInDB, Mandate
|
||||||
if userIds:
|
from modules.security.rootAccess import getRootDbAppConnector
|
||||||
for userId in userIds:
|
dbAppConn = getRootDbAppConnector()
|
||||||
user = appInterface.getUser(userId)
|
|
||||||
if user:
|
# Batch fetch user display names
|
||||||
usersMap[userId] = user.username or user.email or userId
|
usersMap = {}
|
||||||
|
if userIds:
|
||||||
# Use AppObjects interface to fetch mandates (respects access control)
|
for userId in userIds:
|
||||||
mandatesMap = {}
|
users = dbAppConn.getRecordset(UserInDB, {"id": userId})
|
||||||
if mandateIds:
|
if users:
|
||||||
for mandateId in mandateIds:
|
user = users[0]
|
||||||
mandate = appInterface.getMandate(mandateId)
|
fullName = f"{user.get('firstName', '')} {user.get('lastName', '')}".strip()
|
||||||
if mandate:
|
usersMap[userId] = fullName or user.get("email") or user.get("username") or userId
|
||||||
mandatesMap[mandateId] = mandate.name or mandateId
|
|
||||||
|
# Batch fetch mandate display names
|
||||||
|
mandatesMap = {}
|
||||||
|
if mandateIds:
|
||||||
|
for mandateId in mandateIds:
|
||||||
|
mandates = dbAppConn.getRecordset(Mandate, {"id": mandateId})
|
||||||
|
if mandates:
|
||||||
|
mandatesMap[mandateId] = mandates[0].get("name") or mandateId
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not enrich automations with user/mandate names: {e}")
|
||||||
|
usersMap = {}
|
||||||
|
mandatesMap = {}
|
||||||
|
|
||||||
# Enrich each automation with the fetched data
|
# Enrich each automation with the fetched data
|
||||||
for automation in automations:
|
for automation in automations:
|
||||||
|
|
|
||||||
|
|
@ -266,9 +266,10 @@ def _ensureAccessRulesForRole(rootInterface, roleId: str, ruleTemplates: List[Di
|
||||||
existingRules = rootInterface.getAccessRulesByRole(roleId)
|
existingRules = rootInterface.getAccessRulesByRole(roleId)
|
||||||
|
|
||||||
# Create a set of existing rule signatures to avoid duplicates
|
# Create a set of existing rule signatures to avoid duplicates
|
||||||
|
# IMPORTANT: Use .value for enum comparison, not str() which gives "AccessRuleContext.DATA" in Python 3.11+
|
||||||
existingSignatures = set()
|
existingSignatures = set()
|
||||||
for rule in existingRules:
|
for rule in existingRules:
|
||||||
sig = (str(rule.context) if rule.context else None, rule.item)
|
sig = (rule.context.value if rule.context else None, rule.item)
|
||||||
existingSignatures.add(sig)
|
existingSignatures.add(sig)
|
||||||
|
|
||||||
createdCount = 0
|
createdCount = 0
|
||||||
|
|
|
||||||
|
|
@ -230,9 +230,10 @@ def _ensureAccessRulesForRole(rootInterface, roleId: str, ruleTemplates: List[Di
|
||||||
existingRules = rootInterface.getAccessRulesByRole(roleId)
|
existingRules = rootInterface.getAccessRulesByRole(roleId)
|
||||||
|
|
||||||
# Create a set of existing rule signatures to avoid duplicates
|
# Create a set of existing rule signatures to avoid duplicates
|
||||||
|
# IMPORTANT: Use .value for enum comparison, not str() which gives "AccessRuleContext.DATA" in Python 3.11+
|
||||||
existingSignatures = set()
|
existingSignatures = set()
|
||||||
for rule in existingRules:
|
for rule in existingRules:
|
||||||
sig = (str(rule.context) if rule.context else None, rule.item)
|
sig = (rule.context.value if rule.context else None, rule.item)
|
||||||
existingSignatures.add(sig)
|
existingSignatures.add(sig)
|
||||||
|
|
||||||
createdCount = 0
|
createdCount = 0
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,8 @@ def _ensureAccessRulesForRole(rootInterface, roleId: str, ruleTemplates: list) -
|
||||||
from modules.datamodels.datamodelRbac import AccessRule, AccessRuleContext
|
from modules.datamodels.datamodelRbac import AccessRule, AccessRuleContext
|
||||||
|
|
||||||
existingRules = rootInterface.getAccessRulesByRole(roleId)
|
existingRules = rootInterface.getAccessRulesByRole(roleId)
|
||||||
existingSignatures = {(str(r.context) if r.context else None, r.item) for r in existingRules}
|
# IMPORTANT: Use .value for enum comparison, not str() which gives "AccessRuleContext.DATA" in Python 3.11+
|
||||||
|
existingSignatures = {(r.context.value if r.context else None, r.item) for r in existingRules}
|
||||||
createdCount = 0
|
createdCount = 0
|
||||||
|
|
||||||
for template in ruleTemplates or []:
|
for template in ruleTemplates or []:
|
||||||
|
|
|
||||||
|
|
@ -394,9 +394,10 @@ def _ensureAccessRulesForRole(rootInterface, roleId: str, ruleTemplates: List[Di
|
||||||
existingRules = rootInterface.getAccessRulesByRole(roleId)
|
existingRules = rootInterface.getAccessRulesByRole(roleId)
|
||||||
|
|
||||||
# Create a set of existing rule signatures to avoid duplicates
|
# Create a set of existing rule signatures to avoid duplicates
|
||||||
|
# IMPORTANT: Use .value for enum comparison, not str() which gives "AccessRuleContext.DATA" in Python 3.11+
|
||||||
existingSignatures = set()
|
existingSignatures = set()
|
||||||
for rule in existingRules:
|
for rule in existingRules:
|
||||||
sig = (str(rule.context) if rule.context else None, rule.item)
|
sig = (rule.context.value if rule.context else None, rule.item)
|
||||||
existingSignatures.add(sig)
|
existingSignatures.add(sig)
|
||||||
|
|
||||||
createdCount = 0
|
createdCount = 0
|
||||||
|
|
|
||||||
|
|
@ -1192,3 +1192,101 @@ async def getCatalogStats(
|
||||||
status_code=500,
|
status_code=500,
|
||||||
detail=f"Failed to get catalog stats: {str(e)}"
|
detail=f"Failed to get catalog stats: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# CLEANUP: Remove duplicate AccessRules
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@router.post("/cleanup/duplicate-rules", response_model=dict)
|
||||||
|
@limiter.limit("5/minute")
|
||||||
|
async def cleanup_duplicate_access_rules(
|
||||||
|
request: Request,
|
||||||
|
dryRun: bool = Query(True, description="If true, only report duplicates without deleting"),
|
||||||
|
currentUser: User = Depends(requireSysAdmin)
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Find and remove duplicate AccessRules.
|
||||||
|
|
||||||
|
Duplicates are rules with the same (roleId, context, item) signature.
|
||||||
|
Only the first rule (oldest) is kept, all others are deleted.
|
||||||
|
|
||||||
|
Query Parameters:
|
||||||
|
- dryRun: If true (default), only report what would be deleted. Set to false to actually delete.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- Summary with counts and details of duplicates found/removed
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
rootInterface = getRootInterface()
|
||||||
|
|
||||||
|
# Get ALL AccessRules from DB
|
||||||
|
allRules = rootInterface.db.getRecordset(AccessRule)
|
||||||
|
|
||||||
|
# Group by signature (roleId, context, item)
|
||||||
|
rulesBySignature: Dict[tuple, list] = {}
|
||||||
|
for rule in allRules:
|
||||||
|
context = rule.get("context", "")
|
||||||
|
# Normalize context enum value
|
||||||
|
if hasattr(context, 'value'):
|
||||||
|
context = context.value
|
||||||
|
sig = (rule.get("roleId"), str(context), rule.get("item"))
|
||||||
|
if sig not in rulesBySignature:
|
||||||
|
rulesBySignature[sig] = []
|
||||||
|
rulesBySignature[sig].append(rule)
|
||||||
|
|
||||||
|
# Find duplicates and collect IDs to delete
|
||||||
|
duplicateGroups = []
|
||||||
|
idsToDelete = []
|
||||||
|
|
||||||
|
for sig, rules in rulesBySignature.items():
|
||||||
|
if len(rules) > 1:
|
||||||
|
# Sort by creation time (keep oldest)
|
||||||
|
rules.sort(key=lambda r: r.get("_createdAt", 0))
|
||||||
|
keepRule = rules[0]
|
||||||
|
deleteRules = rules[1:]
|
||||||
|
|
||||||
|
duplicateGroups.append({
|
||||||
|
"roleId": sig[0],
|
||||||
|
"context": sig[1],
|
||||||
|
"item": sig[2] or "(global)",
|
||||||
|
"totalCount": len(rules),
|
||||||
|
"keepId": keepRule.get("id"),
|
||||||
|
"deleteCount": len(deleteRules),
|
||||||
|
"deleteIds": [r.get("id") for r in deleteRules]
|
||||||
|
})
|
||||||
|
|
||||||
|
idsToDelete.extend([r.get("id") for r in deleteRules])
|
||||||
|
|
||||||
|
# Perform deletion if not dry run
|
||||||
|
deletedCount = 0
|
||||||
|
if not dryRun and idsToDelete:
|
||||||
|
for ruleId in idsToDelete:
|
||||||
|
try:
|
||||||
|
rootInterface.db.recordDelete(AccessRule, ruleId)
|
||||||
|
deletedCount += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to delete rule {ruleId}: {e}")
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"dryRun": dryRun,
|
||||||
|
"totalRules": len(allRules),
|
||||||
|
"uniqueSignatures": len(rulesBySignature),
|
||||||
|
"duplicateGroups": len(duplicateGroups),
|
||||||
|
"duplicateRulesToDelete": len(idsToDelete),
|
||||||
|
"deletedCount": deletedCount,
|
||||||
|
"details": duplicateGroups[:50] # Limit details to 50 groups
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"AccessRule cleanup: dryRun={dryRun}, total={len(allRules)}, "
|
||||||
|
f"duplicateGroups={len(duplicateGroups)}, toDelete={len(idsToDelete)}, "
|
||||||
|
f"deleted={deletedCount}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during AccessRule cleanup: {str(e)}")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Failed to cleanup duplicate rules: {str(e)}"
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue