Loading services/TS29222_CAPIF_Publish_Service_API/published_apis/core/auth_manager.py +4 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,10 @@ class AuthManager(Resource): auth_context = cert_col.find_one({"id":apf_id}) # Service APIs published by an interconnected CCF have no certificate entry if auth_context is None: return if "services" in auth_context["resources"]: if service_id in auth_context["resources"]["services"]: auth_context["resources"]["services"].remove(service_id) Loading services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py +102 −3 Original line number Diff line number Diff line Loading @@ -22,6 +22,9 @@ import encoder TOTAL_FEATURES = 10 SUPPORTED_FEATURES_HEX = "120" # Seconds allowed for a single request towards a peer CCF INTERCONNECTION_TIMEOUT = 30 publisher_ops = Publisher() Loading Loading @@ -189,10 +192,10 @@ class PublishServiceOperations(Resource): ) # 1. Check shareableInfo if serviceapidescription.shareable_info.is_shareable: if serviceapidescription.shareable_info and serviceapidescription.shareable_info.is_shareable: interconnected_col = self.db.get_col_by_name(self.db.interconnected) # 2. Iterate through capifProvDoms for dom in serviceapidescription.shareable_info.capif_prov_doms: for dom in serviceapidescription.shareable_info.capif_prov_doms or []: # 3. For each domain, check if there is record in the "interconnected" table interconnected_ccf = interconnected_col.find_one({"dst_prov_dom": dom}) # 4. If yes, make a publish request with CCF certificate Loading Loading @@ -298,6 +301,90 @@ class PublishServiceOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) def unpublish_from_interconnected_ccfs(self, api_name, capif_prov_doms): """Unpublish a shared service API from every interconnected CCF holding a copy. A peer assigns its own api_id to the copy it stores, and that id is never kept locally, so it is resolved by api_name out of the service APIs this CCF published there. Returns an error response if a peer could not be reached or refused the removal, None if every copy is gone. """ interconnected_col = self.db.get_col_by_name(self.db.interconnected) config_col = self.db.get_col_by_name(self.db.capif_configuration) config = config_col.find_one({}, {"_id": 0}) ccf_id = config['ccf_id'] headers = {'accept': 'application/json'} certs = ('certs/server.crt', 'certs/server.key') for dom in capif_prov_doms: interconnected_ccf = interconnected_col.find_one({"dst_prov_dom": dom}) if interconnected_ccf is None: continue url = 'https://{}/published-apis/v1/{}/service-apis'.format(dom, ccf_id) try: response = requests.request("GET", url, headers=headers, cert=certs, verify='certs/ca.crt', timeout=INTERCONNECTION_TIMEOUT) except requests.exceptions.RequestException as exc: current_app.logger.error( "Interconnection: listing service apis on {} failed: {}".format(dom, str(exc))) return internal_server_error( detail="Could not reach interconnected CCF {}".format(dom), cause="Service API is still published on an interconnected CCF") try: shared_apis = response.json() except ValueError: shared_apis = None if response.status_code != 200 or not isinstance(shared_apis, list): current_app.logger.error( "Interconnection: {} answered the service api listing with status {}".format( dom, response.status_code)) return internal_server_error( detail="Interconnected CCF {} did not return its published service apis".format(dom), cause="Service API is still published on an interconnected CCF") # The listing is serialized in camel case by the peer shared_api_id = next( (shared_api.get("apiId") for shared_api in shared_apis if isinstance(shared_api, dict) and shared_api.get("apiName") == api_name), None) if shared_api_id is None: current_app.logger.warning( "Interconnection: service api {} is not published on {}".format(api_name, dom)) continue try: response = requests.request("DELETE", '{}/{}'.format(url, shared_api_id), headers=headers, cert=certs, verify='certs/ca.crt', timeout=INTERCONNECTION_TIMEOUT) except requests.exceptions.RequestException as exc: current_app.logger.error( "Interconnection: unpublish from {} failed: {}".format(dom, str(exc))) return internal_server_error( detail="Could not reach interconnected CCF {}".format(dom), cause="Service API is still published on an interconnected CCF") # 404 means the peer already dropped its copy if response.status_code not in (204, 404): current_app.logger.error( "Interconnection: {} refused to unpublish service api {} with status {}".format( dom, shared_api_id, response.status_code)) return internal_server_error( detail="Interconnected CCF {} refused to unpublish the service api".format(dom), cause="Service API is still published on an interconnected CCF") current_app.logger.debug( "Interconnection: service api {} unpublished from {}".format(api_name, dom)) return None def delete_serviceapidescription(self, service_api_id, apf_id): mycol = self.db.get_col_by_name(self.db.service_api_descriptions) Loading Loading @@ -327,8 +414,20 @@ class PublishServiceOperations(Resource): detail="Service API not existing", cause="Service API id not found") # The copies held by interconnected CCFs go first, so that a peer that cannot # be reached leaves the service api published here instead of orphaned there shareable_info = serviceapidescription_dict.get("shareable_info") or {} if shareable_info.get("is_shareable"): interconnection_error = self.unpublish_from_interconnected_ccfs( serviceapidescription_dict.get("api_name"), shareable_info.get("capif_prov_doms") or []) if interconnection_error is not None: return interconnection_error mycol.delete_one(my_query) if "APF" in apf_id: self.auth_manager.remove_auth_service(service_api_id, apf_id) current_app.logger.info("Removed service from database") Loading Loading
services/TS29222_CAPIF_Publish_Service_API/published_apis/core/auth_manager.py +4 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,10 @@ class AuthManager(Resource): auth_context = cert_col.find_one({"id":apf_id}) # Service APIs published by an interconnected CCF have no certificate entry if auth_context is None: return if "services" in auth_context["resources"]: if service_id in auth_context["resources"]["services"]: auth_context["resources"]["services"].remove(service_id) Loading
services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py +102 −3 Original line number Diff line number Diff line Loading @@ -22,6 +22,9 @@ import encoder TOTAL_FEATURES = 10 SUPPORTED_FEATURES_HEX = "120" # Seconds allowed for a single request towards a peer CCF INTERCONNECTION_TIMEOUT = 30 publisher_ops = Publisher() Loading Loading @@ -189,10 +192,10 @@ class PublishServiceOperations(Resource): ) # 1. Check shareableInfo if serviceapidescription.shareable_info.is_shareable: if serviceapidescription.shareable_info and serviceapidescription.shareable_info.is_shareable: interconnected_col = self.db.get_col_by_name(self.db.interconnected) # 2. Iterate through capifProvDoms for dom in serviceapidescription.shareable_info.capif_prov_doms: for dom in serviceapidescription.shareable_info.capif_prov_doms or []: # 3. For each domain, check if there is record in the "interconnected" table interconnected_ccf = interconnected_col.find_one({"dst_prov_dom": dom}) # 4. If yes, make a publish request with CCF certificate Loading Loading @@ -298,6 +301,90 @@ class PublishServiceOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) def unpublish_from_interconnected_ccfs(self, api_name, capif_prov_doms): """Unpublish a shared service API from every interconnected CCF holding a copy. A peer assigns its own api_id to the copy it stores, and that id is never kept locally, so it is resolved by api_name out of the service APIs this CCF published there. Returns an error response if a peer could not be reached or refused the removal, None if every copy is gone. """ interconnected_col = self.db.get_col_by_name(self.db.interconnected) config_col = self.db.get_col_by_name(self.db.capif_configuration) config = config_col.find_one({}, {"_id": 0}) ccf_id = config['ccf_id'] headers = {'accept': 'application/json'} certs = ('certs/server.crt', 'certs/server.key') for dom in capif_prov_doms: interconnected_ccf = interconnected_col.find_one({"dst_prov_dom": dom}) if interconnected_ccf is None: continue url = 'https://{}/published-apis/v1/{}/service-apis'.format(dom, ccf_id) try: response = requests.request("GET", url, headers=headers, cert=certs, verify='certs/ca.crt', timeout=INTERCONNECTION_TIMEOUT) except requests.exceptions.RequestException as exc: current_app.logger.error( "Interconnection: listing service apis on {} failed: {}".format(dom, str(exc))) return internal_server_error( detail="Could not reach interconnected CCF {}".format(dom), cause="Service API is still published on an interconnected CCF") try: shared_apis = response.json() except ValueError: shared_apis = None if response.status_code != 200 or not isinstance(shared_apis, list): current_app.logger.error( "Interconnection: {} answered the service api listing with status {}".format( dom, response.status_code)) return internal_server_error( detail="Interconnected CCF {} did not return its published service apis".format(dom), cause="Service API is still published on an interconnected CCF") # The listing is serialized in camel case by the peer shared_api_id = next( (shared_api.get("apiId") for shared_api in shared_apis if isinstance(shared_api, dict) and shared_api.get("apiName") == api_name), None) if shared_api_id is None: current_app.logger.warning( "Interconnection: service api {} is not published on {}".format(api_name, dom)) continue try: response = requests.request("DELETE", '{}/{}'.format(url, shared_api_id), headers=headers, cert=certs, verify='certs/ca.crt', timeout=INTERCONNECTION_TIMEOUT) except requests.exceptions.RequestException as exc: current_app.logger.error( "Interconnection: unpublish from {} failed: {}".format(dom, str(exc))) return internal_server_error( detail="Could not reach interconnected CCF {}".format(dom), cause="Service API is still published on an interconnected CCF") # 404 means the peer already dropped its copy if response.status_code not in (204, 404): current_app.logger.error( "Interconnection: {} refused to unpublish service api {} with status {}".format( dom, shared_api_id, response.status_code)) return internal_server_error( detail="Interconnected CCF {} refused to unpublish the service api".format(dom), cause="Service API is still published on an interconnected CCF") current_app.logger.debug( "Interconnection: service api {} unpublished from {}".format(api_name, dom)) return None def delete_serviceapidescription(self, service_api_id, apf_id): mycol = self.db.get_col_by_name(self.db.service_api_descriptions) Loading Loading @@ -327,8 +414,20 @@ class PublishServiceOperations(Resource): detail="Service API not existing", cause="Service API id not found") # The copies held by interconnected CCFs go first, so that a peer that cannot # be reached leaves the service api published here instead of orphaned there shareable_info = serviceapidescription_dict.get("shareable_info") or {} if shareable_info.get("is_shareable"): interconnection_error = self.unpublish_from_interconnected_ccfs( serviceapidescription_dict.get("api_name"), shareable_info.get("capif_prov_doms") or []) if interconnection_error is not None: return interconnection_error mycol.delete_one(my_query) if "APF" in apf_id: self.auth_manager.remove_auth_service(service_api_id, apf_id) current_app.logger.info("Removed service from database") Loading