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]]:
|
||||
"""
|
||||
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:
|
||||
return automations
|
||||
|
||||
from modules.interfaces.interfaceDbApp import getInterface as getAppInterface
|
||||
|
||||
# Collect all unique user IDs and mandate IDs
|
||||
userIds = set()
|
||||
mandateIds = set()
|
||||
|
|
@ -139,22 +138,33 @@ class AutomationObjects:
|
|||
if mandateId:
|
||||
mandateIds.add(mandateId)
|
||||
|
||||
# Use AppObjects interface to fetch users (respects access control)
|
||||
appInterface = getAppInterface(self.currentUser)
|
||||
usersMap = {}
|
||||
if userIds:
|
||||
for userId in userIds:
|
||||
user = appInterface.getUser(userId)
|
||||
if user:
|
||||
usersMap[userId] = user.username or user.email or userId
|
||||
# Use root DB connector for display-only lookups (no RBAC needed)
|
||||
try:
|
||||
from modules.datamodels.datamodelUam import UserInDB, Mandate
|
||||
from modules.security.rootAccess import getRootDbAppConnector
|
||||
dbAppConn = getRootDbAppConnector()
|
||||
|
||||
# Use AppObjects interface to fetch mandates (respects access control)
|
||||
mandatesMap = {}
|
||||
if mandateIds:
|
||||
for mandateId in mandateIds:
|
||||
mandate = appInterface.getMandate(mandateId)
|
||||
if mandate:
|
||||
mandatesMap[mandateId] = mandate.name or mandateId
|
||||
# Batch fetch user display names
|
||||
usersMap = {}
|
||||
if userIds:
|
||||
for userId in userIds:
|
||||
users = dbAppConn.getRecordset(UserInDB, {"id": userId})
|
||||
if users:
|
||||
user = users[0]
|
||||
fullName = f"{user.get('firstName', '')} {user.get('lastName', '')}".strip()
|
||||
usersMap[userId] = fullName or user.get("email") or user.get("username") or userId
|
||||
|
||||
# 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
|
||||
for automation in automations:
|
||||
|
|
|
|||
|
|
@ -266,9 +266,10 @@ def _ensureAccessRulesForRole(rootInterface, roleId: str, ruleTemplates: List[Di
|
|||
existingRules = rootInterface.getAccessRulesByRole(roleId)
|
||||
|
||||
# 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()
|
||||
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)
|
||||
|
||||
createdCount = 0
|
||||
|
|
|
|||
|
|
@ -230,9 +230,10 @@ def _ensureAccessRulesForRole(rootInterface, roleId: str, ruleTemplates: List[Di
|
|||
existingRules = rootInterface.getAccessRulesByRole(roleId)
|
||||
|
||||
# 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()
|
||||
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)
|
||||
|
||||
createdCount = 0
|
||||
|
|
|
|||
|
|
@ -245,7 +245,8 @@ def _ensureAccessRulesForRole(rootInterface, roleId: str, ruleTemplates: list) -
|
|||
from modules.datamodels.datamodelRbac import AccessRule, AccessRuleContext
|
||||
|
||||
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
|
||||
|
||||
for template in ruleTemplates or []:
|
||||
|
|
|
|||
|
|
@ -394,9 +394,10 @@ def _ensureAccessRulesForRole(rootInterface, roleId: str, ruleTemplates: List[Di
|
|||
existingRules = rootInterface.getAccessRulesByRole(roleId)
|
||||
|
||||
# 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()
|
||||
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)
|
||||
|
||||
createdCount = 0
|
||||
|
|
|
|||
|
|
@ -1192,3 +1192,101 @@ async def getCatalogStats(
|
|||
status_code=500,
|
||||
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