gateway/modules/interfaces/interfaceDbTrusteeObjects.py
2026-01-13 20:01:50 +01:00

836 lines
30 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Interface to Trustee database.
Manages trustee organisations, roles, access, contracts, documents, and positions.
"""
import logging
import math
from typing import Dict, Any, List, Optional
from modules.connectors.connectorDbPostgre import DatabaseConnector
from modules.shared.configuration import APP_CONFIG
from modules.interfaces.interfaceRbac import getRecordsetWithRBAC
from modules.security.rbac import RbacClass
from modules.datamodels.datamodelUam import User, AccessLevel
from modules.datamodels.datamodelRbac import AccessRuleContext
from modules.datamodels.datamodelTrustee import (
TrusteeOrganisation,
TrusteeRole,
TrusteeAccess,
TrusteeContract,
TrusteeDocument,
TrusteePosition,
TrusteePositionDocument,
)
from modules.datamodels.datamodelPagination import PaginationParams, PaginatedResult
logger = logging.getLogger(__name__)
# Singleton factory for TrusteeObjects instances per context
_trusteeInterfaces = {}
def getInterface(currentUser: User) -> "TrusteeObjects":
"""Get or create a TrusteeObjects instance for the given user context."""
global _trusteeInterfaces
if not currentUser or not currentUser.id:
raise ValueError("Valid user context required")
cacheKey = f"{currentUser.id}_{currentUser.mandateId}"
if cacheKey not in _trusteeInterfaces:
_trusteeInterfaces[cacheKey] = TrusteeObjects(currentUser)
else:
# Update user context if needed
_trusteeInterfaces[cacheKey].setUserContext(currentUser)
return _trusteeInterfaces[cacheKey]
class TrusteeObjects:
"""
Interface to Trustee database.
Manages trustee organisations, roles, access, contracts, documents, and positions.
"""
def __init__(self, currentUser: Optional[User] = None):
"""Initializes the Trustee Interface."""
self.currentUser = currentUser
self.userId = currentUser.id if currentUser else None
self.mandateId = currentUser.mandateId if currentUser else None
self.rbac = None
# Initialize database
self._initializeDatabase()
# Set user context if provided
if currentUser:
self.setUserContext(currentUser)
def setUserContext(self, currentUser: User):
"""Sets the user context for the interface."""
if not currentUser:
logger.info("Initializing interface without user context")
return
self.currentUser = currentUser
self.userId = currentUser.id
self.mandateId = currentUser.mandateId
if not self.userId or not self.mandateId:
raise ValueError("Invalid user context: id and mandateId are required")
self.userLanguage = currentUser.language
# Initialize RBAC interface
from modules.security.rootAccess import getRootDbAppConnector
dbApp = getRootDbAppConnector()
self.rbac = RbacClass(self.db, dbApp=dbApp)
# Update database context
self.db.updateContext(self.userId)
def __del__(self):
"""Cleanup method to close database connection."""
if hasattr(self, "db") and self.db is not None:
try:
self.db.close()
except Exception as e:
logger.error(f"Error closing database connection: {e}")
def _initializeDatabase(self):
"""Initializes the database connection directly."""
try:
dbHost = APP_CONFIG.get("DB_TRUSTEE_HOST", APP_CONFIG.get("DB_CHAT_HOST", "_no_config_default_data"))
dbDatabase = APP_CONFIG.get("DB_TRUSTEE_DATABASE", "trustee")
dbUser = APP_CONFIG.get("DB_TRUSTEE_USER", APP_CONFIG.get("DB_CHAT_USER"))
dbPassword = APP_CONFIG.get("DB_TRUSTEE_PASSWORD_SECRET", APP_CONFIG.get("DB_CHAT_PASSWORD_SECRET"))
dbPort = int(APP_CONFIG.get("DB_TRUSTEE_PORT", APP_CONFIG.get("DB_CHAT_PORT", 5432)))
self.db = DatabaseConnector(
dbHost=dbHost,
dbDatabase=dbDatabase,
dbUser=dbUser,
dbPassword=dbPassword,
dbPort=dbPort,
userId=self.userId,
)
self.db.initDbSystem()
logger.info(f"Trustee database initialized successfully for user {self.userId}")
except Exception as e:
logger.error(f"Failed to initialize Trustee database: {str(e)}")
raise
def checkRbacPermission(
self,
modelClass: type,
operation: str,
recordId: Optional[str] = None
) -> bool:
"""Check RBAC permission for a specific operation on a table."""
if not self.rbac or not self.currentUser:
return False
tableName = modelClass.__name__
permissions = self.rbac.getUserPermissions(
self.currentUser,
AccessRuleContext.DATA,
tableName
)
if not permissions.view:
return False
permLevel = getattr(permissions, operation, AccessLevel.NONE)
if permLevel == AccessLevel.NONE:
return False
return True
# ===== Organisation CRUD =====
def createOrganisation(self, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Create a new organisation."""
if not self.checkRbacPermission(TrusteeOrganisation, "create"):
logger.warning(f"User {self.userId} lacks permission to create organisation")
return None
# Set mandateId from current user
data["mandateId"] = self.mandateId
# Validate ID format (alphanumeric, hyphens, underscores, 3-50 chars)
orgId = data.get("id", "")
if not orgId or len(orgId) < 3 or len(orgId) > 50:
logger.error(f"Invalid organisation ID length: {len(orgId)}")
return None
import re
if not re.match(r'^[a-zA-Z0-9_-]+$', orgId):
logger.error(f"Invalid organisation ID format: {orgId}")
return None
success = self.db.saveRecord(TrusteeOrganisation, orgId, data)
if success:
return self.db.getRecord(TrusteeOrganisation, orgId)
return None
def getOrganisation(self, orgId: str) -> Optional[Dict[str, Any]]:
"""Get a single organisation by ID."""
return self.db.getRecord(TrusteeOrganisation, orgId)
def getAllOrganisations(self, params: Optional[PaginationParams] = None) -> PaginatedResult:
"""Get all organisations with RBAC filtering."""
records = getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteeOrganisation,
currentUser=self.currentUser,
recordFilter=None,
orderBy="id"
)
# Apply pagination
totalItems = len(records)
if params:
pageSize = params.pageSize or 20
page = params.page or 1
startIdx = (page - 1) * pageSize
endIdx = startIdx + pageSize
items = records[startIdx:endIdx]
totalPages = math.ceil(totalItems / pageSize) if pageSize > 0 else 1
else:
items = records
totalPages = 1
page = 1
pageSize = totalItems
return PaginatedResult(
items=items,
totalItems=totalItems,
totalPages=totalPages
)
def updateOrganisation(self, orgId: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Update an organisation."""
if not self.checkRbacPermission(TrusteeOrganisation, "update"):
logger.warning(f"User {self.userId} lacks permission to update organisation")
return None
# ID cannot be changed after creation
if "id" in data and data["id"] != orgId:
logger.error("Organisation ID cannot be changed")
return None
data["id"] = orgId
success = self.db.saveRecord(TrusteeOrganisation, orgId, data)
if success:
return self.db.getRecord(TrusteeOrganisation, orgId)
return None
def deleteOrganisation(self, orgId: str) -> bool:
"""Delete an organisation."""
if not self.checkRbacPermission(TrusteeOrganisation, "delete"):
logger.warning(f"User {self.userId} lacks permission to delete organisation")
return False
return self.db.deleteRecord(TrusteeOrganisation, orgId)
# ===== Role CRUD =====
def createRole(self, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Create a new role (sysadmin only)."""
if not self.checkRbacPermission(TrusteeRole, "create"):
logger.warning(f"User {self.userId} lacks permission to create role")
return None
data["mandateId"] = self.mandateId
roleId = data.get("id", "")
if not roleId:
logger.error("Role ID is required")
return None
success = self.db.saveRecord(TrusteeRole, roleId, data)
if success:
return self.db.getRecord(TrusteeRole, roleId)
return None
def getRole(self, roleId: str) -> Optional[Dict[str, Any]]:
"""Get a single role by ID."""
return self.db.getRecord(TrusteeRole, roleId)
def getAllRoles(self, params: Optional[PaginationParams] = None) -> PaginatedResult:
"""Get all roles with RBAC filtering."""
records = getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteeRole,
currentUser=self.currentUser,
recordFilter=None,
orderBy="id"
)
totalItems = len(records)
if params:
pageSize = params.pageSize or 20
page = params.page or 1
startIdx = (page - 1) * pageSize
endIdx = startIdx + pageSize
items = records[startIdx:endIdx]
totalPages = math.ceil(totalItems / pageSize) if pageSize > 0 else 1
else:
items = records
totalPages = 1
page = 1
pageSize = totalItems
return PaginatedResult(
items=items,
totalItems=totalItems,
totalPages=totalPages
)
def updateRole(self, roleId: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Update a role (sysadmin only)."""
if not self.checkRbacPermission(TrusteeRole, "update"):
logger.warning(f"User {self.userId} lacks permission to update role")
return None
data["id"] = roleId
success = self.db.saveRecord(TrusteeRole, roleId, data)
if success:
return self.db.getRecord(TrusteeRole, roleId)
return None
def deleteRole(self, roleId: str) -> bool:
"""Delete a role (sysadmin only, not if in use)."""
if not self.checkRbacPermission(TrusteeRole, "delete"):
logger.warning(f"User {self.userId} lacks permission to delete role")
return False
# Check if role is in use
accessRecords = self.db.getRecordset(TrusteeAccess, {"roleId": roleId})
if accessRecords:
logger.error(f"Cannot delete role {roleId}: still in use")
return False
return self.db.deleteRecord(TrusteeRole, roleId)
# ===== Access CRUD =====
def createAccess(self, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Create a new access record."""
if not self.checkRbacPermission(TrusteeAccess, "create"):
logger.warning(f"User {self.userId} lacks permission to create access")
return None
data["mandateId"] = self.mandateId
import uuid
accessId = data.get("id") or str(uuid.uuid4())
data["id"] = accessId
success = self.db.saveRecord(TrusteeAccess, accessId, data)
if success:
return self.db.getRecord(TrusteeAccess, accessId)
return None
def getAccess(self, accessId: str) -> Optional[Dict[str, Any]]:
"""Get a single access record by ID."""
return self.db.getRecord(TrusteeAccess, accessId)
def getAllAccess(self, params: Optional[PaginationParams] = None) -> PaginatedResult:
"""Get all access records with RBAC filtering."""
records = getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteeAccess,
currentUser=self.currentUser,
recordFilter=None,
orderBy="id"
)
totalItems = len(records)
if params:
pageSize = params.pageSize or 20
page = params.page or 1
startIdx = (page - 1) * pageSize
endIdx = startIdx + pageSize
items = records[startIdx:endIdx]
totalPages = math.ceil(totalItems / pageSize) if pageSize > 0 else 1
else:
items = records
totalPages = 1
page = 1
pageSize = totalItems
return PaginatedResult(
items=items,
totalItems=totalItems,
totalPages=totalPages
)
def getAccessByOrganisation(self, organisationId: str) -> List[Dict[str, Any]]:
"""Get all access records for a specific organisation."""
return getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteeAccess,
currentUser=self.currentUser,
recordFilter={"organisationId": organisationId},
orderBy="id"
)
def getAccessByUser(self, userId: str) -> List[Dict[str, Any]]:
"""Get all access records for a specific user."""
return getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteeAccess,
currentUser=self.currentUser,
recordFilter={"userId": userId},
orderBy="id"
)
def updateAccess(self, accessId: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Update an access record."""
if not self.checkRbacPermission(TrusteeAccess, "update"):
logger.warning(f"User {self.userId} lacks permission to update access")
return None
data["id"] = accessId
success = self.db.saveRecord(TrusteeAccess, accessId, data)
if success:
return self.db.getRecord(TrusteeAccess, accessId)
return None
def deleteAccess(self, accessId: str) -> bool:
"""Delete an access record."""
if not self.checkRbacPermission(TrusteeAccess, "delete"):
logger.warning(f"User {self.userId} lacks permission to delete access")
return False
return self.db.deleteRecord(TrusteeAccess, accessId)
# ===== Contract CRUD =====
def createContract(self, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Create a new contract."""
if not self.checkRbacPermission(TrusteeContract, "create"):
logger.warning(f"User {self.userId} lacks permission to create contract")
return None
data["mandateId"] = self.mandateId
import uuid
contractId = data.get("id") or str(uuid.uuid4())
data["id"] = contractId
success = self.db.saveRecord(TrusteeContract, contractId, data)
if success:
return self.db.getRecord(TrusteeContract, contractId)
return None
def getContract(self, contractId: str) -> Optional[Dict[str, Any]]:
"""Get a single contract by ID."""
return self.db.getRecord(TrusteeContract, contractId)
def getAllContracts(self, params: Optional[PaginationParams] = None) -> PaginatedResult:
"""Get all contracts with RBAC filtering."""
records = getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteeContract,
currentUser=self.currentUser,
recordFilter=None,
orderBy="id"
)
totalItems = len(records)
if params:
pageSize = params.pageSize or 20
page = params.page or 1
startIdx = (page - 1) * pageSize
endIdx = startIdx + pageSize
items = records[startIdx:endIdx]
totalPages = math.ceil(totalItems / pageSize) if pageSize > 0 else 1
else:
items = records
totalPages = 1
page = 1
pageSize = totalItems
return PaginatedResult(
items=items,
totalItems=totalItems,
totalPages=totalPages
)
def getContractsByOrganisation(self, organisationId: str) -> List[Dict[str, Any]]:
"""Get all contracts for a specific organisation."""
return getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteeContract,
currentUser=self.currentUser,
recordFilter={"organisationId": organisationId},
orderBy="label"
)
def updateContract(self, contractId: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Update a contract (organisationId is immutable)."""
if not self.checkRbacPermission(TrusteeContract, "update"):
logger.warning(f"User {self.userId} lacks permission to update contract")
return None
# Check if organisationId is being changed
existing = self.db.getRecord(TrusteeContract, contractId)
if existing and "organisationId" in data:
if data["organisationId"] != existing.get("organisationId"):
logger.error("Contract organisationId cannot be changed after creation")
return None
data["id"] = contractId
success = self.db.saveRecord(TrusteeContract, contractId, data)
if success:
return self.db.getRecord(TrusteeContract, contractId)
return None
def deleteContract(self, contractId: str) -> bool:
"""Delete a contract."""
if not self.checkRbacPermission(TrusteeContract, "delete"):
logger.warning(f"User {self.userId} lacks permission to delete contract")
return False
return self.db.deleteRecord(TrusteeContract, contractId)
# ===== Document CRUD =====
def createDocument(self, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Create a new document."""
if not self.checkRbacPermission(TrusteeDocument, "create"):
logger.warning(f"User {self.userId} lacks permission to create document")
return None
data["mandateId"] = self.mandateId
import uuid
documentId = data.get("id") or str(uuid.uuid4())
data["id"] = documentId
success = self.db.saveRecord(TrusteeDocument, documentId, data)
if success:
return self.db.getRecord(TrusteeDocument, documentId)
return None
def getDocument(self, documentId: str) -> Optional[Dict[str, Any]]:
"""Get a single document by ID (metadata only)."""
record = self.db.getRecord(TrusteeDocument, documentId)
if record:
# Remove binary data from response
record.pop("documentData", None)
return record
def getDocumentData(self, documentId: str) -> Optional[bytes]:
"""Get document binary data."""
record = self.db.getRecord(TrusteeDocument, documentId)
if record:
return record.get("documentData")
return None
def getAllDocuments(self, params: Optional[PaginationParams] = None) -> PaginatedResult:
"""Get all documents with RBAC filtering (metadata only)."""
records = getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteeDocument,
currentUser=self.currentUser,
recordFilter=None,
orderBy="documentName"
)
# Remove binary data from responses
for record in records:
record.pop("documentData", None)
totalItems = len(records)
if params:
pageSize = params.pageSize or 20
page = params.page or 1
startIdx = (page - 1) * pageSize
endIdx = startIdx + pageSize
items = records[startIdx:endIdx]
totalPages = math.ceil(totalItems / pageSize) if pageSize > 0 else 1
else:
items = records
totalPages = 1
page = 1
pageSize = totalItems
return PaginatedResult(
items=items,
totalItems=totalItems,
totalPages=totalPages
)
def getDocumentsByContract(self, contractId: str) -> List[Dict[str, Any]]:
"""Get all documents for a specific contract."""
records = getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteeDocument,
currentUser=self.currentUser,
recordFilter={"contractId": contractId},
orderBy="documentName"
)
for record in records:
record.pop("documentData", None)
return records
def updateDocument(self, documentId: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Update a document."""
if not self.checkRbacPermission(TrusteeDocument, "update"):
logger.warning(f"User {self.userId} lacks permission to update document")
return None
data["id"] = documentId
success = self.db.saveRecord(TrusteeDocument, documentId, data)
if success:
result = self.db.getRecord(TrusteeDocument, documentId)
if result:
result.pop("documentData", None)
return result
return None
def deleteDocument(self, documentId: str) -> bool:
"""Delete a document."""
if not self.checkRbacPermission(TrusteeDocument, "delete"):
logger.warning(f"User {self.userId} lacks permission to delete document")
return False
return self.db.deleteRecord(TrusteeDocument, documentId)
# ===== Position CRUD =====
def createPosition(self, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Create a new position."""
if not self.checkRbacPermission(TrusteePosition, "create"):
logger.warning(f"User {self.userId} lacks permission to create position")
return None
data["mandateId"] = self.mandateId
# Calculate VAT amount if not provided
if "vatAmount" not in data or data.get("vatAmount") == 0:
bookingAmount = data.get("bookingAmount", 0)
vatPercentage = data.get("vatPercentage", 0)
data["vatAmount"] = bookingAmount * vatPercentage / 100
import uuid
positionId = data.get("id") or str(uuid.uuid4())
data["id"] = positionId
success = self.db.saveRecord(TrusteePosition, positionId, data)
if success:
return self.db.getRecord(TrusteePosition, positionId)
return None
def getPosition(self, positionId: str) -> Optional[Dict[str, Any]]:
"""Get a single position by ID."""
return self.db.getRecord(TrusteePosition, positionId)
def getAllPositions(self, params: Optional[PaginationParams] = None) -> PaginatedResult:
"""Get all positions with RBAC filtering."""
records = getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteePosition,
currentUser=self.currentUser,
recordFilter=None,
orderBy="valuta"
)
totalItems = len(records)
if params:
pageSize = params.pageSize or 20
page = params.page or 1
startIdx = (page - 1) * pageSize
endIdx = startIdx + pageSize
items = records[startIdx:endIdx]
totalPages = math.ceil(totalItems / pageSize) if pageSize > 0 else 1
else:
items = records
totalPages = 1
page = 1
pageSize = totalItems
return PaginatedResult(
items=items,
totalItems=totalItems,
totalPages=totalPages
)
def getPositionsByContract(self, contractId: str) -> List[Dict[str, Any]]:
"""Get all positions for a specific contract."""
return getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteePosition,
currentUser=self.currentUser,
recordFilter={"contractId": contractId},
orderBy="valuta"
)
def getPositionsByOrganisation(self, organisationId: str) -> List[Dict[str, Any]]:
"""Get all positions for a specific organisation."""
return getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteePosition,
currentUser=self.currentUser,
recordFilter={"organisationId": organisationId},
orderBy="valuta"
)
def updatePosition(self, positionId: str, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Update a position."""
if not self.checkRbacPermission(TrusteePosition, "update"):
logger.warning(f"User {self.userId} lacks permission to update position")
return None
data["id"] = positionId
success = self.db.saveRecord(TrusteePosition, positionId, data)
if success:
return self.db.getRecord(TrusteePosition, positionId)
return None
def deletePosition(self, positionId: str) -> bool:
"""Delete a position."""
if not self.checkRbacPermission(TrusteePosition, "delete"):
logger.warning(f"User {self.userId} lacks permission to delete position")
return False
return self.db.deleteRecord(TrusteePosition, positionId)
# ===== Position-Document Link CRUD =====
def createPositionDocument(self, data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Create a new position-document link."""
if not self.checkRbacPermission(TrusteePositionDocument, "create"):
logger.warning(f"User {self.userId} lacks permission to create position-document link")
return None
data["mandateId"] = self.mandateId
import uuid
linkId = data.get("id") or str(uuid.uuid4())
data["id"] = linkId
success = self.db.saveRecord(TrusteePositionDocument, linkId, data)
if success:
return self.db.getRecord(TrusteePositionDocument, linkId)
return None
def getPositionDocument(self, linkId: str) -> Optional[Dict[str, Any]]:
"""Get a single position-document link by ID."""
return self.db.getRecord(TrusteePositionDocument, linkId)
def getAllPositionDocuments(self, params: Optional[PaginationParams] = None) -> PaginatedResult:
"""Get all position-document links with RBAC filtering."""
records = getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteePositionDocument,
currentUser=self.currentUser,
recordFilter=None,
orderBy="id"
)
totalItems = len(records)
if params:
pageSize = params.pageSize or 20
page = params.page or 1
startIdx = (page - 1) * pageSize
endIdx = startIdx + pageSize
items = records[startIdx:endIdx]
totalPages = math.ceil(totalItems / pageSize) if pageSize > 0 else 1
else:
items = records
totalPages = 1
page = 1
pageSize = totalItems
return PaginatedResult(
items=items,
totalItems=totalItems,
totalPages=totalPages
)
def getDocumentsForPosition(self, positionId: str) -> List[Dict[str, Any]]:
"""Get all documents linked to a position."""
links = getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteePositionDocument,
currentUser=self.currentUser,
recordFilter={"positionId": positionId},
orderBy="id"
)
return links
def getPositionsForDocument(self, documentId: str) -> List[Dict[str, Any]]:
"""Get all positions linked to a document."""
links = getRecordsetWithRBAC(
connector=self.db,
modelClass=TrusteePositionDocument,
currentUser=self.currentUser,
recordFilter={"documentId": documentId},
orderBy="id"
)
return links
def deletePositionDocument(self, linkId: str) -> bool:
"""Delete a position-document link."""
if not self.checkRbacPermission(TrusteePositionDocument, "delete"):
logger.warning(f"User {self.userId} lacks permission to delete position-document link")
return False
return self.db.deleteRecord(TrusteePositionDocument, linkId)
# ===== Trustee-specific Access Check =====
def getUserAccessForOrganisation(self, userId: str, organisationId: str) -> List[Dict[str, Any]]:
"""Get all access records for a user in a specific organisation."""
return self.db.getRecordset(
TrusteeAccess,
{"userId": userId, "organisationId": organisationId}
)
def checkUserTrusteePermission(
self,
userId: str,
organisationId: str,
requiredRole: str,
contractId: Optional[str] = None
) -> bool:
"""
Check if a user has a specific role for an organisation (and optionally contract).
Args:
userId: User ID to check
organisationId: Organisation ID
requiredRole: Required role (userreport, admin, operate)
contractId: Optional contract ID for contract-specific access
Returns:
True if user has the required role
"""
accessRecords = self.getUserAccessForOrganisation(userId, organisationId)
for access in accessRecords:
if access.get("roleId") == requiredRole:
accessContractId = access.get("contractId")
# If access has no contractId, it grants access to all contracts
if accessContractId is None:
return True
# If checking for specific contract, match it
if contractId and accessContractId == contractId:
return True
# If no specific contract requested and access is contract-specific, deny
if contractId is None:
continue
return False