removed roles page and updated it to be user role templates editin page
This commit is contained in:
parent
2c1ed16464
commit
dd25fa603d
3 changed files with 185 additions and 17 deletions
|
|
@ -693,8 +693,11 @@ def _deduplicateRoles(db: DatabaseConnector) -> None:
|
|||
logger.info(f"Deduplicated roles: removed {deletedCount} duplicates")
|
||||
|
||||
# Migration: Fix isSystemRole flags
|
||||
# Only the three bootstrap templates (admin, user, viewer) may be isSystemRole=True
|
||||
# with mandateId=None. Everything else must be False.
|
||||
_BOOTSTRAP_ROLE_LABELS = {"admin", "user", "viewer"}
|
||||
fixedMandateCount = 0
|
||||
fixedTemplateCount = 0
|
||||
fixedUserTemplateCount = 0
|
||||
for role in allRoles:
|
||||
# Mandate-level roles should NOT be isSystemRole=True
|
||||
if role.get("mandateId") is not None and role.get("isSystemRole") is True:
|
||||
|
|
@ -703,17 +706,23 @@ def _deduplicateRoles(db: DatabaseConnector) -> None:
|
|||
fixedMandateCount += 1
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fix mandate role {role.get('id')}: {e}")
|
||||
# Template roles (mandateId=None, no featureCode) MUST be isSystemRole=True
|
||||
if role.get("mandateId") is None and role.get("featureCode") is None and role.get("isSystemRole") is not True:
|
||||
# User-created global templates (mandateId=None, not a bootstrap label) must NOT
|
||||
# be isSystemRole=True. A previous migration incorrectly promoted them.
|
||||
if (
|
||||
role.get("mandateId") is None
|
||||
and role.get("featureCode") is None
|
||||
and role.get("isSystemRole") is True
|
||||
and role.get("roleLabel") not in _BOOTSTRAP_ROLE_LABELS
|
||||
):
|
||||
try:
|
||||
db.recordModify(Role, role.get("id"), {"isSystemRole": True})
|
||||
fixedTemplateCount += 1
|
||||
db.recordModify(Role, role.get("id"), {"isSystemRole": False})
|
||||
fixedUserTemplateCount += 1
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fix template role {role.get('id')}: {e}")
|
||||
logger.warning(f"Failed to demote user template role {role.get('id')}: {e}")
|
||||
if fixedMandateCount > 0:
|
||||
logger.info(f"Fixed {fixedMandateCount} mandate-level roles: isSystemRole → False")
|
||||
if fixedTemplateCount > 0:
|
||||
logger.info(f"Fixed {fixedTemplateCount} template roles: isSystemRole → True")
|
||||
if fixedUserTemplateCount > 0:
|
||||
logger.info(f"Fixed {fixedUserTemplateCount} user-created global templates: isSystemRole → False")
|
||||
|
||||
|
||||
def _ensureAllMandatesHaveSystemRoles(db: DatabaseConnector) -> None:
|
||||
|
|
@ -824,6 +833,93 @@ def copySystemRolesToMandate(db: DatabaseConnector, mandateId: str) -> int:
|
|||
return copiedCount
|
||||
|
||||
|
||||
def copyUserRoleTemplateToMandate(
|
||||
db: DatabaseConnector,
|
||||
templateRoleId: str,
|
||||
mandateId: str,
|
||||
roleLabel: Optional[str] = None,
|
||||
description: Optional[dict] = None,
|
||||
) -> dict:
|
||||
"""
|
||||
Copy a user role template (global/system, mandateId=None) into a mandate instance role,
|
||||
including all AccessRules.
|
||||
|
||||
Args:
|
||||
db: Database connector
|
||||
templateRoleId: Source template role id
|
||||
mandateId: Target mandate id
|
||||
roleLabel: Optional override for instance role label (defaults to template label)
|
||||
description: Optional override for multilingual description
|
||||
|
||||
Returns:
|
||||
Created mandate role record as dict
|
||||
|
||||
Raises:
|
||||
ValueError: If template invalid, duplicate label, or not found
|
||||
"""
|
||||
import uuid as _uuid
|
||||
|
||||
templateRecords = db.getRecordset(Role, recordFilter={"id": templateRoleId})
|
||||
if not templateRecords:
|
||||
raise ValueError(f"Template role not found: {templateRoleId}")
|
||||
|
||||
templateRole = templateRecords[0]
|
||||
if templateRole.get("mandateId") is not None:
|
||||
raise ValueError("Source role is not a user role template (has mandateId)")
|
||||
if templateRole.get("featureInstanceId") is not None or templateRole.get("featureCode"):
|
||||
raise ValueError("Source role is a feature role, not a user role template")
|
||||
|
||||
effectiveLabel = (roleLabel or templateRole.get("roleLabel") or "").strip().lower().replace(" ", "_")
|
||||
if not effectiveLabel:
|
||||
raise ValueError("roleLabel is required")
|
||||
|
||||
existingMandateRoles = db.getRecordset(
|
||||
Role,
|
||||
recordFilter={"mandateId": mandateId, "featureInstanceId": None},
|
||||
)
|
||||
existingLabels = {r.get("roleLabel") for r in existingMandateRoles}
|
||||
if effectiveLabel in existingLabels:
|
||||
raise ValueError(f"Mandate already has role '{effectiveLabel}'")
|
||||
|
||||
if description is not None:
|
||||
descValue = coerce_text_multilingual(description)
|
||||
else:
|
||||
descValue = coerce_text_multilingual(templateRole.get("description", {}))
|
||||
|
||||
newRoleId = str(_uuid.uuid4())
|
||||
newRole = Role(
|
||||
id=newRoleId,
|
||||
roleLabel=effectiveLabel,
|
||||
description=descValue,
|
||||
mandateId=mandateId,
|
||||
featureInstanceId=None,
|
||||
featureCode=None,
|
||||
isSystemRole=False,
|
||||
)
|
||||
created = db.recordCreate(Role, newRole.model_dump())
|
||||
|
||||
templateRules = db.getRecordset(AccessRule, recordFilter={"roleId": templateRoleId})
|
||||
for rule in templateRules:
|
||||
newRule = AccessRule(
|
||||
id=str(_uuid.uuid4()),
|
||||
roleId=newRoleId,
|
||||
context=rule.get("context"),
|
||||
item=rule.get("item"),
|
||||
view=rule.get("view", False),
|
||||
read=rule.get("read"),
|
||||
create=rule.get("create"),
|
||||
update=rule.get("update"),
|
||||
delete=rule.get("delete"),
|
||||
)
|
||||
db.recordCreate(AccessRule, newRule.model_dump())
|
||||
|
||||
logger.info(
|
||||
f"Copied user role template '{templateRole.get('roleLabel')}' → mandate {mandateId} "
|
||||
f"as '{effectiveLabel}' with {len(templateRules)} AccessRules"
|
||||
)
|
||||
return created
|
||||
|
||||
|
||||
def _getRoleId(db: DatabaseConnector, roleLabel: str) -> Optional[str]:
|
||||
"""
|
||||
Get role ID by label, using cache or database lookup.
|
||||
|
|
|
|||
|
|
@ -1033,6 +1033,77 @@ def create_role(
|
|||
)
|
||||
|
||||
|
||||
@router.post("/roles/from-template", response_model=Dict[str, Any])
|
||||
@limiter.limit("30/minute")
|
||||
def create_role_from_template(
|
||||
request: Request,
|
||||
body: Dict[str, Any] = Body(...),
|
||||
reqContext: RequestContext = Depends(getRequestContext),
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a mandate-instance role by copying a user role template and its AccessRules.
|
||||
|
||||
Body:
|
||||
- templateRoleId: str (required)
|
||||
- mandateId: str (required)
|
||||
- roleLabel: str (optional override)
|
||||
- description: dict (optional multilingual override)
|
||||
"""
|
||||
try:
|
||||
templateRoleId = body.get("templateRoleId")
|
||||
mandateId = body.get("mandateId")
|
||||
if not templateRoleId or not mandateId:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=routeApiMsg("templateRoleId and mandateId are required"),
|
||||
)
|
||||
|
||||
isSysAdmin = reqContext.isPlatformAdmin
|
||||
adminMandateIds = [] if isSysAdmin else _getAdminMandateIds(reqContext)
|
||||
if not isSysAdmin and not adminMandateIds:
|
||||
raise HTTPException(status_code=403, detail=routeApiMsg("Admin role required"))
|
||||
if not isSysAdmin and str(mandateId) not in adminMandateIds:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=routeApiMsg("Access denied: can only create roles in your own mandates"),
|
||||
)
|
||||
|
||||
from modules.interfaces.interfaceBootstrap import copyUserRoleTemplateToMandate
|
||||
|
||||
interface = getRootInterface()
|
||||
created = copyUserRoleTemplateToMandate(
|
||||
interface.db,
|
||||
str(templateRoleId),
|
||||
str(mandateId),
|
||||
roleLabel=body.get("roleLabel"),
|
||||
description=body.get("description"),
|
||||
)
|
||||
|
||||
desc = created.get("description")
|
||||
if hasattr(desc, "model_dump"):
|
||||
desc = desc.model_dump()
|
||||
|
||||
return {
|
||||
"id": created.get("id"),
|
||||
"roleLabel": created.get("roleLabel"),
|
||||
"description": resolveText(desc) if desc else desc,
|
||||
"mandateId": created.get("mandateId"),
|
||||
"featureInstanceId": created.get("featureInstanceId"),
|
||||
"featureCode": created.get("featureCode"),
|
||||
"isSystemRole": created.get("isSystemRole"),
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating role from template: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to create role from template: {str(e)}",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/roles/{roleId}", response_model=Dict[str, Any])
|
||||
@limiter.limit("60/minute")
|
||||
def get_role(
|
||||
|
|
|
|||
|
|
@ -256,15 +256,6 @@ NAVIGATION_SECTIONS = [
|
|||
"title": t("System"),
|
||||
"order": 30,
|
||||
"items": [
|
||||
{
|
||||
"id": "admin-roles",
|
||||
"objectKey": "ui.admin.roles",
|
||||
"label": t("Rollen"),
|
||||
"icon": "FaUserTag",
|
||||
"path": "/admin/mandate-roles",
|
||||
"order": 10,
|
||||
"adminOnly": True,
|
||||
},
|
||||
{
|
||||
"id": "admin-mandates",
|
||||
"objectKey": "ui.admin.mandates",
|
||||
|
|
@ -301,6 +292,16 @@ NAVIGATION_SECTIONS = [
|
|||
"order": 60,
|
||||
"adminOnly": True,
|
||||
},
|
||||
{
|
||||
"id": "admin-user-role-templates",
|
||||
"objectKey": "ui.admin.userRoleTemplates",
|
||||
"label": t("User Role Templates"),
|
||||
"icon": "FaUserTag",
|
||||
"path": "/admin/user-role-templates",
|
||||
"order": 65,
|
||||
"adminOnly": True,
|
||||
"sysAdminOnly": True,
|
||||
},
|
||||
{
|
||||
"id": "admin-feature-roles",
|
||||
"objectKey": "ui.admin.featureRoles",
|
||||
|
|
|
|||
Loading…
Reference in a new issue