diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/apiinvokerenrolmentdetails.py b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/apiinvokerenrolmentdetails.py index 80988ca84cadea6cb3e2b0f5597b4ae7b9748c6e..9dd3df599312e632c5f3f74ae25c896a9a7a917a 100644 --- a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/apiinvokerenrolmentdetails.py +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/apiinvokerenrolmentdetails.py @@ -3,30 +3,33 @@ from pymongo import ReturnDocument import secrets import requests from .responses import bad_request_error, not_found_error, forbidden_error, internal_server_error, make_response -from flask import current_app, Flask, Response +from flask import current_app, Response import json from datetime import datetime -from ..util import dict_to_camel_case, clean_empty, serialize_clean_camel_case +from ..util import dict_to_camel_case, serialize_clean_camel_case from .auth_manager import AuthManager from .resources import Resource from ..config import Config from api_invoker_management.models.api_invoker_enrolment_details import APIInvokerEnrolmentDetails from .redis_event import RedisEvent +from .redis_internal_event import RedisInternalEvent from .publisher import Publisher publisher_ops = Publisher() + + class InvokerManagementOperations(Resource): def __check_api_invoker_id(self, api_invoker_id): current_app.logger.debug("Cheking api invoker id") mycol = self.db.get_col_by_name(self.db.invoker_enrolment_details) - my_query = {'api_invoker_id':api_invoker_id} + my_query = {'api_invoker_id': api_invoker_id} old_values = mycol.find_one(my_query) if old_values is None: current_app.logger.error("Not found api invoker id") - return not_found_error(detail="Please provide an existing Network App ID", cause= "Not exist Network App ID" ) + return not_found_error(detail="Please provide an existing Network App ID", cause="Not exist Network App ID") return old_values @@ -35,13 +38,14 @@ class InvokerManagementOperations(Resource): url = f"http://{self.config['ca_factory']['url']}:{self.config['ca_factory']['port']}/v1/pki_int/sign/my-ca" headers = {'X-Vault-Token': self.config['ca_factory']['token']} data = { - 'format':'pem_bundle', + 'format': 'pem_bundle', 'ttl': '43000h', 'csr': publick_key, 'common_name': invoker_id } - response = requests.request("POST", url, headers=headers, data=data, verify = self.config["ca_factory"].get("verify", False)) + response = requests.request("POST", url, headers=headers, data=data, + verify=self.config["ca_factory"].get("verify", False)) print(response) response_payload = json.loads(response.text) @@ -52,51 +56,59 @@ class InvokerManagementOperations(Resource): self.auth_manager = AuthManager() self.config = Config().get_config() - def add_apiinvokerenrolmentdetail(self, apiinvokerenrolmentdetail, username, uuid): mycol = self.db.get_col_by_name(self.db.invoker_enrolment_details) - #try: + # try: current_app.logger.debug("Creating invoker resource") - res = mycol.find_one({'onboarding_information.api_invoker_public_key': apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key}) + res = mycol.find_one({'onboarding_information.api_invoker_public_key': + apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key}) if res is not None: - current_app.logger.error("Generating forbbiden error, invoker registered") - return forbidden_error(detail= "Invoker already registered", cause = "Identical invoker public key") + current_app.logger.error( + "Generating forbbiden error, invoker registered") + return forbidden_error(detail="Invoker already registered", cause="Identical invoker public key") if rfc3987.match(apiinvokerenrolmentdetail.notification_destination, rule="URI") is None: current_app.logger.error("Bad url format") - return bad_request_error(detail="Bad Param", cause = "Detected Bad formar of param", invalid_params=[{"param": "notificationDestination", "reason": "Not valid URL format"}]) + return bad_request_error(detail="Bad Param", cause="Detected Bad formar of param", invalid_params=[{"param": "notificationDestination", "reason": "Not valid URL format"}]) current_app.logger.debug("Signing Certificate") api_invoker_id = 'INV'+str(secrets.token_hex(15)) - cert = self.__sign_cert(apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key, api_invoker_id) + cert = self.__sign_cert( + apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key, api_invoker_id) apiinvokerenrolmentdetail.api_invoker_id = api_invoker_id current_app.logger.debug(cert) - apiinvokerenrolmentdetail.onboarding_information.api_invoker_certificate = cert['data']['certificate'] + apiinvokerenrolmentdetail.onboarding_information.api_invoker_certificate = cert[ + 'data']['certificate'] # Onboarding Date Record invoker_dict = apiinvokerenrolmentdetail.to_dict() invoker_dict["onboarding_date"] = datetime.now() - invoker_dict["username"]=username - invoker_dict["uuid"]=uuid + invoker_dict["username"] = username + invoker_dict["uuid"] = uuid mycol.insert_one(invoker_dict) current_app.logger.debug("Invoker inserted in database") current_app.logger.debug("Netapp onboarded sucessfuly") - self.auth_manager.add_auth_invoker(cert['data']['certificate'], api_invoker_id) + self.auth_manager.add_auth_invoker( + cert['data']['certificate'], api_invoker_id) - res = make_response(object=serialize_clean_camel_case(apiinvokerenrolmentdetail), status=201) - res.headers['Location'] = "/api-invoker-management/v1/onboardedInvokers/" + str(api_invoker_id) + res = make_response(object=serialize_clean_camel_case( + apiinvokerenrolmentdetail), status=201) + res.headers['Location'] = "/api-invoker-management/v1/onboardedInvokers/" + \ + str(api_invoker_id) if res.status_code == 201: current_app.logger.info("Invoker Created") - RedisEvent("API_INVOKER_ONBOARDED", ["apiInvokerIds"], [[str(api_invoker_id)]]).send_event() + RedisEvent("API_INVOKER_ONBOARDED", + ["apiInvokerIds"], + [[str(api_invoker_id)]]).send_event() return res def update_apiinvokerenrolmentdetail(self, onboard_id, apiinvokerenrolmentdetail): @@ -111,16 +123,23 @@ class InvokerManagementOperations(Resource): return result if apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key != result["onboarding_information"]["api_invoker_public_key"]: - cert = self.__sign_cert(apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key, result["api_invoker_id"]) - apiinvokerenrolmentdetail.onboarding_information.api_invoker_certificate = cert['data']['certificate'] - self.auth_manager.update_auth_invoker(cert['data']["certificate"], onboard_id) + cert = self.__sign_cert( + apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key, result["api_invoker_id"]) + apiinvokerenrolmentdetail.onboarding_information.api_invoker_certificate = cert[ + 'data']['certificate'] + self.auth_manager.update_auth_invoker( + cert['data']["certificate"], onboard_id) apiinvokerenrolmentdetail_update = apiinvokerenrolmentdetail.to_dict() apiinvokerenrolmentdetail_update = { key: value for key, value in apiinvokerenrolmentdetail_update.items() if value is not None } - result = mycol.find_one_and_update(result, {"$set":apiinvokerenrolmentdetail_update}, projection={'_id': 0},return_document=ReturnDocument.AFTER ,upsert=False) + result = mycol.find_one_and_update(result, + {"$set": apiinvokerenrolmentdetail_update}, + projection={'_id': 0}, + return_document=ReturnDocument.AFTER, + upsert=False) result = { key: value for key, value in result.items() if value is not None @@ -130,10 +149,13 @@ class InvokerManagementOperations(Resource): invoker_updated = APIInvokerEnrolmentDetails().from_dict(dict_to_camel_case(result)) - res = make_response(object=serialize_clean_camel_case(invoker_updated), status=200) + res = make_response(object=serialize_clean_camel_case( + invoker_updated), status=200) if res.status_code == 200: current_app.logger.info("Invoker Updated") - RedisEvent("API_INVOKER_UPDATED", ["apiInvokerIds"], [[onboard_id]]).send_event() + RedisEvent("API_INVOKER_UPDATED", + ["apiInvokerIds"], + [[onboard_id]]).send_event() return res except Exception as e: @@ -151,22 +173,26 @@ class InvokerManagementOperations(Resource): if isinstance(result, Response): return result - mycol.delete_one({'api_invoker_id':onboard_id}) + mycol.delete_one({'api_invoker_id': onboard_id}) self.auth_manager.remove_auth_invoker(onboard_id) current_app.logger.debug("Invoker resource removed from database") current_app.logger.debug("Netapp offboarded sucessfuly") - out = "The Network App matching onboardingId " + onboard_id + " was offboarded." + out = "The Network App matching onboardingId " + onboard_id + " was offboarded." res = make_response(out, status=204) if res.status_code == 204: current_app.logger.info("Invoker Removed") - RedisEvent("API_INVOKER_OFFBOARDED", ["apiInvokerIds"], [[onboard_id]]).send_event() - publisher_ops.publish_message("internal-messages", f"invoker-removed:{onboard_id}") + RedisEvent("API_INVOKER_OFFBOARDED", + ["apiInvokerIds"], + [[onboard_id]]).send_event() + RedisInternalEvent("INVOKER-REMOVED", + "invokerId", + { + "api_invoker_id": onboard_id + }).send_event() return res except Exception as e: exception = "An exception occurred in remove invoker" current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - - diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/consumer_messager.py b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/consumer_messager.py index 26f8d6b025229c757bd7e909699c337d02a4a53d..8d5c56f3b71070734d4b9fbabd1f3b81f8d1435c 100644 --- a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/consumer_messager.py +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/consumer_messager.py @@ -2,6 +2,8 @@ import redis from .invoker_internal_ops import InvokerInternalOperations from flask import current_app +import json + class Subscriber(): @@ -14,13 +16,38 @@ class Subscriber(): def listen(self): for raw_message in self.p.listen(): if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": - message, invoker_id, api_id = raw_message["data"].decode('utf-8').split(":") - if message == "security-context-created": - current_app.logger.debug("Internal message received, updating Api list on invoker") - self.invoker_ops.update_services_list(invoker_id, api_id) - if message == "security-context-removed": - current_app.logger.debug("Internal message received, removing service in Api list of invoker") - self.invoker_ops.remove_services_list(invoker_id, api_id) - - - + current_app.logger.info("New internal event received") + internal_redis_event = json.loads( + raw_message["data"].decode('utf-8')) + if internal_redis_event.get('event') == "SECURITY-CONTEXT-CREATED": + current_app.logger.debug( + "Internal message received, updating Api list on invoker") + security_context_information = internal_redis_event.get( + 'information', None) + if security_context_information is not None: + api_invoker_id = security_context_information.get( + 'api_invoker_id') + api_id = security_context_information.get('api_id') + self.invoker_ops.update_services_list( + api_invoker_id, api_id) + elif internal_redis_event.get('event') == "SECURITY-CONTEXT-REMOVED": + current_app.logger.debug( + "Internal message received, removing service in Api list of invoker") + security_context_information = internal_redis_event.get( + 'information', None) + if security_context_information is not None: + api_invoker_id = security_context_information.get( + 'api_invoker_id') + api_id = security_context_information.get('api_id') + self.invoker_ops.remove_services_list( + api_invoker_id, api_id) + elif internal_redis_event.get('event') == "INVOKER-REMOVED": + api_invoker_id = internal_redis_event.get( + 'information', {"api_invoker_id": None}).get('api_invoker_id') + if api_invoker_id is not None: + self.acls_ops.remove_invoker_acl(api_invoker_id) + elif internal_redis_event.get('event') == "PROVIDER-REMOVED": + aef_ids = internal_redis_event.get( + 'information', {"aef_ids": []}).get('aef_ids') + if len(aef_ids) > 0: + self.acls_ops.remove_provider_acls(aef_ids[0]) diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_internal_event.py b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_internal_event.py new file mode 100644 index 0000000000000000000000000000000000000000..faa5ea12cb985b96b3cf8dfb7aaba6136b39e01e --- /dev/null +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_internal_event.py @@ -0,0 +1,35 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisInternalEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.INTERNAL_MESSAGES = [ + 'INVOKER-REMOVED', + 'PROVIDER-REMOVED', + 'SECURITY-CONTEXT-CREATED', + 'SECURITY-CONTEXT-REMOVED', + 'create-acl', + 'remove-acl', + ] + if event not in self.INTERNAL_MESSAGES: + raise Exception( + "Internal Message (" + event + ") is not on INTERNAL_MESSAGES enum (" + ','.join(self.INTERNAL_MESSAGES) + ")") + self.redis_event = { + "event": event + } + if event_detail_key is not None and information is not None: + self.redis_event['key'] = event_detail_key + self.redis_event['information'] = information + + def to_string(self): + return json.dumps(self.redis_event, cls=JSONEncoder) + + def send_event(self): + publisher_ops.publish_message("internal-messages", self.to_string()) + + def __call__(self): + return self.redis_event diff --git a/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/provider_enrolment_details_api.py b/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/provider_enrolment_details_api.py index 30760b996db7ae7fbbc5fcd212bfaf979dc8a7f2..de8b08f969573f6854a1dedeb4fb3eacddcd7a99 100644 --- a/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/provider_enrolment_details_api.py +++ b/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/provider_enrolment_details_api.py @@ -10,6 +10,7 @@ from .resources import Resource from .auth_manager import AuthManager from api_provider_management.models.api_provider_enrolment_details import APIProviderEnrolmentDetails # noqa: E501 +from .redis_internal_event import RedisInternalEvent class ProviderManagementOperations(Resource): @@ -40,19 +41,24 @@ class ProviderManagementOperations(Resource): my_provider_enrolment_details = mycol.find_one(search_filter) if my_provider_enrolment_details is not None: - current_app.logger.error("Found provider registered with same id") + current_app.logger.error( + "Found provider registered with same id") return forbidden_error(detail="Provider already registered", cause="Identical provider reg sec") - api_provider_enrolment_details.api_prov_dom_id = secrets.token_hex(15) + api_provider_enrolment_details.api_prov_dom_id = secrets.token_hex( + 15) current_app.logger.debug("Generating certs to api prov funcs") for api_provider_func in api_provider_enrolment_details.api_prov_funcs: - api_provider_func.api_prov_func_id = api_provider_func.api_prov_func_role + str(secrets.token_hex(15)) - certificate = sign_certificate(api_provider_func.reg_info.api_prov_pub_key, api_provider_func.api_prov_func_id) + api_provider_func.api_prov_func_id = api_provider_func.api_prov_func_role + \ + str(secrets.token_hex(15)) + certificate = sign_certificate( + api_provider_func.reg_info.api_prov_pub_key, api_provider_func.api_prov_func_id) api_provider_func.reg_info.api_prov_cert = certificate - self.auth_manager.add_auth_provider(certificate, api_provider_func.api_prov_func_id, api_provider_func.api_prov_func_role, api_provider_enrolment_details.api_prov_dom_id) + self.auth_manager.add_auth_provider(certificate, api_provider_func.api_prov_func_id, + api_provider_func.api_prov_func_role, api_provider_enrolment_details.api_prov_dom_id) # Onboarding Date Record provider_dict = api_provider_enrolment_details.to_dict() @@ -61,12 +67,14 @@ class ProviderManagementOperations(Resource): provider_dict["uuid"] = uuid mycol.insert_one(provider_dict) - + current_app.logger.debug("Provider inserted in database") - res = make_response(object=serialize_clean_camel_case(api_provider_enrolment_details), status=201) + res = make_response(object=serialize_clean_camel_case( + api_provider_enrolment_details), status=201) - res.headers['Location'] = "/api-provider-management/v1/registrations/" + str(api_provider_enrolment_details.api_prov_dom_id) + res.headers['Location'] = "/api-provider-management/v1/registrations/" + \ + str(api_provider_enrolment_details.api_prov_dom_id) return res except Exception as e: @@ -84,17 +92,32 @@ class ProviderManagementOperations(Resource): if isinstance(result, Response): return result - apf_id = [ provider_func['api_prov_func_id'] for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'APF' ] - aef_id = [ provider_func['api_prov_func_id'] for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'AEF' ] - amf_id = [ provider_func['api_prov_func_id'] for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'AMF' ] + func_ids = list() + for provider_func in result["api_prov_funcs"]: + func_ids.append(provider_func['api_prov_func_id']) + apf_ids = [provider_func['api_prov_func_id'] + for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'APF'] + aef_ids = [provider_func['api_prov_func_id'] + for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'AEF'] + amf_ids = [provider_func['api_prov_func_id'] + for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'AMF'] mycol.delete_one({'api_prov_dom_id': api_prov_dom_id}) - out = "The provider matching apiProvDomainId " + api_prov_dom_id + " was offboarded." + out = "The provider matching apiProvDomainId " + \ + api_prov_dom_id + " was offboarded." current_app.logger.debug("Removed provider domain from database") - self.auth_manager.remove_auth_provider([apf_id[0], aef_id[0], amf_id[0]]) + self.auth_manager.remove_auth_provider(func_ids) + + RedisInternalEvent("PROVIDER-REMOVED", + "providerIds", + { + "apf_ids": apf_ids, + "aef_ids": aef_ids, + "amf_ids": amf_ids, + "all_ids": apf_ids + aef_ids + amf_ids + }).send_event() - self.publish_ops.publish_message("internal-messages", f"provider-removed:{aef_id[0]}:{apf_id[0]}:{amf_id[0]}") return make_response(object=out, status=204) except Exception as e: @@ -114,26 +137,33 @@ class ProviderManagementOperations(Resource): for func in api_provider_enrolment_details.api_prov_funcs: if func.api_prov_func_id is None: - func.api_prov_func_id = func.api_prov_func_role + str(secrets.token_hex(15)) - certificate = sign_certificate(func.reg_info.api_prov_pub_key, func.api_prov_func_id) + func.api_prov_func_id = func.api_prov_func_role + \ + str(secrets.token_hex(15)) + certificate = sign_certificate( + func.reg_info.api_prov_pub_key, func.api_prov_func_id) func.reg_info.api_prov_cert = certificate - self.auth_manager.update_auth_provider(certificate, func.api_prov_func_id, api_prov_dom_id, func.api_prov_func_role) + self.auth_manager.update_auth_provider( + certificate, func.api_prov_func_id, api_prov_dom_id, func.api_prov_func_role) else: api_prov_funcs = result["api_prov_funcs"] for api_func in api_prov_funcs: if func.api_prov_func_id == api_func["api_prov_func_id"]: if func.api_prov_func_role != api_func["api_prov_func_role"]: - return bad_request_error(detail="Bad Role in provider", cause="Different role in update reqeuest", invalid_params=[{"param":"api_prov_func_role","reason":"different role with same id"}]) + return bad_request_error(detail="Bad Role in provider", cause="Different role in update reqeuest", invalid_params=[{"param": "api_prov_func_role", "reason": "different role with same id"}]) if func.reg_info.api_prov_pub_key != api_func["reg_info"]["api_prov_pub_key"]: - certificate = sign_certificate(func.reg_info.api_prov_pub_key, api_func["api_prov_func_id"]) + certificate = sign_certificate( + func.reg_info.api_prov_pub_key, api_func["api_prov_func_id"]) func.reg_info.api_prov_cert = certificate - self.auth_manager.update_auth_provider(certificate, func.api_prov_func_id, api_prov_dom_id, func.api_prov_func_role) + self.auth_manager.update_auth_provider( + certificate, func.api_prov_func_id, api_prov_dom_id, func.api_prov_func_role) api_provider_enrolment_details = api_provider_enrolment_details.to_dict() - api_provider_enrolment_details = clean_empty(api_provider_enrolment_details) + api_provider_enrolment_details = clean_empty( + api_provider_enrolment_details) - result = mycol.find_one_and_update(result, {"$set":api_provider_enrolment_details}, projection={'_id': 0},return_document=ReturnDocument.AFTER ,upsert=False) + result = mycol.find_one_and_update(result, {"$set": api_provider_enrolment_details}, projection={ + '_id': 0}, return_document=ReturnDocument.AFTER, upsert=False) result = clean_empty(result) current_app.logger.debug("Provider domain updated in database") @@ -157,9 +187,11 @@ class ProviderManagementOperations(Resource): return result api_provider_enrolment_details_patch = api_provider_enrolment_details_patch.to_dict() - api_provider_enrolment_details_patch = clean_empty(api_provider_enrolment_details_patch) + api_provider_enrolment_details_patch = clean_empty( + api_provider_enrolment_details_patch) - result = mycol.find_one_and_update(result, {"$set":api_provider_enrolment_details_patch}, projection={'_id': 0},return_document=ReturnDocument.AFTER ,upsert=False) + result = mycol.find_one_and_update(result, {"$set": api_provider_enrolment_details_patch}, projection={ + '_id': 0}, return_document=ReturnDocument.AFTER, upsert=False) result = clean_empty(result) @@ -171,4 +203,4 @@ class ProviderManagementOperations(Resource): except Exception as e: exception = "An exception occurred in patch provider" current_app.logger.error(exception + "::" + str(e)) - return internal_server_error(detail=exception, cause=str(e)) \ No newline at end of file + return internal_server_error(detail=exception, cause=str(e)) diff --git a/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/redis_internal_event.py b/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/redis_internal_event.py new file mode 100644 index 0000000000000000000000000000000000000000..faa5ea12cb985b96b3cf8dfb7aaba6136b39e01e --- /dev/null +++ b/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/redis_internal_event.py @@ -0,0 +1,35 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisInternalEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.INTERNAL_MESSAGES = [ + 'INVOKER-REMOVED', + 'PROVIDER-REMOVED', + 'SECURITY-CONTEXT-CREATED', + 'SECURITY-CONTEXT-REMOVED', + 'create-acl', + 'remove-acl', + ] + if event not in self.INTERNAL_MESSAGES: + raise Exception( + "Internal Message (" + event + ") is not on INTERNAL_MESSAGES enum (" + ','.join(self.INTERNAL_MESSAGES) + ")") + self.redis_event = { + "event": event + } + if event_detail_key is not None and information is not None: + self.redis_event['key'] = event_detail_key + self.redis_event['information'] = information + + def to_string(self): + return json.dumps(self.redis_event, cls=JSONEncoder) + + def send_event(self): + publisher_ops.publish_message("internal-messages", self.to_string()) + + def __call__(self): + return self.redis_event diff --git a/services/TS29222_CAPIF_Access_Control_Policy_API/capif_acl/core/consumer_messager.py b/services/TS29222_CAPIF_Access_Control_Policy_API/capif_acl/core/consumer_messager.py index 0da24408e801f22693e0412636df090680c1424c..fef5f8d1ba8e1e453141d10932b212158e692a7c 100644 --- a/services/TS29222_CAPIF_Access_Control_Policy_API/capif_acl/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Access_Control_Policy_API/capif_acl/core/consumer_messager.py @@ -3,13 +3,16 @@ import redis from config import Config from .internal_service_ops import InternalServiceOps from flask import current_app +import json + class Subscriber(): def __init__(self): self.config = Config().get_config() - #set this params using config params - self.r = redis.Redis(host=self.config["redis"]["host"], port=self.config["redis"]["port"], db=self.config["redis"]["db"]) + # set this params using config params + self.r = redis.Redis( + host=self.config["redis"]["host"], port=self.config["redis"]["port"], db=self.config["redis"]["db"]) self.acls_ops = InternalServiceOps() self.p = self.r.pubsub() self.p.subscribe("acls-messages", "internal-messages") @@ -19,19 +22,24 @@ class Subscriber(): if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "acls-messages": current_app.logger.info("acls-messages recived") message, *ids = raw_message["data"].decode('utf-8').split(":") - if message == "create-acl" and len(ids)==3: + if message == "create-acl" and len(ids) == 3: self.acls_ops.create_acl(ids[0], ids[1], ids[2]) - if message == "remove-acl" and len(ids)==3: + if message == "remove-acl" and len(ids) == 3: self.acls_ops.remove_acl(ids[0], ids[1], ids[2]) - if message == "remove-acl" and len(ids)==1: - self.acls_ops.remove_invoker_acl(ids[0]) - if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": - message, *ids = raw_message["data"].decode('utf-8').split(":") - if message == "invoker-removed" and len(ids)>0: + if message == "remove-acl" and len(ids) == 1: self.acls_ops.remove_invoker_acl(ids[0]) - if message == "provider-removed" or message == "service-removed" and len(ids) > 0: - self.acls_ops.remove_provider_acls(ids[0]) - - - + if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": + current_app.logger.info("New internal event received") + internal_redis_event = json.loads( + raw_message["data"].decode('utf-8')) + if internal_redis_event.get('event') == "INVOKER-REMOVED": + api_invoker_id = internal_redis_event.get( + 'information', {"api_invoker_id": None}).get('api_invoker_id') + if api_invoker_id is not None: + self.acls_ops.remove_invoker_acl(api_invoker_id) + elif internal_redis_event.get('event') == "PROVIDER-REMOVED": + aef_ids = internal_redis_event.get( + 'information', {"aef_ids": []}).get('aef_ids') + if len(aef_ids) > 0: + self.acls_ops.remove_provider_acls(aef_ids[0]) diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py b/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py index ce6479afb5c3537246cb1b2192709c6e4ed83d8c..c3d095fb6726758b7e61cc551ad5e3a02c9b66bb 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py @@ -9,6 +9,7 @@ from .notifications import Notifications from .internal_event_ops import InternalEventOperations from flask import current_app + class Subscriber(): def __init__(self): @@ -23,15 +24,20 @@ class Subscriber(): current_app.logger.info(raw_message) if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "events": current_app.logger.info("Event received") - redis_event=json.loads(raw_message["data"].decode('utf-8')) + redis_event = json.loads(raw_message["data"].decode('utf-8')) current_app.logger.info(json.dumps(redis_event, indent=4)) self.notification.send_notifications(redis_event) elif raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": - message, *subscriber_ids = raw_message["data"].decode('utf-8').split(":") - if message == "invoker-removed" and len(subscriber_ids)>0: - self.event_ops.delete_all_events(subscriber_ids) - if message == "provider-removed" and len(subscriber_ids)>0: - self.event_ops.delete_all_events(subscriber_ids) - - - + current_app.logger.info("New internal event received") + internal_redis_event = json.loads( + raw_message["data"].decode('utf-8')) + if internal_redis_event.get('event') == "INVOKER-REMOVED": + api_invoker_id = internal_redis_event.get( + 'information', {"api_invoker_id": None}).get('api_invoker_id') + if api_invoker_id is not None: + self.event_ops.delete_all_events([api_invoker_id]) + elif internal_redis_event.get('event') == "PROVIDER-REMOVED": + all_ids = internal_redis_event.get( + 'information', {"all_ids": None}).get('all_ids') + if all_ids is not None: + self.event_ops.delete_all_events(all_ids) diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py index 699208bf19690af438d9faa7faa9a948ab462979..193dae4f53601ab7d4380eb7acebc5b186fea946 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py @@ -1,24 +1,18 @@ -import connexion -from ..models.service_api_description import ServiceAPIDescription # noqa: E501 from ..core.serviceapidescriptions import PublishServiceOperations +from ..models.service_api_description import ServiceAPIDescription # noqa: E501 -from published_apis.models.problem_details import ProblemDetails # noqa: E501 -from published_apis.models.service_api_description import ServiceAPIDescription # noqa: E501 -from published_apis import util - -from flask import Response, request, current_app +from flask import request, current_app from cryptography import x509 from cryptography.hazmat.backends import default_backend from ..core.validate_user import ControlAccess from functools import wraps -import asyncio - service_operations = PublishServiceOperations() valid_user = ControlAccess() + def cert_validation(): def _cert_validation(f): @wraps(f) @@ -36,8 +30,16 @@ def cert_validation(): if cn != "superadmin": cert_signature = cert.signature.hex() + service_api_id = None + if 'serviceApiId' in args: + service_api_id = args["serviceApiId"] result = valid_user.validate_user_cert( - args["apfId"], args["serviceApiId"], cert_signature) + args["apfId"], cert_signature, service_api_id) + + if result is not None: + return result + + result = service_operations.check_apf(args["apfId"]) if result is not None: return result @@ -47,6 +49,8 @@ def cert_validation(): return __cert_validation return _cert_validation + +@cert_validation() def apf_id_service_apis_get(apf_id): # noqa: E501 """apf_id_service_apis_get @@ -63,6 +67,7 @@ def apf_id_service_apis_get(apf_id): # noqa: E501 return res +@cert_validation() def apf_id_service_apis_post(apf_id, body): # noqa: E501 """apf_id_service_apis_post @@ -77,20 +82,22 @@ def apf_id_service_apis_post(apf_id, body): # noqa: E501 """ current_app.logger.info("Publishing service") - supp_feat_dict = ServiceAPIDescription.return_supp_feat_dict(body['supportedFeatures']) + supp_feat_dict = ServiceAPIDescription.return_supp_feat_dict( + body['supportedFeatures']) vendor_specific = [] if request.is_json: if supp_feat_dict['VendorExt']: aef_profile_array = body['aefProfiles'] - for (profile,i) in zip(aef_profile_array, range(len(aef_profile_array))): + for (profile, i) in zip(aef_profile_array, range(len(aef_profile_array))): for key, val in profile.items(): if 'vendorSpecific' in key: vendor_specific.append((i, key, val)) body = ServiceAPIDescription.from_dict(request.get_json()) - res = service_operations.add_serviceapidescription(apf_id, body, vendor_specific) + res = service_operations.add_serviceapidescription( + apf_id, body, vendor_specific) return res @@ -114,6 +121,7 @@ def apf_id_service_apis_service_api_id_delete(service_api_id, apf_id): # noqa: return res + @cert_validation() def apf_id_service_apis_service_api_id_get(service_api_id, apf_id): # noqa: E501 """apf_id_service_apis_service_api_id_get @@ -132,6 +140,7 @@ def apf_id_service_apis_service_api_id_get(service_api_id, apf_id): # noqa: E50 return res + @cert_validation() def apf_id_service_apis_service_api_id_put(service_api_id, apf_id, body): # noqa: E501 """apf_id_service_apis_service_api_id_put diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/consumer_messager.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/consumer_messager.py index f781ead9e3beb684bf6face38ef9c0e4fd5cc5bc..2d13802550e9a1a94954bac8f2522a1998767355 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/consumer_messager.py @@ -2,6 +2,8 @@ import redis from .internal_service_ops import InternalServiceOps from flask import current_app +import json + class Subscriber(): @@ -15,10 +17,12 @@ class Subscriber(): current_app.logger.info("Listening publish messages") for raw_message in self.p.listen(): if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": - message, *ids = raw_message["data"].decode('utf-8').split(":") - if message == "provider-removed" and len(ids) > 0: - self.security_ops.delete_intern_service(ids[1]) - - - + current_app.logger.info("New internal event received") + internal_redis_event = json.loads( + raw_message["data"].decode('utf-8')) + if internal_redis_event.get('event') == "PROVIDER-REMOVED": + apf_ids = internal_redis_event.get( + 'information', {"apf_ids": []}).get('apf_ids') + if len(apf_ids) > 0: + self.security_ops.delete_intern_service(apf_ids) diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/internal_service_ops.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/internal_service_ops.py index 71c2759ddb2faeae4ddefc657055119bda31ee9d..d90174bd54c22837404165be6aa55ff55ad89abf 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/internal_service_ops.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/internal_service_ops.py @@ -9,14 +9,15 @@ class InternalServiceOps(Resource): Resource.__init__(self) self.auth_manager = AuthManager() - def delete_intern_service(self, apf_id): + def delete_intern_service(self, apf_ids): current_app.logger.info("Provider removed, removing services published by APF") mycol = self.db.get_col_by_name(self.db.service_api_descriptions) - my_query = {'apf_id': apf_id} - mycol.delete_many(my_query) + for apf_id in apf_ids: + my_query = {'apf_id': apf_id} + mycol.delete_many(my_query) #We dont need remove all auth events, because when provider is removed, remove auth entry #self.auth_manager.remove_auth_all_service(apf_id) - current_app.logger.info("Removed service") \ No newline at end of file + current_app.logger.info("Removed service") diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/resources.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/resources.py index efbe3c2d4cf28cec298ef17b394011dcd6aa66cb..641d4029c98c529e454163cec68c7f625f158c15 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/resources.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/resources.py @@ -1,7 +1,8 @@ -from abc import ABC, abstractmethod +from abc import ABC from db.db import MongoDatabse + class Resource(ABC): def __init__(self): - self.db = MongoDatabse() \ No newline at end of file + self.db = MongoDatabse() diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py index cddfceaba69f2becd7948b09a0c7928d7e782d78..ef5d4bd79a81507cfd9caf4c765feaecce211ac8 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py @@ -1,10 +1,10 @@ from pymongo import ReturnDocument import secrets -from flask import current_app, Flask, Response +from flask import current_app from .resources import Resource from datetime import datetime -from ..util import dict_to_camel_case, clean_empty, serialize_clean_camel_case,clean_n_camel_case +from ..util import dict_to_camel_case, clean_empty, clean_n_camel_case from .responses import internal_server_error, forbidden_error, not_found_error, unauthorized_error, make_response from .auth_manager import AuthManager from .redis_event import RedisEvent @@ -20,7 +20,7 @@ service_api_not_found_message = "Service API not found" class PublishServiceOperations(Resource): - def __check_apf(self, apf_id): + def check_apf(self, apf_id): providers_col = self.db.get_col_by_name(self.db.capif_provider_col) current_app.logger.debug("Checking apf id") @@ -29,13 +29,17 @@ class PublishServiceOperations(Resource): if provider is None: current_app.logger.error("Publisher not exist") - return unauthorized_error(detail="Publisher not existing", cause="Publisher id not found") + return unauthorized_error( + detail="Publisher not existing", + cause="Publisher id not found") list_apf_ids = [func["api_prov_func_id"] for func in provider["api_prov_funcs"] if func["api_prov_func_role"] == "APF"] if apf_id not in list_apf_ids: current_app.logger.debug("This id not belongs to APF") - return unauthorized_error(detail="You are not a publisher", cause="This API is only available for publishers") + return unauthorized_error( + detail="You are not a publisher", + cause="This API is only available for publishers") return None @@ -51,13 +55,19 @@ class PublishServiceOperations(Resource): current_app.logger.debug("Geting service apis") - result = self.__check_apf(apf_id) - - if result != None: - return result - - service = mycol.find({"apf_id": apf_id}, {"_id": 0, "api_name": 1, "api_id": 1, "aef_profiles": 1, "description": 1, - "supported_features": 1, "shareable_info": 1, "service_api_category": 1, "api_supp_feats": 1, "pub_api_path": 1, "ccf_id": 1}) + service = mycol.find( + {"apf_id": apf_id}, + {"_id": 0, + "api_name": 1, + "api_id": 1, + "aef_profiles": 1, + "description": 1, + "supported_features": 1, + "shareable_info": 1, + "service_api_category": 1, + "api_supp_feats": 1, + "pub_api_path": 1, + "ccf_id": 1}) current_app.logger.debug(service) if service is None: current_app.logger.error("Not found services for this apf id") @@ -85,12 +95,7 @@ class PublishServiceOperations(Resource): mycol = self.db.get_col_by_name(self.db.service_api_descriptions) try: - current_app.logger.debug("Publishing service") - result = self.__check_apf(apf_id) - - if result != None: - return result service = mycol.find_one( {"api_name": serviceapidescription.api_name}) @@ -107,7 +112,7 @@ class PublishServiceOperations(Resource): serviceapidescription_dict = serviceapidescription.to_dict() if vendor_specific: for vend_spec in vendor_specific: - for (profile,i) in zip(serviceapidescription_dict['aef_profiles'], range(len(serviceapidescription_dict['aef_profiles']))): + for (profile, i) in zip(serviceapidescription_dict['aef_profiles'], range(len(serviceapidescription_dict['aef_profiles']))): if i == vend_spec[0]: profile[vend_spec[1]] = vend_spec[2] rec.update(serviceapidescription_dict) @@ -118,9 +123,10 @@ class PublishServiceOperations(Resource): current_app.logger.debug("Service inserted in database") - res = make_response(object=clean_n_camel_case(serviceapidescription_dict), status=201) + res = make_response(object=clean_n_camel_case( + serviceapidescription_dict), status=201) res.headers['Location'] = "http://localhost:8080/published-apis/v1/" + \ - str(apf_id) + "/service-apis/" + str(api_id) + str(apf_id) + "/service-apis/" + str(api_id) if res.status_code == 201: current_app.logger.info("Service published") @@ -128,30 +134,36 @@ class PublishServiceOperations(Resource): if serviceapidescription.supported_features is not None: if serviceapidescription.return_supp_feat_dict(serviceapidescription.supported_features)["ApiStatusMonitoring"]: current_app.logger.info(f"Service available") - RedisEvent("SERVICE_API_AVAILABLE", ["serviceAPIDescriptions", "apiIds"], - [[clean_n_camel_case(serviceapidescription.to_dict())],[str(api_id)]]).send_event() + RedisEvent("SERVICE_API_AVAILABLE", + ["serviceAPIDescriptions", "apiIds"], + [[clean_n_camel_case(serviceapidescription.to_dict())], [str(api_id)]]).send_event() else: current_app.logger.info("Service available") - RedisEvent("SERVICE_API_AVAILABLE", ["apiIds"], - [str(api_id)]).send_event() + RedisEvent("SERVICE_API_AVAILABLE", + ["apiIds"], + [str(api_id)]).send_event() else: current_app.logger.info("Service available") - RedisEvent("SERVICE_API_AVAILABLE", ["apiIds"], - [str(api_id)]).send_event() + RedisEvent("SERVICE_API_AVAILABLE", + ["apiIds"], + [str(api_id)]).send_event() else: if serviceapidescription.supported_features is not None: if serviceapidescription.return_supp_feat_dict(serviceapidescription.supported_features)["ApiStatusMonitoring"]: current_app.logger.info(f"Service unavailable") - RedisEvent("SERVICE_API_UNAVAILABLE", ["serviceAPIDescriptions", "apiIds"], - [[clean_n_camel_case(serviceapidescription.to_dict())],[str(api_id)]]).send_event() + RedisEvent("SERVICE_API_UNAVAILABLE", + ["serviceAPIDescriptions", "apiIds"], + [[clean_n_camel_case(serviceapidescription.to_dict())], [str(api_id)]]).send_event() else: current_app.logger.info("Service available") - RedisEvent("SERVICE_API_UNAVAILABLE", ["apiIds"], - [str(api_id)]).send_event() + RedisEvent("SERVICE_API_UNAVAILABLE", + ["apiIds"], + [str(api_id)]).send_event() else: - current_app.logger.info("Service available") - RedisEvent("SERVICE_API_UNAVAILABLE", ["apiIds"], - [str(api_id)]).send_event() + current_app.logger.info("Service available") + RedisEvent("SERVICE_API_UNAVAILABLE", + ["apiIds"], + [str(api_id)]).send_event() return res @@ -167,17 +179,24 @@ class PublishServiceOperations(Resource): try: current_app.logger.debug( "Geting service api with id: " + service_api_id) - result = self.__check_apf(apf_id) - - if result != None: - return result my_query = {'apf_id': apf_id, 'api_id': service_api_id} - service_api = mycol.find_one(my_query, {"_id": 0, "api_name": 1, "api_id": 1, "aef_profiles": 1, "description": 1, - "supported_features": 1, "shareable_info": 1, "service_api_category": 1, "api_supp_feats": 1, "pub_api_path": 1, "ccf_id": 1}) + service_api = mycol.find_one(my_query, {"_id": 0, + "api_name": 1, + "api_id": 1, + "aef_profiles": 1, + "description": 1, + "supported_features": 1, + "shareable_info": 1, + "service_api_category": 1, + "api_supp_feats": 1, + "pub_api_path": 1, + "ccf_id": 1}) if service_api is None: current_app.logger.error(service_api_not_found_message) - return not_found_error(detail=service_api_not_found_message, cause="No Service with specific credentials exists") + return not_found_error( + detail=service_api_not_found_message, + cause="No Service with specific credentials exists") my_service_api = dict_to_camel_case(service_api) my_service_api = clean_empty(my_service_api) @@ -199,17 +218,16 @@ class PublishServiceOperations(Resource): current_app.logger.debug( "Removing api service with id: " + service_api_id) - result = self.__check_apf(apf_id) - - if result != None: - return result my_query = {'apf_id': apf_id, 'api_id': service_api_id} - serviceapidescription_dict = mycol.find_one(my_query, {"_id": 0, "onboarding_date": 0, "apf_id": 0}) + serviceapidescription_dict = mycol.find_one( + my_query, {"_id": 0, "onboarding_date": 0, "apf_id": 0}) if serviceapidescription_dict is None: current_app.logger.error(service_api_not_found_message) - return not_found_error(detail="Service API not existing", cause="Service API id not found") + return not_found_error( + detail="Service API not existing", + cause="Service API id not found") mycol.delete_one(my_query) @@ -218,10 +236,12 @@ class PublishServiceOperations(Resource): current_app.logger.debug("Removed service from database") out = "The service matching api_id " + service_api_id + " was deleted." res = make_response(out, status=204) - serviceapidescription= clean_empty(dict_to_camel_case(serviceapidescription_dict)) + serviceapidescription = clean_empty( + dict_to_camel_case(serviceapidescription_dict)) if res.status_code == 204: is_supported = serviceapidescription.get("supportedFeatures") and \ - ServiceAPIDescription.return_supp_feat_dict(serviceapidescription["supportedFeatures"]).get("ApiStatusMonitoring") + ServiceAPIDescription.return_supp_feat_dict( + serviceapidescription["supportedFeatures"]).get("ApiStatusMonitoring") if is_supported: current_app.logger.info("Service unavailable") @@ -232,7 +252,8 @@ class PublishServiceOperations(Resource): ).send_event() else: - status_message = "Service available" if serviceapidescription.get("supportedFeatures") is None else "Service unavailable" + status_message = "Service available" if serviceapidescription.get( + "supportedFeatures") is None else "Service unavailable" current_app.logger.info(status_message) RedisEvent( "SERVICE_API_UNAVAILABLE", @@ -247,7 +268,9 @@ class PublishServiceOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - def update_serviceapidescription(self, service_api_id, apf_id, service_api_description): + def update_serviceapidescription(self, + service_api_id, apf_id, + service_api_description): mycol = self.db.get_col_by_name(self.db.service_api_descriptions) @@ -256,11 +279,6 @@ class PublishServiceOperations(Resource): current_app.logger.debug( "Updating service api with id: " + service_api_id) - result = self.__check_apf(apf_id) - - if result != None: - return result - my_query = {'apf_id': apf_id, 'api_id': service_api_id} serviceapidescription = mycol.find_one(my_query) @@ -271,8 +289,21 @@ class PublishServiceOperations(Resource): service_api_description = service_api_description.to_dict() service_api_description = clean_empty(service_api_description) - result = mycol.find_one_and_update(serviceapidescription, {"$set": service_api_description}, projection={"_id": 0, "api_name": 1, "api_id": 1, "aef_profiles": 1, "description": 1, - "supported_features": 1, "shareable_info": 1, "service_api_category": 1, "api_supp_feats": 1, "pub_api_path": 1, "ccf_id": 1}, return_document=ReturnDocument.AFTER, upsert=False) + result = mycol.find_one_and_update( + serviceapidescription, + {"$set": service_api_description}, + projection={"_id": 0, + "api_name": 1, + "api_id": 1, + "aef_profiles": 1, + "description": 1, + "supported_features": 1, + "shareable_info": 1, + "service_api_category": 1, + "api_supp_feats": 1, + "pub_api_path": 1, + "ccf_id": 1}, + return_document=ReturnDocument.AFTER, upsert=False) result = clean_empty(result) @@ -282,40 +313,46 @@ class PublishServiceOperations(Resource): response = make_response( object=service_api_description_updated, status=200) - - service_api_description = ServiceAPIDescription.from_dict(json.dumps(service_api_description_updated, cls=CustomJSONEncoder)) + + service_api_description = ServiceAPIDescription.from_dict( + json.dumps(service_api_description_updated, cls=CustomJSONEncoder)) if response.status_code == 200: RedisEvent("SERVICE_API_UPDATE", ["serviceAPIDescriptions"], [[ service_api_description_updated]]).send_event() - if service_api_description.api_status is None or len(service_api_description.api_status.aef_ids) > 0: + if service_api_description.api_status is None or len(service_api_description.api_status.aef_ids) > 0: if service_api_description.supported_features is not None: if service_api_description.return_supp_feat_dict(service_api_description.supported_features)["ApiStatusMonitoring"]: current_app.logger.info(f"Service available") - RedisEvent("SERVICE_API_AVAILABLE", ["serviceAPIDescriptions", "apiIds"], - [[service_api_description],[str(service_api_id)]]).send_event() + RedisEvent("SERVICE_API_AVAILABLE", + ["serviceAPIDescriptions", "apiIds"], + [[service_api_description], [str(service_api_id)]]).send_event() else: current_app.logger.info("Service available") - RedisEvent("SERVICE_API_AVAILABLE", ["apiIds"], - [str(service_api_id)]).send_event() + RedisEvent("SERVICE_API_AVAILABLE", + ["apiIds"], + [str(service_api_id)]).send_event() else: - current_app.logger.info("Service available") - RedisEvent("SERVICE_API_AVAILABLE", ["apiIds"], - [str(service_api_id)]).send_event() + current_app.logger.info("Service available") + RedisEvent("SERVICE_API_AVAILABLE", + ["apiIds"], + [str(service_api_id)]).send_event() else: if service_api_description.supported_features is not None: if service_api_description.return_supp_feat_dict(service_api_description.supported_features)["ApiStatusMonitoring"]: current_app.logger.info(f"Service unavailable") - RedisEvent("SERVICE_API_UNAVAILABLE", ["serviceAPIDescriptions", "apiIds"], - [[service_api_description],[str(service_api_id)]]).send_event() + RedisEvent("SERVICE_API_UNAVAILABLE", + ["serviceAPIDescriptions", "apiIds"], + [[service_api_description], [str(service_api_id)]]).send_event() else: current_app.logger.info("Service available") - RedisEvent("SERVICE_API_UNAVAILABLE", ["apiIds"], - [str(service_api_id)]).send_event() + RedisEvent("SERVICE_API_UNAVAILABLE", + ["apiIds"], + [str(service_api_id)]).send_event() else: - current_app.logger.info("Service available") - RedisEvent("SERVICE_API_UNAVAILABLE", ["apiIds"], - [str(service_api_id)]).send_event() - + current_app.logger.info("Service available") + RedisEvent("SERVICE_API_UNAVAILABLE", + ["apiIds"], + [str(service_api_id)]).send_event() return response diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/validate_user.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/validate_user.py index e73cf8a8c856a064ecbb6b31536521b3bbcca026..9d0c69ca88f765d40e48b353b74f9d1cf9eadf76 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/validate_user.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/validate_user.py @@ -9,21 +9,39 @@ from ..util import serialize_clean_camel_case class ControlAccess(Resource): - def validate_user_cert(self, apf_id, service_id, cert_signature): + def validate_user_cert(self, apf_id, cert_signature, service_id=None): cert_col = self.db.get_col_by_name(self.db.certs_col) try: - my_query = {'id':apf_id} + my_query = {'id': apf_id} cert_entry = cert_col.find_one(my_query) if cert_entry is not None: - if cert_entry["cert_signature"] != cert_signature or "services" not in cert_entry["resources"] or service_id not in cert_entry["resources"]["services"]: - prob = ProblemDetails(title="Unauthorized", detail="User not authorized", cause="You are not the owner of this resource") + is_user_owner = True + if cert_entry["cert_signature"] != cert_signature: + is_user_owner = False + elif service_id: + if "services" not in cert_entry["resources"]: + is_user_owner = False + elif cert_entry.get("resources") and cert_entry["resources"].get("services"): + if service_id not in cert_entry["resources"].get("services"): + is_user_owner = False + if is_user_owner == False: + current_app.logger.info("STEP3") + prob = ProblemDetails( + title="Unauthorized", + detail="User not authorized", + cause="You are not the owner of this resource") + current_app.logger.info("STEP4") prob = serialize_clean_camel_case(prob) - return Response(json.dumps(prob, cls=CustomJSONEncoder), status=401, mimetype="application/json") + current_app.logger.info("STEP5") + return Response( + json.dumps(prob, cls=CustomJSONEncoder), + status=401, + mimetype="application/json") except Exception as e: exception = "An exception occurred in validate apf" current_app.logger.error(exception + "::" + str(e)) - return internal_server_error(detail=exception, cause=str(e)) \ No newline at end of file + return internal_server_error(detail=exception, cause=str(e)) diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/db/db.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/db/db.py index 643dda4f7a96c4e663d6aac3fd956ba0797bb39b..3885c5894ec0dde63d0e5f0593a9d35270f5cb22 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/db/db.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/db/db.py @@ -13,6 +13,13 @@ if monitoring_value == "true": class MongoDatabse(): + # _instance = None + + # def __new__(cls): + # if cls._instance is None: + # cls._instance = super(MongoDatabse, cls).__new__(cls) + # return cls._instance + def __init__(self): self.config = Config().get_config() self.db = self.__connect() @@ -20,7 +27,6 @@ class MongoDatabse(): self.capif_provider_col = self.config['mongo']['capif_provider_col'] self.certs_col = self.config['mongo']['certs_col'] - def get_col_by_name(self, name): return self.db[name].with_options(codec_options=CodecOptions(tz_aware=True)) diff --git a/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py b/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py index d56b02c71619bc920ad8acb1095699bbf518f3d3..eec268a656a32e7138817824f555074ccc214a96 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py @@ -1,15 +1,6 @@ -import connexion -from typing import Dict -from typing import Tuple -from typing import Union - -from capif_security.models.access_token_err import AccessTokenErr # noqa: E501 -from capif_security.models.access_token_rsp import AccessTokenRsp # noqa: E501 -from capif_security.models.problem_details import ProblemDetails # noqa: E501 from capif_security.models.res_owner_id import ResOwnerId # noqa: E501 from capif_security.models.security_notification import SecurityNotification # noqa: E501 from capif_security.models.service_security import ServiceSecurity # noqa: E501 -from capif_security import util from cryptography import x509 from cryptography.hazmat.backends import default_backend @@ -20,13 +11,16 @@ from ..core.publisher import Publisher from ..core.servicesecurity import SecurityOperations -from flask import Response, request, current_app +from flask import request, current_app + +from ..core.redis_internal_event import RedisInternalEvent service_security_ops = SecurityOperations() publish_ops = Publisher() valid_user = ControlAccess() + def cert_validation(): def _cert_validation(f): @wraps(f) @@ -36,17 +30,21 @@ def cert_validation(): cert_tmp = request.headers['X-Ssl-Client-Cert'] cert_raw = cert_tmp.replace('\t', '') - cert = x509.load_pem_x509_certificate(str.encode(cert_raw), default_backend()) + cert = x509.load_pem_x509_certificate( + str.encode(cert_raw), default_backend()) - cn = cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value.strip() + cn = cert.subject.get_attributes_for_oid( + x509.OID_COMMON_NAME)[0].value.strip() current_app.logger.info(f"CN: {cn}") - if cn != "superadmin" and "AEF" not in cn : + if cn != "superadmin" and "AEF" not in cn: cert_signature = cert.signature.hex() if "securityId" in args: - result = valid_user.validate_user_cert(args["securityId"], cert_signature) + result = valid_user.validate_user_cert( + args["securityId"], cert_signature) else: - result = valid_user.validate_user_cert(args["apiInvokerId"], cert_signature) + result = valid_user.validate_user_cert( + args["apiInvokerId"], cert_signature) if result is not None: return result @@ -56,6 +54,7 @@ def cert_validation(): return __cert_validation return _cert_validation + @cert_validation() def securities_security_id_token_post(security_id, body): # noqa: E501 """securities_security_id_token_post @@ -84,7 +83,7 @@ def securities_security_id_token_post(security_id, body): # noqa: E501 current_app.logger.info("Creating security token") if request.is_json: res_owner_id = ResOwnerId.from_dict(request.get_json()) # noqa: E501 - + # body={"security_id": security_id, # "grant_type": grant_type, # "client_id": client_id, @@ -95,12 +94,12 @@ def securities_security_id_token_post(security_id, body): # noqa: E501 # "redirect_uri": redirect_uri # } current_app.logger.debug(body) - - res = service_security_ops.return_token(security_id, body) + res = service_security_ops.return_token(security_id, body) return res + @cert_validation() def trusted_invokers_api_invoker_id_delete(api_invoker_id): # noqa: E501 """trusted_invokers_api_invoker_id_delete @@ -115,6 +114,7 @@ def trusted_invokers_api_invoker_id_delete(api_invoker_id): # noqa: E501 current_app.logger.info("Removing security context") return service_security_ops.delete_servicesecurity(api_invoker_id) + @cert_validation() def trusted_invokers_api_invoker_id_delete_post(api_invoker_id, body): # noqa: E501 """trusted_invokers_api_invoker_id_delete_post @@ -134,9 +134,10 @@ def trusted_invokers_api_invoker_id_delete_post(api_invoker_id, body): # noqa: current_app.logger.info("Revoking permissions") res = service_security_ops.revoke_api_authorization(api_invoker_id, body) - + return res + @cert_validation() def trusted_invokers_api_invoker_id_get(api_invoker_id, authentication_info=None, authorization_info=None): # noqa: E501 """trusted_invokers_api_invoker_id_get @@ -153,10 +154,12 @@ def trusted_invokers_api_invoker_id_get(api_invoker_id, authentication_info=None :rtype: Union[ServiceSecurity, Tuple[ServiceSecurity, int], Tuple[ServiceSecurity, int, Dict[str, str]] """ current_app.logger.info("Obtaining security context") - res = service_security_ops.get_servicesecurity(api_invoker_id, authentication_info, authorization_info) + res = service_security_ops.get_servicesecurity( + api_invoker_id, authentication_info, authorization_info) return res + @cert_validation() def trusted_invokers_api_invoker_id_put(api_invoker_id, body): # noqa: E501 """trusted_invokers_api_invoker_id_put @@ -179,10 +182,16 @@ def trusted_invokers_api_invoker_id_put(api_invoker_id, body): # noqa: E501 if res.status_code == 201: for service_instance in body.security_info: if service_instance.api_id is not None: - publish_ops.publish_message("internal-messages", "security-context-created:"+api_invoker_id+":"+service_instance.api_id ) + RedisInternalEvent("SECURITY-CONTEXT-CREATED", + "securityIds", + { + "api_invoker_id": api_invoker_id, + "api_id": service_instance.api_id + }).send_event() return res + @cert_validation() def trusted_invokers_api_invoker_id_update_post(api_invoker_id, body): # noqa: E501 """trusted_invokers_api_invoker_id_update_post diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py b/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py index 43445583236a5743ea1475079a3c329bb0788787..5a90b3eea15655cec2432af72d4320cd534e1487 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py @@ -2,6 +2,7 @@ import redis from .internal_security_ops import InternalSecurityOps from flask import current_app +import json class Subscriber(): @@ -16,8 +17,15 @@ class Subscriber(): current_app.logger.info("Listening security context messages") for raw_message in self.p.listen(): if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": - message, *ids = raw_message["data"].decode('utf-8').split(":") - if message == "invoker-removed" and len(ids) > 0: - self.security_ops.delete_intern_servicesecurity(ids[0]) - if message == "provider-removed" or message == "service-removed" and len(ids) > 0: - self.security_ops.update_intern_servicesecurity(ids[0]) + internal_redis_event = json.loads( + raw_message["data"].decode('utf-8')) + if internal_redis_event.get('event') == "INVOKER-REMOVED": + api_invoker_id = internal_redis_event.get( + 'information', {"api_invoker_id": None}).get('api_invoker_id') + if api_invoker_id is not None: + self.security_ops.delete_intern_servicesecurity(api_invoker_id) + elif internal_redis_event.get('event') == "PROVIDER-REMOVED": + aef_ids = internal_redis_event.get( + 'information', {"aef_ids": []}).get("aef_ids") + if len(aef_ids) > 0: + self.security_ops.update_intern_servicesecurity(aef_ids) diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/internal_security_ops.py b/services/TS29222_CAPIF_Security_API/capif_security/core/internal_security_ops.py index d1b28d5938fa8f7c1d2c5d2bf9d4a3a9b3c68fda..1f0871017571b7967291e424822aee522192fb3e 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/internal_security_ops.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/internal_security_ops.py @@ -1,6 +1,7 @@ from .resources import Resource + class InternalSecurityOps(Resource): def delete_intern_servicesecurity(self, api_invoker_id): @@ -9,13 +10,16 @@ class InternalSecurityOps(Resource): my_query = {'api_invoker_id': api_invoker_id} mycol.delete_many(my_query) - def update_intern_servicesecurity(self, id): + def update_intern_servicesecurity(self, ids): security_col = self.db.get_col_by_name(self.db.security_info) - - security_contexts = security_col.find({"$or":[{"security_info.aef_id":id}, {"security_info.api_id":id}]}) - - for security_context in security_contexts: - new_security_info = [info for info in security_context["security_info"] if info["aef_id"]!=id and info["api_id"] != id] - security_context["security_info"] = new_security_info - security_col.update_one({'api_invoker_id':security_context["api_invoker_id"]}, {"$set":security_context}) + for id in ids: + security_contexts = security_col.find( + {"$or": [{"security_info.aef_id": id}, {"security_info.api_id": id}]}) + + for security_context in security_contexts: + new_security_info = [info for info in security_context["security_info"] + if info["aef_id"] != id and info["api_id"] != id] + security_context["security_info"] = new_security_info + security_col.update_one({'api_invoker_id': security_context["api_invoker_id"]}, { + "$set": security_context}) diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/redis_internal_event.py b/services/TS29222_CAPIF_Security_API/capif_security/core/redis_internal_event.py new file mode 100644 index 0000000000000000000000000000000000000000..faa5ea12cb985b96b3cf8dfb7aaba6136b39e01e --- /dev/null +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/redis_internal_event.py @@ -0,0 +1,35 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisInternalEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.INTERNAL_MESSAGES = [ + 'INVOKER-REMOVED', + 'PROVIDER-REMOVED', + 'SECURITY-CONTEXT-CREATED', + 'SECURITY-CONTEXT-REMOVED', + 'create-acl', + 'remove-acl', + ] + if event not in self.INTERNAL_MESSAGES: + raise Exception( + "Internal Message (" + event + ") is not on INTERNAL_MESSAGES enum (" + ','.join(self.INTERNAL_MESSAGES) + ")") + self.redis_event = { + "event": event + } + if event_detail_key is not None and information is not None: + self.redis_event['key'] = event_detail_key + self.redis_event['information'] = information + + def to_string(self): + return json.dumps(self.redis_event, cls=JSONEncoder) + + def send_event(self): + publisher_ops.publish_message("internal-messages", self.to_string()) + + def __call__(self): + return self.redis_event diff --git a/services/run_capif_tests.sh b/services/run_capif_tests.sh index 1ad4d973112a0daf16fe0dbc6f26d45062d16d4c..65749bf3f9514fae2ead580962777a1292a0cf76 100755 --- a/services/run_capif_tests.sh +++ b/services/run_capif_tests.sh @@ -18,8 +18,7 @@ CAPIF_HTTPS_PORT=443 # VAULT access configuration CAPIF_VAULT=vault CAPIF_VAULT_PORT=8200 -CAPIF_VAULT_TOKEN=read-ca-token - +CAPIF_VAULT_TOKEN=dev-only-token MOCK_SERVER_URL=http://mock-server:9100 NOTIFICATION_DESTINATION_URL=http://mock-server:9100 diff --git a/tests/features/CAPIF Api Provider Management/capif_api_provider_management.robot b/tests/features/CAPIF Api Provider Management/capif_api_provider_management.robot index 9e4d81e29e2f5c96176e457995e572757053d0ea..bda73735c2bd023f1abc3bbfba4c73e40eff31c4 100644 --- a/tests/features/CAPIF Api Provider Management/capif_api_provider_management.robot +++ b/tests/features/CAPIF Api Provider Management/capif_api_provider_management.robot @@ -19,7 +19,7 @@ Register Api Provider [Tags] capif_api_provider_management-1 # Register Provider User An create Certificates for each function ${register_user_info}= Register User At Jwt Auth Provider - ... username=${PROVIDER_USERNAME} role=${PROVIDER_ROLE} + ... username=${PROVIDER_USERNAME} # Create provider Registration Body ${apf_func_details}= Create Api Provider Function Details diff --git a/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot b/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot index 5df5c4cf29be197ab6f2eed2653e41fb0468648d..8808e7deb5efaabfbf900bc55f73dd29e179548a 100644 --- a/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot +++ b/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot @@ -16,7 +16,7 @@ ${SERVICE_API_ID_NOT_VALID} not-valid *** Test Cases *** Publish API by Authorised API Publisher - [Tags] capif_api_publish_service-1 smoke + [Tags] capif_api_publish_service-1 smoke # Register APF ${register_user_info}= Provider Default Registration @@ -284,10 +284,10 @@ Delete API Published by Authorised apfId with valid serviceApiId ... verify=ca.crt ... username=${APF_PROVIDER_USERNAME} - Check Response Variable Type And Values ${resp} 401 ProblemDetails - ... title=Unauthorized - ... detail=User not authorized - ... cause=You are not the owner of this resource + Check Response Variable Type And Values ${resp} 404 ProblemDetails + ... title=Not Found + ... detail=Service API not found + ... cause=No Service with specific credentials exists Delete APIs Published by Authorised apfId with invalid serviceApiId [Tags] capif_api_publish_service-12 @@ -324,3 +324,72 @@ Delete APIs Published by NON Authorised apfId ... status=401 ... detail=User not authorized ... cause=Certificate not authorized + +Check Two Published APIs with different APFs are removed when Provider is deleted + [Tags] capif_api_publish_service-14 + # Register APF with 2 APF roles + ${register_user_info}= Provider Default Registration total_apf_roles=2 + + # Publish APIs with both APFs + ${service_api_description_published_1} ${resource_url} ${request_body}= Publish Service Api + ... ${register_user_info} + ... service_1 + ${service_api_description_published_2} ${resource_url} ${request_body}= Publish Service Api + ... ${register_user_info} + ... service_2 + ... apf_username=${APF_PROVIDER_USERNAME}_1 + + # Store apiId1 + ${serviceApiId1}= Set Variable ${service_api_description_published_1['apiId']} + ${serviceApiId2}= Set Variable ${service_api_description_published_2['apiId']} + + # Retrieve Service1 + ${resp}= Get Request Capif + ... /published-apis/v1/${register_user_info['apf_id']}/service-apis/${serviceApiId1} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${APF_PROVIDER_USERNAME} + + Check Response Variable Type And Values ${resp} 200 ServiceAPIDescription + Dictionaries Should Be Equal ${resp.json()} ${service_api_description_published_1} + + # Retrieve Service2 + ${resp}= Get Request Capif + ... /published-apis/v1/${register_user_info['apf_roles']['${APF_PROVIDER_USERNAME}_1']['apf_id']}/service-apis/${serviceApiId2} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${APF_PROVIDER_USERNAME}_1 + + Check Response Variable Type And Values ${resp} 200 ServiceAPIDescription + Dictionaries Should Be Equal ${resp.json()} ${service_api_description_published_2} + + # Get all services present at CCF + ${services_present_on_ccf_after_publish}= Get Number Of Services + + # Delete Provider using AMF cert + ${resp}= Delete Request Capif + ... ${register_user_info['resource_url'].path} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AMF_PROVIDER_USERNAME} + + Call Method ${CAPIF_USERS} remove_capif_users_entry ${register_user_info['resource_url'].path} + + ${services_present_on_ccf_after_delete_provider}= Get Number Of Services + + ${services_removed}= Evaluate ${services_present_on_ccf_after_publish} - ${services_present_on_ccf_after_delete_provider} + + Run Keyword And Continue On Failure Should Be Equal "${services_removed}" "2" msg=Not all services removed after delete provider (removed) vs (expected) + + # Remove service API by superadmin + ${resp}= Delete Request Capif + ... /published-apis/v1/${register_user_info['apf_roles']['${APF_PROVIDER_USERNAME}_1']['apf_id']}/service-apis/${serviceApiId2} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${SUPERADMIN_USERNAME} + + ${services_present_on_ccf_after_provider_deletion_superadmin}= Get Number Of Services + + ${services_removed}= Evaluate ${services_present_on_ccf_after_publish} - ${services_present_on_ccf_after_provider_deletion_superadmin} + + Run Keyword And Continue On Failure Should Be Equal "${services_removed}" "2" msg=Not all services removed after delete provider (removed) vs (expected) diff --git a/tests/features/CAPIF Security Api/capif_security_api.robot b/tests/features/CAPIF Security Api/capif_security_api.robot index eb2d725a3aaf9fc0f55095de2deeab6ada4f5164..85b26ee335bc29def3b8054701dfc42dfaf121f5 100644 --- a/tests/features/CAPIF Security Api/capif_security_api.robot +++ b/tests/features/CAPIF Security Api/capif_security_api.robot @@ -835,7 +835,6 @@ Retrieve access token with invalid client_id Retrieve access token with unsupported grant_type [Tags] capif_security_api-24 - Skip Test ${TEST_NAME} is not currently supported by CAPIF # Default Invoker Registration and Onboarding # Register APF ${register_user_info_provider}= Provider Default Registration @@ -890,9 +889,10 @@ Retrieve access token with unsupported grant_type Check Response Variable Type And Values ... ${resp} ... 400 - ... AccessTokenErr - ... error=unsupported_grant_type - ... error_description=Invalid value for `grant_type` \\(${grant_type}\\), must be one of \\['client_credentials'\\] - 'grant_type' + ... ProblemDetails + ... title=Bad Request + ... detail='not_valid' is not one of \\['client_credentials', 'authorization_code'\\] - 'grant_type' + ... status=400 Retrieve access token with invalid scope [Tags] capif_security_api-25 diff --git a/tests/features/__init__.robot b/tests/features/__init__.robot index 19e0fdb7e68b094801d11ec7f563450fa5029a0b..3ac73ab8898640e7a1040fe8d9603d55b30fa3c6 100644 --- a/tests/features/__init__.robot +++ b/tests/features/__init__.robot @@ -45,6 +45,8 @@ Prepare environment END # Obtain ca root certificate Retrieve Ca Root + + Retrieve Superadmin Cert Reset Testing Environment @@ -54,3 +56,12 @@ Retrieve Ca Root Status Should Be 200 ${resp} Log ${resp.json()['data']['data']['ca']} Store In File ca.crt ${resp.json()['data']['data']['ca']} + +Retrieve Superadmin Cert + [Documentation] This keyword retrieve ca.root from CAPIF and store it at ca.crt in order to use at TLS communications + ${resp}= Obtain Superadmin Cert From Vault /v1/pki_int/sign/my-ca ${CAPIF_HTTP_VAULT_URL} + Status Should Be 200 ${resp} + Log Dictionary ${resp.json()} + Log ${resp.json()['data']['certificate']} + Store In File ${SUPERADMIN_USERNAME}.crt ${resp.json()['data']['certificate']} + diff --git a/tests/libraries/bodyRequests.py b/tests/libraries/bodyRequests.py index 5e451242c693f385bccea597db898a5f7c16f725..b458689720ee3fa5a1d981a9bd9cd7dca445774b 100644 --- a/tests/libraries/bodyRequests.py +++ b/tests/libraries/bodyRequests.py @@ -5,4 +5,4 @@ from api_publish_service.bodyRequests import * from api_events.bodyRequests import * from security_api.bodyRequests import * from api_provider_management.bodyRequests import * - +from vault_requests.bodyRequests import * diff --git a/tests/libraries/common/bodyRequests.py b/tests/libraries/common/bodyRequests.py index 1d4ec14f60759bfb221b686ba84df23b1386fa6c..c1c3061ab7b9455bbb3d99fb6a403996206b9026 100644 --- a/tests/libraries/common/bodyRequests.py +++ b/tests/libraries/common/bodyRequests.py @@ -1,4 +1,3 @@ -from operator import contains import re import json from xmlrpc.client import boolean @@ -116,6 +115,7 @@ def check_uri(input,rule): else: raise Exception(rule + " is not accomplish rfc3986 rule ("+input+")") + def check_regex(input, regex): matched = re.match(regex, input) is_match = bool(matched) @@ -123,3 +123,13 @@ def check_regex(input, regex): print("Regex match") else: raise Exception("Input(" + input + ") not match regex (" + regex + ")") + + +def vault_sign_superadmin_certificate_body(csr_request): + data = { + 'format': 'pem_bundle', + 'ttl': '43000h', + 'csr': csr_request, + 'common_name': "superadmin" + } + return data diff --git a/tests/libraries/helpers.py b/tests/libraries/helpers.py index c7806f15fa4dc5155495794e009ca8b782dbf06b..1b974f2d743368fe3379e6c3f58f3bda8248e229 100644 --- a/tests/libraries/helpers.py +++ b/tests/libraries/helpers.py @@ -1,13 +1,10 @@ -import requests import re -import pandas as pd from urllib.parse import urlparse from OpenSSL.crypto import (dump_certificate_request, dump_privatekey, PKey, TYPE_RSA, X509Req) from OpenSSL.SSL import FILETYPE_PEM import socket import copy -import json import pickle @@ -142,23 +139,45 @@ def create_scope(aef_id, api_name): return data + def read_dictionary(file_path): with open(file_path, 'rb') as fp: data = pickle.load(fp) print('Dictionary loaded') return data + def write_dictionary(file_path, data): with open(file_path, 'wb') as fp: pickle.dump(data, fp) print('dictionary saved successfully to file ' + file_path) + def filter_users_by_prefix_username(users, prefix): if prefix.strip() == "": raise Exception('prefix value must contain some value') - filtered_users=list() + filtered_users = list() for user in users: if user['username'].startswith(prefix): filtered_users.append(user['username']) return filtered_users + + +def sign_csr_body(username, public_key): + data = { + "csr": public_key.decode("utf-8"), + "mode": "client", + "filename": username + } + return data + + +def vault_sign_superadmin_certificate_body(csr_request): + data = { + "format": "pem_bundle", + "ttl": "43000h", + "csr": csr_request.decode("utf-8"), + "common_name": "superadmin" + } + return data diff --git a/tests/resources/common.resource b/tests/resources/common.resource index 0acd867808cd86832d3ae299cf5f5ada7d7b20d7..d5cc35eb293202e616eceb614aad8083d6089394 100644 --- a/tests/resources/common.resource +++ b/tests/resources/common.resource @@ -8,6 +8,7 @@ Resource /opt/robot-tests/tests/resources/common/basicRequests.robot *** Variables *** +${SUPERADMIN_USERNAME} ROBOT_TESTING_SUPERADMIN ${INVOKER_USERNAME} ROBOT_TESTING_INVOKER ${PROVIDER_USERNAME} ROBOT_TESTING_PROVIDER ${APF_PROVIDER_USERNAME} APF_${PROVIDER_USERNAME} diff --git a/tests/resources/common/basicRequests.robot b/tests/resources/common/basicRequests.robot index 838086f925a9d8310d3e00c55b652c036588b567..f9c1139dfe3dbf9e27c3023dafe810ad70a33a8e 100644 --- a/tests/resources/common/basicRequests.robot +++ b/tests/resources/common/basicRequests.robot @@ -6,7 +6,8 @@ Library Collections Library OperatingSystem Library XML Library Telnet -Library String +Library String + *** Variables *** @@ -85,7 +86,7 @@ Create Register Admin Session ## NEW REQUESTS TO REGISTER Post Request Admin Register - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${json}=${NONE} @@ -115,7 +116,7 @@ Post Request Admin Register RETURN ${resp} Get Request Admin Register - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${server}=${NONE} @@ -149,7 +150,7 @@ Delete User Admin Register Request # NEW REQUESTS END Post Request Capif - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${json}=${NONE} @@ -179,7 +180,7 @@ Post Request Capif RETURN ${resp} Get Request Capif - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${server}=${NONE} @@ -205,7 +206,7 @@ Get Request Capif RETURN ${resp} Get CA Vault - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${server}=${NONE} @@ -229,10 +230,39 @@ Get CA Vault ... verify=${verify} ... cert=${cert} RETURN ${resp} - RETURN ${response} + +Obtain Superadmin Cert From Vault + [Timeout] ${REQUESTS_TIMEOUT} + [Arguments] + ... ${endpoint} + ... ${server}=${NONE} + ... ${access_token}=${NONE} + ... ${auth}=${NONE} + ... ${verify}=${FALSE} + ... ${cert}=${NONE} + ... ${username}=${NONE} + + ${headers}= Create CAPIF Session ${server} ${access_token} vault_token=${CAPIF_VAULT_TOKEN} + + IF '${username}' != '${NONE}' + ${cert}= Set variable ${{ ('${username}.crt','${username}.key') }} + END + + ${csr_request}= Create User Csr ${SUPERADMIN_USERNAME} cn=superadmin + ${json}= Vault Sign Superadmin Certificate Body ${csr_request} + + ${resp}= Post On Session + ... apisession + ... ${endpoint} + ... json=${json} + ... headers=${headers} + ... expected_status=any + ... verify=${verify} + ... cert=${cert} + RETURN ${resp} Put Request Capif - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${json}=${NONE} @@ -261,7 +291,7 @@ Put Request Capif RETURN ${resp} Patch Request Capif - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${json}=${NONE} @@ -274,7 +304,7 @@ Patch Request Capif ${headers}= Create CAPIF Session ${server} ${access_token} - Set To Dictionary ${headers} Content-Type application/merge-patch+json + Set To Dictionary ${headers} Content-Type application/merge-patch+json IF '${username}' != '${NONE}' ${cert}= Set variable ${{ ('${username}.crt','${username}.key') }} @@ -292,7 +322,7 @@ Patch Request Capif RETURN ${resp} Delete Request Capif - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${server}=${NONE} @@ -367,19 +397,65 @@ Register User At Jwt Auth RETURN ${register_user_info} Register User At Jwt Auth Provider - [Arguments] ${username} ${role} ${password}=password ${description}=Testing + [Arguments] + ... ${username} + ... ${password}=password + ... ${description}=Testing + ... ${total_apf_roles}=1 + ... ${total_aef_roles}=1 + ... ${total_amf_roles}=1 + + ${apf_roles}= Create Dictionary + ${default_apf_username}= Set Variable APF_${username} + FOR ${index} IN RANGE ${total_apf_roles} + ${apf_username}= Set Variable ${default_apf_username}_${index} + IF ${index} == 0 + ${apf_username}= Set Variable ${default_apf_username} + END + ${apf_csr_request}= Create User Csr ${apf_username} apf + ${apf_role}= + ... Create Dictionary + ... username=${apf_username} + ... csr_request=${apf_csr_request} + ... role=APF + Set To Dictionary ${apf_roles} ${apf_username}=${apf_role} + END - ${apf_username}= Set Variable APF_${username} - ${aef_username}= Set Variable AEF_${username} - ${amf_username}= Set Variable AMF_${username} + ${aef_roles}= Create Dictionary + ${default_aef_username}= Set Variable AEF_${username} + FOR ${index} IN RANGE ${total_aef_roles} + ${aef_username}= Set Variable ${default_aef_username}_${index} + IF ${index} == 0 + ${aef_username}= Set Variable ${default_aef_username} + END + ${aef_csr_request}= Create User Csr ${aef_username} aef + ${aef_role}= + ... Create Dictionary + ... username=${aef_username} + ... csr_request=${aef_csr_request} + ... role=AEF + Set To Dictionary ${aef_roles} ${aef_username}=${aef_role} + END + + ${amf_roles}= Create Dictionary + ${default_amf_username}= Set Variable AMF_${username} + FOR ${index} IN RANGE ${total_amf_roles} + ${amf_username}= Set Variable ${default_amf_username}_${index} + IF ${index} == 0 + ${amf_username}= Set Variable ${default_amf_username} + END + ${amf_csr_request}= Create User Csr ${amf_username} amf + ${amf_role}= + ... Create Dictionary + ... username=${amf_username} + ... csr_request=${amf_csr_request} + ... role=AMF + Set To Dictionary ${amf_roles} ${amf_username}=${amf_role} + END # Create a certificate for each kind of role under provider ${csr_request}= Create User Csr ${username} provider - ${apf_csr_request}= Create User Csr ${apf_username} apf - ${aef_csr_request}= Create User Csr ${aef_username} aef - ${amf_csr_request}= Create User Csr ${amf_username} amf - # Register provider ${resp}= Create User At Register ... ${username} @@ -394,12 +470,15 @@ Register User At Jwt Auth Provider ${register_user_info}= Create Dictionary ... netappID=${resp.json()['uuid']} ... csr_request=${csr_request} - ... apf_csr_request=${apf_csr_request} - ... aef_csr_request=${aef_csr_request} - ... amf_csr_request=${amf_csr_request} - ... apf_username=${apf_username} - ... aef_username=${aef_username} - ... amf_username=${amf_username} + ... apf_username=${default_apf_username} + ... aef_username=${default_aef_username} + ... amf_username=${default_amf_username} + ... apf_csr_request=${apf_roles['${default_apf_username}']['csr_request']} + ... aef_csr_request=${aef_roles['${default_aef_username}']['csr_request']} + ... amf_csr_request=${amf_roles['${default_amf_username}']['csr_request']} + ... apf_roles=${apf_roles} + ... aef_roles=${aef_roles} + ... amf_roles=${amf_roles} ... &{resp.json()} ... &{get_auth_response} @@ -439,22 +518,23 @@ Delete User At Register [Documentation] (Administrator) This Keyword delete a user from register. [Arguments] ${username}=${NONE} ${uuid}=${NONE} ${user_uuid}= Set Variable ${uuid} - ${environment_users}= Set Variable ${TRUE} + ${environment_users}= Set Variable ${TRUE} IF "${username}" != "${NONE}" ${user_uuid}= Call Method ${CAPIF_USERS} get_user_uuid ${username} END - IF "${user_uuid}" == "${NONE}" - ${user_uuid}= Get User Uuid At Register ${username} - ${environment_users}= Set Variable ${FALSE} + IF "${user_uuid}" == "${NONE}" + ${user_uuid}= Get User Uuid At Register ${username} + ${environment_users}= Set Variable ${FALSE} END - ${resp}= Delete User Admin Register Request ${user_uuid} + ${resp}= Delete User Admin Register Request ${user_uuid} Should Be Equal As Strings ${resp.status_code} 204 + q - IF ${environment_users} + IF ${environment_users} Call Method ${CAPIF_USERS} remove_register_users_entry ${user_uuid} END @@ -473,23 +553,22 @@ Get List of Users At Register Get User Uuid At Register [Documentation] (Administrator) This Keyword retrieve a list of users and search uuid of user passed by parameters [Arguments] ${username} - ${users}= Get List of Users At Register + ${users}= Get List of Users At Register # Find the first user with username indicated ${user_uuid}= Set Variable &{EMPTY} - FOR ${user} IN @{users} - IF "${user['username']}" == "${username}" - ${user_uuid}= Set Variable ${user['uuid']} + FOR ${user} IN @{users} + IF "${user['username']}" == "${username}" + ${user_uuid}= Set Variable ${user['uuid']} BREAK END END - IF "${user_uuid}" == "${EMPTY}" + IF "${user_uuid}" == "${EMPTY}" Log ${username} not found in Register END - - RETURN ${user_uuid} + RETURN ${user_uuid} Get Auth For User [Documentation] (User) This Keyword retrieve token to be used by user towards first interaction with CCF. @@ -567,7 +646,7 @@ Remove Resource ... verify=ca.crt ... username=${management_cert} - Run Keyword and Continue On Failure Status Should Be 204 ${resp} + Run Keyword and Continue On Failure Status Should Be 204 ${resp} Delete User At Register username=${username} @@ -611,20 +690,16 @@ Invoker Default Onboarding Provider Registration [Arguments] ${register_user_info} + ${api_prov_funcs}= Create List + # Create provider Registration Body - ${apf_func_details}= Create Api Provider Function Details - ... ${register_user_info['apf_username']} - ... ${register_user_info['apf_csr_request']} - ... APF - ${aef_func_details}= Create Api Provider Function Details - ... ${register_user_info['aef_username']} - ... ${register_user_info['aef_csr_request']} - ... AEF - ${amf_func_details}= Create Api Provider Function Details - ... ${register_user_info['amf_username']} - ... ${register_user_info['amf_csr_request']} - ... AMF - ${api_prov_funcs}= Create List ${apf_func_details} ${aef_func_details} ${amf_func_details} + FOR ${key} ${value} IN &{register_user_info['apf_roles']} &{register_user_info['aef_roles']} &{register_user_info['amf_roles']} + ${func_details}= Create Api Provider Function Details + ... ${key} + ... ${value['csr_request']} + ... ${value['role']} + Append To List ${api_prov_funcs} ${func_details} + END ${request_body}= Create Api Provider Enrolment Details Body ... ${register_user_info['access_token']} @@ -647,12 +722,29 @@ Provider Registration FOR ${prov} IN @{resp.json()['apiProvFuncs']} Log Dictionary ${prov} Store In File ${prov['apiProvFuncInfo']}.crt ${prov['regInfo']['apiProvCert']} + Log Dictionary ${register_user_info} + Log ${register_user_info['apf_username']} IF "${prov['apiProvFuncRole']}" == "APF" - Set To Dictionary ${register_user_info} apf_id=${prov['apiProvFuncId']} + IF "${prov['apiProvFuncInfo']}" == "${register_user_info['apf_username']}" + Set To Dictionary ${register_user_info} apf_id=${prov['apiProvFuncId']} + END + Set To Dictionary + ... ${register_user_info['apf_roles']['${prov['apiProvFuncInfo']}']} + ... apf_id=${prov['apiProvFuncId']} ELSE IF "${prov['apiProvFuncRole']}" == "AEF" - Set To Dictionary ${register_user_info} aef_id=${prov['apiProvFuncId']} + IF "${prov['apiProvFuncInfo']}" == "${register_user_info['aef_username']}" + Set To Dictionary ${register_user_info} aef_id=${prov['apiProvFuncId']} + END + Set To Dictionary + ... ${register_user_info['aef_roles']['${prov['apiProvFuncInfo']}']} + ... aef_id=${prov['apiProvFuncId']} ELSE IF "${prov['apiProvFuncRole']}" == "AMF" - Set To Dictionary ${register_user_info} amf_id=${prov['apiProvFuncId']} + IF "${prov['apiProvFuncInfo']}" == "${register_user_info['amf_username']}" + Set To Dictionary ${register_user_info} amf_id=${prov['apiProvFuncId']} + END + Set To Dictionary + ... ${register_user_info['amf_roles']['${prov['apiProvFuncInfo']}']} + ... amf_id=${prov['apiProvFuncId']} ELSE Fail "${prov['apiProvFuncRole']} is not valid role" END @@ -674,10 +766,20 @@ Provider Registration RETURN ${register_user_info} Provider Default Registration - [Arguments] ${provider_username}=${PROVIDER_USERNAME} + [Arguments] + ... ${provider_username}=${PROVIDER_USERNAME} + ... ${total_apf_roles}=1 + ... ${total_aef_roles}=1 + ... ${total_amf_roles}=1 + ... ${apf_id}=${NONE} + ... ${apf_username}=${NONE} + # Register Provider ${register_user_info}= Register User At Jwt Auth Provider - ... username=${provider_username} role=${PROVIDER_ROLE} + ... username=${provider_username} + ... total_apf_roles=${total_apf_roles} + ... total_aef_roles=${total_aef_roles} + ... total_amf_roles=${total_amf_roles} ${register_user_info}= Provider Registration ${register_user_info} @@ -686,15 +788,34 @@ Provider Default Registration RETURN ${register_user_info} Publish Service Api - [Arguments] ${register_user_info_provider} ${service_name}=service_1 + [Arguments] + ... ${register_user_info_provider} + ... ${service_name}=service_1 + ... ${apf_id}=${NONE} + ... ${apf_username}=${NONE} + + ${apf_id_to_use}= Set Variable ${register_user_info_provider['apf_id']} + ${apf_username_to_use}= Set Variable ${register_user_info_provider['apf_username']} + IF "${apf_id}" != "${NONE}" and "${apf_id}" != "${register_user_info_provider['apf_id']}" + FOR ${apf_username} ${apf_role} IN &{register_user_info_provider['apf_roles']} + IF "${apf_role['apf_id']}" == "${apf_id}" + ${apf_id_to_use}= Set Variable ${apf_id} + ${apf_username_to_use}= Set Variable ${apf_username} + BREAK + END + END + ELSE IF "${apf_username}" != "${NONE}" and "${apf_username}" != "${register_user_info_provider['apf_username']}" + ${apf_id_to_use}= Set Variable ${register_user_info_provider['apf_roles']['${apf_username}']['apf_id']} + ${apf_username_to_use}= Set Variable ${apf_username} + END ${request_body}= Create Service Api Description ${service_name} ${register_user_info_provider['aef_id']} ${resp}= Post Request Capif - ... /published-apis/v1/${register_user_info_provider['apf_id']}/service-apis + ... /published-apis/v1/${apf_id_to_use}/service-apis ... json=${request_body} ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt - ... username=${register_user_info_provider['apf_username']} + ... username=${apf_username_to_use} Check Response Variable Type And Values ${resp} 201 ServiceAPIDescription Dictionary Should Contain Key ${resp.json()} apiId @@ -781,3 +902,14 @@ Create Security Context Between invoker and provider Check Response Variable Type And Values ${resp} 201 ServiceSecurity +Get Number Of Services + ${resp}= Get Request Capif + ... /helper/getServices + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${SUPERADMIN_USERNAME} + + Log Dictionary ${resp.json()} + ${size}= Get Length ${resp.json()['services']} + + RETURN ${size}