def createResource(self, resource: Resource, parentResource: Resource = None, originator: str = None) -> Result: Logging.logDebug( f'Adding resource ri: {resource.ri}, type: {resource.ty:d}') if parentResource is not None: Logging.logDebug(f'Parent ri: {parentResource.ri}') if not parentResource.canHaveChild(resource): if resource.ty == T.SUB: err = 'Parent resource is not subscribable' Logging.logWarn(err) return Result(rsc=RC.targetNotSubscribable, dbg=err) else: err = f'Invalid child resource type: {T(resource.ty).value}' Logging.logWarn(err) return Result(rsc=RC.invalidChildResourceType, dbg=err) # if not already set: determine and add the srn if resource.__srn__ is None: resource[resource._srn] = Utils.structuredPath(resource) # add the resource to storage if (res := resource.dbCreate(overwrite=False)).rsc != RC.created: return res
def _handleRequest(self, path:str, operation:Operation) -> Response: """ Get and check all the necessary information from the request and build the internal strutures. Then, depending on the operation, call the associated request handler. """ Logging.logDebug(f'==> {operation.name}: /{path}') # path = request.path w/o the root Logging.logDebug(f'Headers: \n{str(request.headers)}') httpRequestResult = Utils.dissectHttpRequest(request, operation, Utils.retrieveIDFromPath(path, CSE.cseRn, CSE.cseCsi)) if self.isStopped: responseResult = Result(rsc=RC.internalServerError, dbg='http server not running ', status=False) else: try: if httpRequestResult.status: if operation in [ Operation.CREATE, Operation.UPDATE ]: if httpRequestResult.request.ct == ContentSerializationType.CBOR: Logging.logDebug(f'Body: \n{Utils.toHex(httpRequestResult.request.data)}\n=>\n{httpRequestResult.request.dict}') else: Logging.logDebug(f'Body: \n{str(httpRequestResult.request.data)}') responseResult = self._requestHandlers[operation](httpRequestResult.request) else: responseResult = httpRequestResult except Exception as e: responseResult = self._prepareException(e) responseResult.request = httpRequestResult.request return self._prepareResponse(responseResult)
def createResource(self, resource: Resource, overwrite: bool = True) -> Result: if resource is None: Logging.logErr('resource is None') raise RuntimeError('resource is None') ri = resource.ri # Logging.logDebug(f'Adding resource (ty: {resource.ty:d}, ri: {resource.ri}, rn: {resource.rn})' srn = resource.__srn__ if overwrite: Logging.logDebug('Resource enforced overwrite') self.db.upsertResource(resource) else: if not self.hasResource( ri, srn): # Only when not resource does not exist yet self.db.insertResource(resource) else: Logging.logWarn( f'Resource already exists (Skipping): {resource}') return Result(status=False, rsc=RC.alreadyExists, dbg='resource already exists') # Add path to identifiers db self.db.insertIdentifier(resource, ri, srn) return Result(status=True, rsc=RC.created)
def _deleteCSRonRegistrarCSE(self) -> Result: Logging.logDebug(f'Deleting registrar CSR: {self.registrarCSI} url: {self.registrarCSRURL}') res = CSE.request.sendDeleteRequest(self.registrarCSRURL, CSE.cseCsi, ct=self.registrarSerialization) # own CSE.csi is the originator if res.rsc not in [ RC.deleted, RC.OK ]: return Result(rsc=res.rsc, dbg='cannot delete registrar CSR') Logging.log(f'Registrar CSR deleted: {self.registrarCSI}') return Result(rsc=RC.deleted)
def retrieveRequest(self, request: CSERequest) -> Result: Logging.logDebug( f'RETRIEVE ID: {request.id if request.id is not None else request.srn}, originator: {request.headers.originator}' ) # handle transit requests if self.isTransitID(request.id): return self.handleTransitRetrieveRequest( request) if self.enableTransit else Result( rsc=RC.operationNotAllowed, dbg='operation not allowed') if request.args.rt == ResponseType.blockingRequest: return CSE.dispatcher.processRetrieveRequest( request, request.headers.originator) elif request.args.rt in [ ResponseType.nonBlockingRequestSynch, ResponseType.nonBlockingRequestAsynch ]: return self._handleNonBlockingRequest(request) elif request.args.rt == ResponseType.flexBlocking: if self.flexBlockingBlocking: # flexBlocking as blocking return CSE.dispatcher.processRetrieveRequest( request, request.headers.originator) else: # flexBlocking as non-blocking return self._handleNonBlockingRequest(request) return Result( rsc=RC.badRequest, dbg='Unknown or unsupported ResponseType: {request.args.rt}')
def _retrieveLocalCSRs(self, csi:str=None, onlyOwn:bool=True) -> Result: localCsrs = CSE.dispatcher.directChildResources(pi=CSE.cseRi, ty=T.CSR) if csi is None: csi = self.registrarCSI # Logging.logDebug(f'Retrieving local CSR: {csi}') if onlyOwn: for localCsr in localCsrs: if (c := localCsr.csi) is not None and c == csi: return Result(lst=[ localCsr ]) return Result(rsc=RC.badRequest, dbg='local CSR not found')
def addSubscription(self, subscription: Resource, originator: str) -> Result: if not self.enableNotifications: return Result(status=False, rsc=RC.subscriptionVerificationInitiationFailed, dbg='notifications are disabled') Logging.logDebug('Adding subscription') if (res := self._checkNusInSubscription(subscription, originator=originator) ).lst is None: # verification requests happen here return Result(status=False, rsc=res.rsc, dbg=res.dbg)
def checkResourceDeletion(self, resource: Resource) -> Result: if resource.ty == T.AE: if not self.handleAEDeRegistration(resource): return Result(status=False, dbg='cannot deregister AE') if resource.ty == T.REQ: if not self.handleREQDeRegistration(resource): return Result(status=False, dbg='cannot deregister REQ') if resource.ty == T.CSR: if not self.handleCSRDeRegistration(resource): return Result(status=False, dbg='cannot deregister CSR') return Result(status=True)
def resourceFromDict(resDict: Dict[str, Any] = {}, pi: str = None, ty: T = None, create: bool = False, isImported: bool = False) -> Result: """ Create a resource from a dictionary structure. This will *not* call the activate method, therefore some attributes may be set separately. """ resDict, tpe = Utils.pureResource( resDict) # remove optional "m2m:xxx" level typ = resDict['ty'] if 'ty' in resDict else ty # Check whether given type during CREATE matches the resource's ty attribute if typ != None and ty != None and typ != ty: Logging.logWarn( dbg := f'parameter type ({ty}) and resource type ({typ}) mismatch') return Result(dbg=dbg, rsc=RC.badRequest) # Check whether given type during CREATE matches the resource type specifier if ty != None and tpe != None and ty not in [ T.FCNT, T.FCNTAnnc, T.FCI, T.FCIAnnc, T.MGMTOBJ, T.MGMTOBJAnnc ] and ty.tpe() != tpe: Logging.logWarn( dbg := f'parameter type ({ty}) and resource type specifier ({tpe}) mismatch' ) return Result(dbg=dbg, rsc=RC.badRequest) # store the import status in the original resDict if isImported: resDict[Resource. _imported] = True # Indicate that this is an imported resource # Determine a factory and call it factory: FactoryT = None if typ == T.MGMTOBJ: # for <mgmtObj> mgd = resDict[ 'mgd'] if 'mgd' in resDict else None # Identify mdg in <mgmtObj> factory = resourceFactoryMap.get(mgd) elif typ == T.MGMTOBJAnnc: # for <mgmtObjA> mgd = resDict[ 'mgd'] if 'mgd' in resDict else None # Identify mdg in <mgmtObj> factory = resourceFactoryMap.get( T.announcedMgd(mgd)) # Get the announced version else: factory = resourceFactoryMap.get(typ) if factory is not None: return Result(resource=factory(resDict, tpe, pi, create)) return Result(resource=Unknown(resDict, tpe, pi=pi, create=create)) # Capture-All resource
def _updateCSRonRegistrarCSE(self, localCSE:Resource=None) -> Result: Logging.logDebug(f'Updating registrar CSR in CSE: {self.registrarCSI}') if localCSE is None: localCSE = Utils.getCSE().resource csr = CSR.CSR() self._copyCSE2CSR(csr, localCSE, isUpdate=True) del csr['acpi'] # remove ACPI (don't provide ACPI in updates...a bit) res = CSE.request.sendUpdateRequest(self.registrarCSRURL, CSE.cseCsi, data=csr.asDict(), ct=self.registrarSerialization) # own CSE.csi is the originator if res.rsc not in [ RC.updated, RC.OK ]: if res.rsc != RC.alreadyExists: Logging.logDebug(f'Error updating registrar CSR in CSE: {res.rsc:d}') return Result(rsc=res.rsc, dbg='cannot update remote CSR') Logging.logDebug(f'Registrar CSR updated in CSE: {self.registrarCSI}') return Result(resource=CSR.CSR(res.dict, pi=''), rsc=RC.updated)
class ACP(AnnounceableResource): def __init__(self, dct:JSON=None, pi:str=None, rn:str=None, create:bool=False, createdInternally:str=None) -> None: super().__init__(T.ACP, dct, pi, create=create, inheritACP=True, rn=rn, attributePolicies=attributePolicies) self.resourceAttributePolicies = acpPolicies # only the resource type's own policies if self.dict is not None: self.setAttribute('pv/acr', [], overwrite=False) self.setAttribute('pvs/acr', [], overwrite=False) if createdInternally is not None: self.setCreatedInternally(createdInternally) # Enable check for allowed sub-resources def canHaveChild(self, resource:Resource) -> bool: return super()._canHaveChild(resource, [ T.SUB # TODO Transaction to be added ]) def validate(self, originator:str=None, create:bool=False, dct:JSON=None) -> Result: if not (res := super().validate(originator, create, dct)).status: return res if dct is not None and (pvs := Utils.findXPath(dct, f'{T.ACPAnnc.tpe()}/pvs')) is not None: if len(pvs) == 0: return Result(status=False, rsc=RC.badRequest, dbg='pvs must not be empty')
class DVC(MgmtObj): def __init__(self, dct:JSON=None, pi:str=None, create:bool=False) -> None: self.resourceAttributePolicies = dvcPolicies # only the resource type's own policies super().__init__(dct, pi, mgd=T.DVC, create=create, attributePolicies=attributePolicies) if self.dict is not None: self.setAttribute('can', 'unknown', overwrite=False) self.setAttribute('att', False, overwrite=False) self.setAttribute('cas', { "acn" : "unknown", "sus" : 0 }, overwrite=False) self.setAttribute('cus', False, overwrite=False) self.setAttribute('ena', True, overwrite=True) # always True self.setAttribute('dis', True, overwrite=True) # always True # # Handling the special behaviour for ena and dis attributes in # validate() and update() # def validate(self, originator:str=None, create:bool=False, dct:JSON=None) -> Result: if not (res := super().validate(originator, create, dct)).status: return res self.setAttribute('ena', True, overwrite=True) # always set (back) to True self.setAttribute('dis', True, overwrite=True) # always set (back) to True return Result(status=True)
class PCH(Resource): def __init__(self, dct: JSON = None, pi: str = None, create: bool = False) -> None: super().__init__(T.PCH, dct, pi, create=create, attributePolicies=attributePolicies) self.resourceAttributePolicies = pchPolicies # only the resource type's own policies # Enable check for allowed sub-resources def canHaveChild(self, resource: Resource) -> bool: return super()._canHaveChild(resource, [T.PCH_PCU]) # TODO test Retrieve by AE only! Add new willBeRetrieved() function # TODO continue with 10.2.5.14 Retrieve <pollingChannel> def activate(self, parentResource: Resource, originator: str) -> Result: if not (res := super().activate(parentResource, originator)).status: return res # NOTE Check for uniqueness is done in <AE>.childWillBeAdded() # TODO the same for CSR # register pollingChannelURI virtual resource Logging.logDebug(f'Registering <PCU> for: {self.ri}') pcu = Factory.resourceFromDict( pi=self.ri, ty=T.PCH_PCU).resource # rn is assigned by resource itself if (res := CSE.dispatcher.createResource(pcu)).resource is None: return Result(status=False, rsc=res.rsc, dbg=res.dbg)
def update(self, dct:JSON=None, originator:str=None) -> Result: dictOrg = deepcopy(self.dict) # Save for later for notification updatedAttributes = None if dct is not None: if self.tpe not in dct and self.ty not in [T.FCNTAnnc, T.FCIAnnc]: # Don't check announced versions of announced FCNT Logging.logWarn("Update type doesn't match target") return Result(status=False, rsc=RC.contentsUnacceptable, dbg='resource types mismatch') # validate the attributes if not (res := CSE.validator.validateAttributes(dct, self.tpe, self.attributePolicies, create=False, createdInternally=self.isCreatedInternally(), isAnnounced=self.isAnnounced())).status: return res if self.ty not in [T.FCNTAnnc, T.FCIAnnc]: updatedAttributes = dct[self.tpe] # get structure under the resource type specifier else: updatedAttributes = Utils.findXPath(dct, '{0}') # Check that acpi, if present, is the only attribute if 'acpi' in updatedAttributes: # No further checks here. This has been done before in the Dispatcher.processUpdateRequest() # Check whether referenced <ACP> exists. If yes, change ID also to CSE relative unstructured if not (res := self._checkAndFixACPIreferences(updatedAttributes['acpi'])).status: return res self.setAttribute('acpi', res.lst, overwrite=True) # copy new value or add new attributes
class CNT_OL(Resource): def __init__(self, dct: JSON = None, pi: str = None, create: bool = False) -> None: super().__init__(T.CNT_OL, dct, pi, create=create, inheritACP=True, readOnly=True, rn='ol', isVirtual=True) # Enable check for allowed sub-resources def canHaveChild(self, resource: Resource) -> bool: return super()._canHaveChild(resource, []) def handleRetrieveRequest(self, request: CSERequest = None, id: str = None, originator: str = None) -> Result: """ Handle a RETRIEVE request. Return resource """ Logging.logDebug('Retrieving oldest CIN from CNT') if (r := self._getOldest()) is None: return Result(rsc=RC.notFound, dbg='no instance for <oldest>') return Result(resource=r)
def validatePvs(self, dct: JSON) -> Result: """ Validating special case for lists that are not allowed to be empty (pvs in ACP). """ if (l := len(dct['pvs'])) == 0: err = 'Attribute pvs must not be an empty list' Logging.logWarn(err) return Result(status=False, dbg=err)
class CSEBase(Resource): def __init__(self, dct: JSON = None, create: bool = False) -> None: super().__init__(T.CSEBase, dct, '', create=create, attributePolicies=attributePolicies) if self.dict is not None: self.setAttribute('ri', 'cseid', overwrite=False) self.setAttribute('rn', 'cse', overwrite=False) self.setAttribute('csi', 'cse', overwrite=False) self.setAttribute('rr', False, overwrite=False) self.setAttribute('srt', C.supportedResourceTypes, overwrite=False) self.setAttribute('csz', C.supportedContentSerializations, overwrite=False) self.setAttribute('srv', CSE.supportedReleaseVersions, overwrite=False) # This must be a list self.setAttribute('poa', [CSE.httpServer.serverAddress], overwrite=False ) # TODO add more address schemes when available self.setAttribute('cst', CSE.cseType, overwrite=False) # Enable check for allowed sub-resources def canHaveChild(self, resource: Resource) -> bool: return super()._canHaveChild( resource, [T.ACP, T.AE, T.CSR, T.CNT, T.FCNT, T.GRP, T.NOD, T.REQ, T.SUB]) def validate(self, originator: str = None, create: bool = False, dct: JSON = None) -> Result: if not (res := super().validate(originator, create, dct)).status: return res self.normalizeURIAttribute('poa') # Update the hcl attribute in the hosting node (similar to AE) nl = self['nl'] _nl_ = self.__node__ if nl is not None or _nl_ is not None: if nl != _nl_: if _nl_ is not None: nresource = CSE.dispatcher.retrieveResource(_nl_).resource if nresource is not None: nresource['hcl'] = None # remove old link CSE.dispatcher.updateResource(nresource) self[Resource._node] = nl nresource = CSE.dispatcher.retrieveResource(nl) if nresource is not None: nresource['hcl'] = self['ri'] CSE.dispatcher.updateResource(nresource) self[Resource._node] = nl return Result(status=True)
class RBO(MgmtObj): def __init__(self, dct: JSON = None, pi: str = None, create: bool = False) -> None: self.resourceAttributePolicies = rboPolicies # only the resource type's own policies super().__init__(dct, pi, mgd=T.RBO, create=create, attributePolicies=attributePolicies) if self.dict is not None: self.setAttribute('rbo', False, overwrite=True) # always False self.setAttribute('far', False, overwrite=True) # always False # # Handling the special behaviour for rbo and far attributes in # validate() and update() # def validate(self, originator: str = None, create: bool = False, dct: JSON = None) -> Result: if not (res := super().validate(originator, create, dct)).status: return res self.setAttribute('rbo', False, overwrite=True) # always set (back) to True self.setAttribute('far', False, overwrite=True) # always set (back) to True return Result(status=True)
def updateSubscription(self, subscription: Resource, newDict: JSON, previousNus: list[str], originator: str) -> Result: Logging.logDebug('Updating subscription') #previousSub = CSE.storage.getSubscription(subscription.ri) if (res := self._checkNusInSubscription( subscription, newDict, previousNus, originator=originator) ).lst is None: # verification/delete requests happen here return Result(status=False, rsc=res.rsc, dbg=res.dbg)
def handleRetrieveRequest(self, request: CSERequest = None, id: str = None, originator: str = None) -> Result: """ Handle a RETRIEVE request. Return resource """ Logging.logDebug('Retrieving oldest CIN from CNT') if (r := self._getOldest()) is None: return Result(rsc=RC.notFound, dbg='no instance for <oldest>')
def _deleteLocalCSR(self, localCSR: Resource) -> Result: Logging.logDebug(f'Deleting local CSR: {localCSR.ri}') if not CSE.registration.handleCSRDeRegistration(localCSR): return Result(rsc=RC.badRequest, dbg='cannot deregister CSR') # Delete local CSR return CSE.dispatcher.deleteResource(localCSR)
def updateResource(self, resource: Resource) -> Result: if resource is None: Logging.logErr('resource is None') raise RuntimeError('resource is None') ri = resource.ri # Logging.logDebug(f'Updating resource (ty: {resource.ty:d}, ri: {ri}, rn: {resource.rn})') return Result(resource=self.db.updateResource(resource), rsc=RC.updated)
def deleteResource(self, resource: Resource) -> Result: if resource is None: Logging.logErr('resource is None') raise RuntimeError('resource is None') # Logging.logDebug(f'Removing resource (ty: {resource.ty:d}, ri: {ri}, rn: {resource.rn})' self.db.deleteResource(resource) self.db.deleteIdentifier(resource) return Result(status=True, rsc=RC.deleted)
def checkResourceCreation(self, resource: Resource, originator: str, parentResource: Resource = None) -> Result: if resource.ty == T.AE: if (originator := self.handleAERegistration( resource, originator, parentResource).originator ) is None: # assigns new originator return Result(rsc=RC.badRequest, dbg='cannot register AE')
def handleCreator(self, resource: Resource, originator: str) -> Result: """ Check for set creator attribute as well as assign it to allowed resources. """ if resource.hasAttribute('cr'): if resource.ty not in C.creatorAllowed: return Result( rsc=RC.badRequest, dbg= f'"creator" attribute is not allowed for resource type: {resource.ty}' ) if resource.cr is not None: # Check whether cr is set to a value. This is wrong Logging.logWarn('Setting "creator" attribute is not allowed.') return Result(rsc=RC.badRequest, dbg='setting "creator" attribute is not allowed') else: resource['cr'] = originator # fall-through return Result() # implicit OK
def update(self, dct:JSON=None, originator:str=None) -> Result: # Check for ena & dis updates if dct is not None and self.tpe in dct: ena = Utils.findXPath(dct, 'm2m:dvc/ena') dis = Utils.findXPath(dct, 'm2m:dvc/dis') if ena is not None and dis is not None and ena and dis: return Result(status=False, rsc=RC.badRequest, dbg='both ena and dis updated to True is not allowed') return super().update(dct, originator)
class AE(AnnounceableResource): def __init__(self, dct: JSON = None, pi: str = None, create: bool = False) -> None: super().__init__(T.AE, dct, pi, create=create, attributePolicies=attributePolicies) self.resourceAttributePolicies = aePolicies # only the resource type's own policies if self.dict is not None: self.setAttribute('aei', Utils.uniqueAEI(), overwrite=False) self.setAttribute('rr', False, overwrite=False) # Enable check for allowed sub-resources def canHaveChild(self, resource: Resource) -> bool: return super()._canHaveChild( resource, [T.ACP, T.CNT, T.FCNT, T.GRP, T.PCH, T.SUB]) def childWillBeAdded(self, childResource: Resource, originator: str) -> Result: if not (res := super().childWillBeAdded(childResource, originator)).status: return res # Perform checks for <PCH> if childResource.ty == T.PCH: # Check correct originator. Even the ADMIN is not allowed that if self.aei != originator: Logging.logDebug(dbg := f'Originator must be the parent <AE>') return Result(status=False, rsc=RC.originatorHasNoPrivilege, dbg=dbg) # check that there will only by one PCH as a child if CSE.dispatcher.countDirectChildResources(self.ri, ty=T.PCH) > 0: return Result(status=False, rsc=RC.badRequest, dbg='Only one PCH per AE is allowed') return Result(status=True)
class NotificationManager(object): def __init__(self) -> None: self.lockBatchNotification = Lock() # Lock for batchNotifications self.enableNotifications = Configuration.get('cse.enableNotifications') if self.enableNotifications: Logging.log('Notifications ENABLED') else: Logging.log('Notifications DISABLED') Logging.log('NotificationManager initialized') def shutdown(self) -> bool: Logging.log('NotificationManager shut down') return True ########################################################################### # # Subscriptions # # def addSubscription(self, subscription:Resource, originator:str) -> Result: # if not self.enableNotifications: # return Result(status=False, rsc=RC.subscriptionVerificationInitiationFailed, dbg='notifications are disabled') # Logging.logDebug('Adding subscription') # if (res := self._checkNusInSubscription(subscription, originator=originator)).lst is None: # verification requests happen here # return Result(status=False, rsc=res.rsc, dbg=res.dbg) # return Result(status=True) if CSE.storage.addSubscription(subscription) else Result(status=False, rsc=RC.internalServerError, dbg='cannot add subscription to database') def addSubscription(self, subscription: Resource, originator: str) -> Result: if not self.enableNotifications: return Result(status=False, rsc=RC.subscriptionVerificationInitiationFailed, dbg='notifications are disabled') Logging.logDebug('Adding subscription') if (res := self._checkNusInSubscription(subscription, originator=originator) ).lst is None: # verification requests happen here return Result(status=False, rsc=res.rsc, dbg=res.dbg) return Result(status=True) if CSE.storage.addSubscription( subscription) else Result( status=False, rsc=RC.internalServerError, dbg='cannot add subscription to database')
def hasAcpiUpdatePermission(self, request: CSERequest, targetResource: Resource, originator: str) -> Result: """ Check whether this is actually a correct update of the acpi attribute, and whether this is actually allowed. """ updatedAttributes = Utils.findXPath(request.dict, '{0}') # Check that acpi, if present, is the only attribute if 'acpi' in updatedAttributes: if len(updatedAttributes) > 1: Logging.logDebug( dbg := '"acpi" must be the only attribute in update') return Result(status=False, rsc=RC.badRequest, dbg=dbg) # Check whether the originator has UPDATE privileges for the acpi attribute (pvs!) if targetResource.acpi is None: if originator != targetResource[targetResource._originator]: Logging.logDebug( dbg := f'No access to update acpi for originator: {originator}' ) return Result(status=False, rsc=RC.originatorHasNoPrivilege, dbg=dbg) else: pass # allowed for creating originator else: # test the current acpi whether the originator is allowed to update the acpi for ri in targetResource.acpi: if (acp := CSE.dispatcher.retrieveResource(ri).resource ) is None: Logging.logWarn( f'Access Check for acpi: referenced <ACP> resource not found: {ri}' ) continue if acp.checkSelfPermission(originator, Permission.UPDATE): break else: Logging.logDebug( dbg := f'Originator has no permission to update acpi: {ri}') return Result(status=False, rsc=RC.originatorHasNoPrivilege, dbg=dbg)
def foptRequest(self, operation: Operation, fopt: GRP_FOPT, request: CSERequest, id: str, originator: str) -> Result: """ Handle requests to a fanOutPoint. This method might be called recursivly, when there are groups in groups.""" # get parent / group and check permissions group = fopt.retrieveParentResource() if group is None: return Result(rsc=RC.notFound, dbg='group resource not found') # get the permission flags for the request operation permission = operation.permission() #check access rights for the originator through memberAccessControlPolicies if CSE.security.hasAccess(originator, group, requestedPermission=permission, ty=request.headers.resourceType, isCreateRequest=True if operation == Operation.CREATE else False) == False: return Result(rsc=RC.originatorHasNoPrivilege, dbg='access denied') # check whether there is something after the /fopt ... _, _, tail = id.partition('/fopt/') if '/fopt/' in id else (None, None, '') Logging.logDebug(f'Adding additional path elements: {tail}') # walk through all members resultList: list[Result] = [] tail = '/' + tail if len( tail) > 0 else '' # add remaining path, if any for mid in group.mid.copy( ): # copy mi because it is changed in the loop # Try to get the SRN and add the tail if (srn := Utils.structuredPathFromRI(mid)) is not None: mid = srn + tail else: mid = mid + tail # Invoke the request if operation == Operation.RETRIEVE: if (res := CSE.dispatcher.processRetrieveRequest( request, originator, mid)).resource is None: return res