diff --git a/.gitignore b/.gitignore index 66e4e335e7817b3622c0e3d74ea3ad807702da9a..e19b88a4ceba1e3e4a81b97058120fd5b870d26d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ __pycache__/ *.crt *.zip *.srl +*.log services/nginx/certs/sign_req_body.json services/easy_rsa/certs/pki services/easy_rsa/certs/*EasyRSA* @@ -35,4 +36,4 @@ docs/testing_with_postman/package-lock.json results helm/capif/*.lock -helm/capif/charts \ No newline at end of file +helm/capif/charts diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/controllers/default_controller.py b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/controllers/default_controller.py index 27eb1c8fde859b355f543856af9f4857d36710bb..12c217cfd224b92362eb86e4713e47f5882f1bcc 100644 --- a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/controllers/default_controller.py +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/controllers/default_controller.py @@ -15,7 +15,6 @@ from ..core.publisher import Publisher from functools import wraps invoker_operations = InvokerManagementOperations() -publisher_ops = Publisher() valid_user = ControlAccess() @@ -59,11 +58,6 @@ def onboarded_invokers_onboarding_id_delete(onboarding_id): # noqa: E501 current_app.logger.info("Removing invoker") res = invoker_operations.remove_apiinvokerenrolmentdetail(onboarding_id) - if res.status_code == 204: - current_app.logger.info("Invoker Removed") - publisher_ops.publish_message("events", "API_INVOKER_OFFBOARDED") - publisher_ops.publish_message("internal-messages", f"invoker-removed:{onboarding_id}") - return res @cert_validation() @@ -84,10 +78,6 @@ def onboarded_invokers_onboarding_id_put(onboarding_id, body): # noqa: E501 body = APIInvokerEnrolmentDetails.from_dict(connexion.request.get_json()) # noqa: E501 res = invoker_operations.update_apiinvokerenrolmentdetail(onboarding_id,body) - if res.status_code == 200: - current_app.logger.info("Invoker Updated") - publisher_ops.publish_message("events", "API_INVOKER_UPDATED") - return res @@ -111,8 +101,5 @@ def onboarded_invokers_post(body): # noqa: E501 current_app.logger.info("Creating Invoker") res = invoker_operations.add_apiinvokerenrolmentdetail(body, username, uuid) - if res.status_code == 201: - current_app.logger.info("Invoker Created") - publisher_ops.publish_message("events", "API_INVOKER_ONBOARDED") return res 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 2d43af2e22ae37fe2be82f15ca4a7ff560553f61..3a8ec3ffe2787f220ac90e4ef7906a41a800789e 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 @@ -11,9 +11,10 @@ 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 .publisher import Publisher - - +publisher_ops = Publisher() class InvokerManagementOperations(Resource): def __check_api_invoker_id(self, api_invoker_id): @@ -93,6 +94,10 @@ class InvokerManagementOperations(Resource): res = make_response(object=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() return res # except Exception as e: @@ -130,6 +135,9 @@ class InvokerManagementOperations(Resource): current_app.logger.debug("Invoker Resource inserted in database") res = make_response(object=APIInvokerEnrolmentDetails().from_dict(dict_to_camel_case(result)), status=200) + if res.status_code == 200: + current_app.logger.info("Invoker Updated") + RedisEvent("API_INVOKER_UPDATED", "apiInvokerIds", [onboard_id]).send_event() return res except Exception as e: @@ -153,7 +161,12 @@ class InvokerManagementOperations(Resource): current_app.logger.debug("Invoker resource removed from database") current_app.logger.debug("Netapp offboarded sucessfuly") out = "The Netapp matching onboardingId " + onboard_id + " was offboarded." - return make_response(out, status=204) + 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}") + return res except Exception as e: exception = "An exception occurred in remove invoker" diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py new file mode 100644 index 0000000000000000000000000000000000000000..aadbdbb6b3116cf288648de41effdb12b9ca9143 --- /dev/null +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_event.py @@ -0,0 +1,41 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.EVENTS_ENUM = [ + 'SERVICE_API_AVAILABLE', + 'SERVICE_API_UNAVAILABLE', + 'SERVICE_API_UPDATE', + 'API_INVOKER_ONBOARDED', + 'API_INVOKER_OFFBOARDED', + 'SERVICE_API_INVOCATION_SUCCESS', + 'SERVICE_API_INVOCATION_FAILURE', + 'ACCESS_CONTROL_POLICY_UPDATE', + 'ACCESS_CONTROL_POLICY_UNAVAILABLE', + 'API_INVOKER_AUTHORIZATION_REVOKED', + 'API_INVOKER_UPDATED', + 'API_TOPOLOGY_HIDING_CREATED', + 'API_TOPOLOGY_HIDING_REVOKED'] + if event not in self.EVENTS_ENUM: + raise Exception( + "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") + self.redis_event = { + "event": event + } + if event_detail_key != None and information != 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("events", 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 6ba9043eb48f523d44fedab79a1510456d19b62c..399ac7457f971397966d7385f5ad2702babc338e 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 @@ -93,7 +93,7 @@ class ProviderManagementOperations(Resource): self.auth_manager.remove_auth_provider([apf_id[0], aef_id[0], amf_id[0]]) - self.publish_ops.publish_message("internal-messages", f"provider-removed:{aef_id[0]}:{apf_id[0]}") + 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: diff --git a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/accesscontrolpolicyapi.py b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/accesscontrolpolicyapi.py index fd14e80dc86e5754e79f66e251e2bda2212266b8..f644caef07f1ffb72d8ed4fc5492f37ebf97e14e 100644 --- a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/accesscontrolpolicyapi.py +++ b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/accesscontrolpolicyapi.py @@ -20,8 +20,8 @@ class accessControlPolicyApi(Resource): projection = {"_id":0} if api_invoker_id is not None: - query['apiInvokerPolicies.api_invoker_id'] = api_invoker_id - projection['apiInvokerPolicies.$'] = 1 + query['api_invoker_policies.api_invoker_id'] = api_invoker_id + projection['api_invoker_policies.$'] = 1 if supported_features is not None: current_app.logger.debug(f"SupportedFeatures present on query with value {supported_features}, but currently not used") @@ -37,8 +37,8 @@ class accessControlPolicyApi(Resource): current_app.logger.debug(policies) - api_invoker_policies = policies[0]['apiInvokerPolicies'] - current_app.logger.debug(f"apiinvokerPolicies: {api_invoker_policies}") + api_invoker_policies = policies[0]['api_invoker_policies'] + current_app.logger.debug(f"api_invoker_policies: {api_invoker_policies}") if not api_invoker_policies: current_app.logger.info(f"ACLs list is present but empty, then no ACLs found for the requested service: {service_api_id}, aef_id: {aef_id}, invoker: {api_invoker_id} and supportedFeatures: {supported_features}") #Not found error diff --git a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/internal_service_ops.py b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/internal_service_ops.py index e10986adc4d84cec60374c8a21ddb04c149971ad..26a7d53795a41899aa2ff4726a80685fcdc01db2 100644 --- a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/internal_service_ops.py +++ b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/internal_service_ops.py @@ -4,82 +4,105 @@ from .resources import Resource from ..models.api_invoker_policy import ApiInvokerPolicy from ..models.time_range_list import TimeRangeList from datetime import datetime, timedelta -from ..core.publisher import Publisher +from .redis_event import RedisEvent +from ..util import dict_to_camel_case, clean_empty + -publisher_ops = Publisher() class InternalServiceOps(Resource): - + def create_acl(self, invoker_id, service_id, aef_id): current_app.logger.info(f"Creating ACL for invoker: {invoker_id}") if "acls" not in self.db.db.list_collection_names(): self.db.db.create_collection("acls") - + mycol = self.db.get_col_by_name(self.db.acls) - res = mycol.find_one({"service_id": service_id, "aef_id":aef_id}, {"_id":0}) - + res = mycol.find_one( + {"service_id": service_id, "aef_id": aef_id}, {"_id": 0}) + if res: - current_app.logger.info(f"Adding invoker ACL for invoker {invoker_id}") - range_list = [TimeRangeList(datetime.utcnow(), datetime.utcnow()+timedelta(days=365))] - invoker_acl = ApiInvokerPolicy(invoker_id, current_app.config["invocations"]["total"], current_app.config["invocations"]["perSecond"], range_list) - r = mycol.find_one({"service_id": service_id, "aef_id":aef_id, "apiInvokerPolicies.api_invoker_id": invoker_id}, {"_id":0}) + current_app.logger.info( + f"Adding invoker ACL for invoker {invoker_id}") + range_list = [TimeRangeList( + datetime.utcnow(), datetime.utcnow()+timedelta(days=365))] + invoker_acl = ApiInvokerPolicy( + invoker_id, current_app.config["invocations"]["total"], current_app.config["invocations"]["perSecond"], range_list) + r = mycol.find_one({"service_id": service_id, "aef_id": aef_id, + "api_invoker_policies.api_invoker_id": invoker_id}, {"_id": 0}) if r is None: - mycol.update_one({"service_id": service_id, "aef_id":aef_id }, {"$push":{"apiInvokerPolicies":invoker_acl.to_dict()}}) + mycol.update_one({"service_id": service_id, "aef_id": aef_id}, { + "$push": {"api_invoker_policies": invoker_acl.to_dict()}}) else: - current_app.logger.info(f"Creating service ACLs for service: {service_id}") - range_list = [TimeRangeList(datetime.utcnow(), datetime.utcnow()+timedelta(days=365))] - invoker_acl = ApiInvokerPolicy(invoker_id, current_app.config["invocations"]["total"], current_app.config["invocations"]["perSecond"], range_list) - - + current_app.logger.info( + f"Creating service ACLs for service: {service_id}") + range_list = [TimeRangeList( + datetime.utcnow(), datetime.utcnow()+timedelta(days=365))] + invoker_acl = ApiInvokerPolicy( + invoker_id, current_app.config["invocations"]["total"], current_app.config["invocations"]["perSecond"], range_list) service_acls = { "service_id": service_id, "aef_id": aef_id, - "apiInvokerPolicies": [invoker_acl.to_dict()] + "api_invoker_policies": [invoker_acl.to_dict()] } - mycol.insert_one(service_acls) - publisher_ops.publish_message("events", "ACCESS_CONTROL_POLICY_UPDATE") - - current_app.logger.info(f"Invoker ACL added for invoker: {invoker_id} for service: {service_id}") - + result = mycol.insert_one(service_acls) + + inserted_service_acls=mycol.find_one({"_id": result.inserted_id}, {"_id": 0}) + current_app.logger.info(inserted_service_acls) + inserted_service_acls_camel=dict_to_camel_case(inserted_service_acls) + current_app.logger.info(inserted_service_acls_camel) + accCtrlPolListExt = { + "apiId": service_id, + "apiInvokerPolicies": inserted_service_acls_camel['apiInvokerPolicies'] + } + RedisEvent("ACCESS_CONTROL_POLICY_UPDATE", + "accCtrlPolList", accCtrlPolListExt).send_event() + + current_app.logger.info( + f"Invoker ACL added for invoker: {invoker_id} for service: {service_id}") + def remove_acl(self, invoker_id, service_id, aef_id): current_app.logger.info(f"Removing ACL for invoker: {invoker_id}") - + mycol = self.db.get_col_by_name(self.db.acls) - res = mycol.find_one({"service_id": service_id, "aef_id":aef_id}, {"_id":0}) - + res = mycol.find_one( + {"service_id": service_id, "aef_id": aef_id}, {"_id": 0}) + if res: - mycol.update_many({"service_id": service_id, "aef_id":aef_id}, - {"$pull":{ "apiInvokerPolicies": { "api_invoker_id": invoker_id }}} - ) + mycol.update_many({"service_id": service_id, "aef_id": aef_id}, + {"$pull": {"api_invoker_policies": { + "api_invoker_id": invoker_id}}} + ) else: - current_app.logger.info(f"Not found: {service_id} for api : {service_id}") - - publisher_ops.publish_message("events", "ACCESS_CONTROL_POLICY_UNAVAILABLE") - - current_app.logger.info(f"Invoker ACL removed for invoker: {invoker_id} for service: {service_id}") - + current_app.logger.info( + f"Not found: {service_id} for api : {service_id}") + + RedisEvent("ACCESS_CONTROL_POLICY_UNAVAILABLE").send_event() + + current_app.logger.info( + f"Invoker ACL removed for invoker: {invoker_id} for service: {service_id}") + def remove_invoker_acl(self, invoker_id): current_app.logger.info(f"Removing ACLs for invoker: {invoker_id}") mycol = self.db.get_col_by_name(self.db.acls) - - mycol.update_many({"apiInvokerPolicies.api_invoker_id": invoker_id}, - {"$pull":{ "apiInvokerPolicies": { "api_invoker_id": invoker_id }}} - ) - publisher_ops.publish_message("events", "ACCESS_CONTROL_POLICY_UNAVAILABLE") + + mycol.update_many({"api_invoker_policies.api_invoker_id": invoker_id}, + {"$pull": {"api_invoker_policies": { + "api_invoker_id": invoker_id}}} + ) + RedisEvent("ACCESS_CONTROL_POLICY_UNAVAILABLE").send_event() current_app.logger.info(f"ACLs for invoker: {invoker_id} removed") - + def remove_provider_acls(self, id): current_app.logger.info(f"Removing ACLs for provider/service: {id}") mycol = self.db.get_col_by_name(self.db.acls) - - mycol.delete_many({"$or":[{"service_id":id}, {"aef_id":id}]} - ) - publisher_ops.publish_message("events", "ACCESS_CONTROL_POLICY_UNAVAILABLE") - current_app.logger.info(f"ACLs for provider/service: {id} removed") \ No newline at end of file + + mycol.delete_many({"$or": [{"service_id": id}, {"aef_id": id}]}) + RedisEvent("ACCESS_CONTROL_POLICY_UNAVAILABLE").send_event() + current_app.logger.info(f"ACLs for provider/service: {id} removed") diff --git a/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/redis_event.py b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/redis_event.py new file mode 100644 index 0000000000000000000000000000000000000000..aadbdbb6b3116cf288648de41effdb12b9ca9143 --- /dev/null +++ b/services/TS29222_CAPIF_Access_Control_Policy_API/openapi_server/core/redis_event.py @@ -0,0 +1,41 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.EVENTS_ENUM = [ + 'SERVICE_API_AVAILABLE', + 'SERVICE_API_UNAVAILABLE', + 'SERVICE_API_UPDATE', + 'API_INVOKER_ONBOARDED', + 'API_INVOKER_OFFBOARDED', + 'SERVICE_API_INVOCATION_SUCCESS', + 'SERVICE_API_INVOCATION_FAILURE', + 'ACCESS_CONTROL_POLICY_UPDATE', + 'ACCESS_CONTROL_POLICY_UNAVAILABLE', + 'API_INVOKER_AUTHORIZATION_REVOKED', + 'API_INVOKER_UPDATED', + 'API_TOPOLOGY_HIDING_CREATED', + 'API_TOPOLOGY_HIDING_REVOKED'] + if event not in self.EVENTS_ENUM: + raise Exception( + "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") + self.redis_event = { + "event": event + } + if event_detail_key != None and information != 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("events", self.to_string()) + + def __call__(self): + return self.redis_event 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 0f9fa6ab3653ba97701c3f0e034809c69d6b5a08..ce6479afb5c3537246cb1b2192709c6e4ed83d8c 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 @@ -20,14 +20,18 @@ class Subscriber(): def listen(self): for raw_message in self.p.listen(): + 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") - self.notification.send_notifications(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, *invoker_id = raw_message["data"].decode('utf-8').split(":") - if message == "invoker-removed" and len(invoker_id)>0: - self.event_ops.delete_all_events(invoker_id[0]) + 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) diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/internal_event_ops.py b/services/TS29222_CAPIF_Events_API/capif_events/core/internal_event_ops.py index 3f4fc6a7274e9eddbfa51cf630f779d31bf18bfb..e49089a3756b4b3946bafa93b4138721e8a1be7b 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/internal_event_ops.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/internal_event_ops.py @@ -8,22 +8,23 @@ class InternalEventOperations(Resource): Resource.__init__(self) self.auth_manager = AuthManager() - def delete_all_events(self, subscriber_id): + def delete_all_events(self, subscriber_ids): - mycol = self.db.get_col_by_name(self.db.event_collection) - my_query = {'subscriber_id': subscriber_id} - mycol.delete_many(my_query) + for subscriber_id in subscriber_ids: + mycol = self.db.get_col_by_name(self.db.event_collection) + my_query = {'subscriber_id': subscriber_id} + mycol.delete_many(my_query) - current_app.logger.info(f"Removed events for this subscriber: {subscriber_id}") + current_app.logger.info(f"Removed events for this subscriber: {subscriber_id}") #We dont need remove all auth events, becase when invoker is removed, remove auth entry #self.auth_manager.remove_auth_all_event(subscriber_id) def get_event_subscriptions(self, event): + current_app.logger.info("get subscription from db") try: mycol = self.db.get_col_by_name(self.db.event_collection) - - query= {'events':event} + query={'events':{'$in':[event]}} subscriptions = mycol.find(query) if subscriptions is None: @@ -39,21 +40,3 @@ class InternalEventOperations(Resource): except Exception as e: current_app.logger.error("An exception occurred ::" + str(e)) return False - - # def get_acls(self, service_id): - # try: - # mycol = self.db.get_col_by_name(self.db.acls_col) - - # query= {'api_id': service_id} - # acls = mycol.find(query) - - # if acls is None: - # current_app.logger.error("Not found event subscriptions") - - # else: - - # return acls - - # except Exception as e: - # current_app.logger.error("An exception occurred ::" + str(e)) - # return False \ No newline at end of file diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py b/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py index a02f0abee8a4fa60a8405ea5175e20f3a33236c8..2229a7182fadb59adec89bac65565dc5affe4bea 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/notifications.py @@ -8,30 +8,34 @@ from ..encoder import JSONEncoder import sys import json from flask import current_app +import asyncio +import aiohttp class Notifications(): def __init__(self): self.events_ops = InternalEventOperations() - def send_notifications(self, event): - current_app.logger.info("Received event, sending notifications") - subscriptions = self.events_ops.get_event_subscriptions(event) - # message, *ids = event.split(":") - + def send_notifications(self, redis_event): try: + if redis_event.get('event', None) == None: + raise("Event value is not present on received event from REDIS") + + current_app.logger.info("Received event " + redis_event.get('event') + ", sending notifications") + subscriptions = self.events_ops.get_event_subscriptions(redis_event.get('event')) + current_app.logger.info(subscriptions) + for sub in subscriptions: url = sub["notification_destination"] - data = EventNotification(sub["subscription_id"], events=event) - # details = CAPIFEventDetail() - # if message == "ACCESS_CONTROL_POLICY_UPDATE": - # current_app.logger.info("event: ACCESS_CONTROL_POLICY_UPDATE") - # acls = self.events_ops.get_acls(ids[0]) - # details.acc_ctrl_pol_list = AccessControlPolicyListExt(api_id=acls['service_id'], api_invoker_policies=acls['apiInvokerPolicies']) + current_app.logger.debug(url) + event_detail=None + if redis_event.get('key', None) != None and redis_event.get('information', None) != None: + event_detail={redis_event.get('key'):redis_event.get('information')} + current_app.logger.debug(event_detail) + data = EventNotification(sub["subscription_id"], events=redis_event.get('event'), event_detail=event_detail) + current_app.logger.debug(json.dumps(data,cls=JSONEncoder)) - # data.event_detail=details - self.request_post(url, data) - #current_app.logger.info("notification sended") + asyncio.run(self.send(url, json.loads(json.dumps(data,cls=JSONEncoder)))) except Exception as e: current_app.logger.error("An exception occurred ::" + str(e)) @@ -40,4 +44,20 @@ class Notifications(): def request_post(self, url, data): headers = {'content-type': 'application/json'} return requests.post(url, json={'text': str(data.to_str())}, headers=headers) - + + async def send_request(self, url, data): + async with aiohttp.ClientSession() as session: + timeout = aiohttp.ClientTimeout(total=10) # Establecer timeout a 10 segundos + headers = {'content-type': 'application/json'} + async with session.post(url, json=data, timeout=timeout, headers=headers) as response: + return await response.text() + + async def send(self, url, data): + try: + response = await self.send_request(url, data) + current_app.logger.debug(response) + except asyncio.TimeoutError: + current_app.logger.error("Timeout: Request timeout") + except Exception as e: + current_app.logger.error("An exception occurred sending notification::" + str(e)) + return False diff --git a/services/TS29222_CAPIF_Events_API/requirements.txt b/services/TS29222_CAPIF_Events_API/requirements.txt index bbef950cda414a17f3ebb70ca8689ae35c121ca0..743f814afa711b0c66d3236a0875642335eedd99 100644 --- a/services/TS29222_CAPIF_Events_API/requirements.txt +++ b/services/TS29222_CAPIF_Events_API/requirements.txt @@ -21,3 +21,5 @@ rfc3987 redis flask_executor Flask-APScheduler +aiohttp==3.9.5 +async-timeout==4.0.3 diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py index dde0c649d29cbd53c51ba20119422551f415e031..00cf9b7d902663a8ef93e804fd5379e967af66a2 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py @@ -13,6 +13,11 @@ from ..util import dict_to_camel_case, clean_empty from .resources import Resource from .responses import bad_request_error, internal_server_error, forbidden_error, not_found_error, unauthorized_error, make_response from ..models.invocation_log import InvocationLog +# from .publisher import Publisher +from .redis_event import RedisEvent +import copy + +# publisher_ops = Publisher() class LoggingInvocationOperations(Resource): @@ -61,7 +66,7 @@ class LoggingInvocationOperations(Resource): return None def add_invocationlog(self, aef_id, invocationlog): - + mycol = self.db.get_col_by_name(self.db.invocation_logs) try: @@ -79,11 +84,29 @@ class LoggingInvocationOperations(Resource): return result current_app.logger.debug("Check service apis") + event=None + invocation_log_base=json.loads(json.dumps(invocationlog, cls=JSONEncoder)) + for log in invocationlog.logs: result = self.__check_service_apis(log.api_id, log.api_name) + current_app.logger.debug("Inside for loop.") if result is not None: return result + + if log.result: + current_app.logger.debug(log) + if int(log.result) >= 200 and int(log.result) < 300: + event="SERVICE_API_INVOCATION_SUCCESS" + else: + event="SERVICE_API_INVOCATION_FAILURE" + + current_app.logger.info(event) + invocation_log_base['logs']=[log] + invocationLogs=[invocation_log_base] + RedisEvent(event,"invocationLogs",invocationLogs).send_event() + + current_app.logger.debug("After log check") current_app.logger.debug("Check existing logs") my_query = {'aef_id': aef_id, 'api_invoker_id': invocationlog.api_invoker_id} diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/publisher.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/publisher.py new file mode 100644 index 0000000000000000000000000000000000000000..a15c0d90e8d00233317a6c1ab748b8ff150be80e --- /dev/null +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/publisher.py @@ -0,0 +1,11 @@ +import redis +import sys +from flask import current_app + +class Publisher(): + + def __init__(self): + self.r = redis.Redis(host='redis', port=6379, db=0) + + def publish_message(self, channel, message): + self.r.publish(channel, message) diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py new file mode 100644 index 0000000000000000000000000000000000000000..aadbdbb6b3116cf288648de41effdb12b9ca9143 --- /dev/null +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/redis_event.py @@ -0,0 +1,41 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.EVENTS_ENUM = [ + 'SERVICE_API_AVAILABLE', + 'SERVICE_API_UNAVAILABLE', + 'SERVICE_API_UPDATE', + 'API_INVOKER_ONBOARDED', + 'API_INVOKER_OFFBOARDED', + 'SERVICE_API_INVOCATION_SUCCESS', + 'SERVICE_API_INVOCATION_FAILURE', + 'ACCESS_CONTROL_POLICY_UPDATE', + 'ACCESS_CONTROL_POLICY_UNAVAILABLE', + 'API_INVOKER_AUTHORIZATION_REVOKED', + 'API_INVOKER_UPDATED', + 'API_TOPOLOGY_HIDING_CREATED', + 'API_TOPOLOGY_HIDING_REVOKED'] + if event not in self.EVENTS_ENUM: + raise Exception( + "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") + self.redis_event = { + "event": event + } + if event_detail_key != None and information != 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("events", self.to_string()) + + def __call__(self): + return self.redis_event diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/requirements.txt b/services/TS29222_CAPIF_Logging_API_Invocation_API/requirements.txt index 197399ff21107a2e3f2edc6519445c38b38de335..0dfa8b621257a6601b7645cf6620300ad22be18c 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/requirements.txt +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/requirements.txt @@ -6,6 +6,7 @@ Flask == 2.0.3 pymongo == 4.0.1 elasticsearch == 8.4.3 flask_jwt_extended == 4.4.4 +redis == 4.5.4 opentelemetry-instrumentation == 0.38b0 opentelemetry-instrumentation-flask == 0.38b0 opentelemetry-instrumentation-redis == 0.38b0 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 8fc2b6269ac2c0363ca96b3d3e76cdac7edeba01..dd43886f522f8c131e0a3c6e7ae569d8f5fde3f9 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 @@ -4,7 +4,6 @@ from ..core import serviceapidescriptions from ..core.serviceapidescriptions import PublishServiceOperations from ..core.publisher import Publisher -import json from flask import Response, request, current_app from flask_jwt_extended import jwt_required, get_jwt_identity from flask import current_app @@ -14,14 +13,12 @@ from cryptography import x509 from cryptography.hazmat.backends import default_backend from ..core.validate_user import ControlAccess from functools import wraps -import pymongo - service_operations = PublishServiceOperations() -publisher_ops = Publisher() valid_user = ControlAccess() + def cert_validation(): def _cert_validation(f): @wraps(f) @@ -31,13 +28,16 @@ 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() if cn != "superadmin": cert_signature = cert.signature.hex() - result = valid_user.validate_user_cert(args["apfId"], args["serviceApiId"], cert_signature) + result = valid_user.validate_user_cert( + args["apfId"], args["serviceApiId"], cert_signature) if result is not None: return result @@ -47,6 +47,7 @@ def cert_validation(): return __cert_validation return _cert_validation + def apf_id_service_apis_get(apf_id): # noqa: E501 """apf_id_service_apis_get @@ -82,12 +83,9 @@ def apf_id_service_apis_post(apf_id, body): # noqa: E501 res = service_operations.add_serviceapidescription(apf_id, body) - if res.status_code == 201: - current_app.logger.info("Service published") - publisher_ops.publish_message("events", "SERVICE_API_AVAILABLE") - return res + @cert_validation() def apf_id_service_apis_service_api_id_delete(service_api_id, apf_id): # noqa: E501 """apf_id_service_apis_service_api_id_delete @@ -103,15 +101,12 @@ def apf_id_service_apis_service_api_id_delete(service_api_id, apf_id): # noqa: """ current_app.logger.info("Removing service published") - res = service_operations.delete_serviceapidescription(service_api_id, apf_id) - - if res.status_code == 204: - current_app.logger.info("Removed service published") - publisher_ops.publish_message("events", "SERVICE_API_UNAVAILABLE") - publisher_ops.publish_message("internal-messages", f"service-removed:{service_api_id}") + res = service_operations.delete_serviceapidescription( + service_api_id, apf_id) 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 @@ -131,6 +126,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 @@ -147,14 +143,13 @@ def apf_id_service_apis_service_api_id_put(service_api_id, apf_id, body): # noq :rtype: ServiceAPIDescription """ - current_app.logger.info("Updating service api id with id: " + service_api_id) + current_app.logger.info( + "Updating service api id with id: " + service_api_id) if connexion.request.is_json: body = ServiceAPIDescription.from_dict(connexion.request.get_json()) # noqa: E501 - response = service_operations.update_serviceapidescription(service_api_id, apf_id, body) - - if response.status_code == 200: - publisher_ops.publish_message("events", "SERVICE_API_UPDATE") + response = service_operations.update_serviceapidescription( + service_api_id, apf_id, body) return response 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 93d35e481efb1c4550b4590934c70edd24b31795..6f40a047f5c7d6378a9518a1c492f24bebaddf69 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 @@ -21,7 +21,7 @@ class Subscriber(): 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)==2: + if message == "provider-removed" and len(ids) > 0: self.security_ops.delete_intern_service(ids[1]) diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py new file mode 100644 index 0000000000000000000000000000000000000000..aadbdbb6b3116cf288648de41effdb12b9ca9143 --- /dev/null +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/redis_event.py @@ -0,0 +1,41 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.EVENTS_ENUM = [ + 'SERVICE_API_AVAILABLE', + 'SERVICE_API_UNAVAILABLE', + 'SERVICE_API_UPDATE', + 'API_INVOKER_ONBOARDED', + 'API_INVOKER_OFFBOARDED', + 'SERVICE_API_INVOCATION_SUCCESS', + 'SERVICE_API_INVOCATION_FAILURE', + 'ACCESS_CONTROL_POLICY_UPDATE', + 'ACCESS_CONTROL_POLICY_UNAVAILABLE', + 'API_INVOKER_AUTHORIZATION_REVOKED', + 'API_INVOKER_UPDATED', + 'API_TOPOLOGY_HIDING_CREATED', + 'API_TOPOLOGY_HIDING_REVOKED'] + if event not in self.EVENTS_ENUM: + raise Exception( + "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") + self.redis_event = { + "event": event + } + if event_detail_key != None and information != 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("events", self.to_string()) + + def __call__(self): + return self.redis_event 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 5c903d101952e574884aa4a81830bfee16fc26d1..85b9b781bdb61168b69c461a6ed7cafaa94ac490 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 @@ -17,26 +17,33 @@ from ..util import dict_to_camel_case, clean_empty from .responses import bad_request_error, internal_server_error, forbidden_error, not_found_error, unauthorized_error, make_response from bson import json_util from .auth_manager import AuthManager +from .redis_event import RedisEvent +from .publisher import Publisher + +publisher_ops = Publisher() service_api_not_found_message = "Service API not found" + class PublishServiceOperations(Resource): 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") - provider = providers_col.find_one({"api_prov_funcs.api_prov_func_id": apf_id}) + provider = providers_col.find_one( + {"api_prov_funcs.api_prov_func_id": apf_id}) 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"] + 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 @@ -57,7 +64,8 @@ class PublishServiceOperations(Resource): 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") @@ -92,9 +100,11 @@ class PublishServiceOperations(Resource): if result != None: return result - service = mycol.find_one({"api_name": serviceapidescription.api_name}) + service = mycol.find_one( + {"api_name": serviceapidescription.api_name}) if service is not None: - current_app.logger.error("Service already registered with same api name") + current_app.logger.error( + "Service already registered with same api name") return forbidden_error(detail="Already registered service with same api name", cause="Found service with same api name") api_id = secrets.token_hex(15) @@ -110,8 +120,13 @@ class PublishServiceOperations(Resource): current_app.logger.debug("Service inserted in database") res = make_response(object=serviceapidescription, status=201) - res.headers['Location'] = "http://localhost:8080/published-apis/v1/" + str(apf_id) + "/service-apis/" + str(api_id) + res.headers['Location'] = "http://localhost:8080/published-apis/v1/" + \ + str(apf_id) + "/service-apis/" + str(api_id) + if res.status_code == 201: + current_app.logger.info("Service published") + RedisEvent("SERVICE_API_AVAILABLE", "apiIds", + [str(api_id)]).send_event() return res except Exception as e: @@ -119,26 +134,25 @@ class PublishServiceOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - - def get_one_serviceapi(self, service_api_id, apf_id): mycol = self.db.get_col_by_name(self.db.service_api_descriptions) try: - current_app.logger.debug("Geting service api with id: " + service_api_id) + 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") - my_service_api = dict_to_camel_case(service_api) my_service_api = clean_empty(my_service_api) @@ -157,7 +171,8 @@ class PublishServiceOperations(Resource): try: - current_app.logger.debug("Removing api service with id: " + service_api_id) + current_app.logger.debug( + "Removing api service with id: " + service_api_id) result = self.__check_apf(apf_id) if result != None: @@ -175,22 +190,29 @@ class PublishServiceOperations(Resource): self.auth_manager.remove_auth_service(service_api_id, apf_id) current_app.logger.debug("Removed service from database") - out = "The service matching api_id " + service_api_id + " was deleted." - return make_response(out, status=204) + out = "The service matching api_id " + service_api_id + " was deleted." + res = make_response(out, status=204) + if res.status_code == 204: + current_app.logger.info("Removed service published") + RedisEvent("SERVICE_API_UNAVAILABLE", "apiIds", + [service_api_id]).send_event() + publisher_ops.publish_message( + "internal-messages", f"service-removed:{service_api_id}") + return res except Exception as e: exception = "An exception occurred in delete service" 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): mycol = self.db.get_col_by_name(self.db.service_api_descriptions) try: - current_app.logger.debug("Updating service api with id: " + service_api_id) + current_app.logger.debug( + "Updating service api with id: " + service_api_id) result = self.__check_apf(apf_id) @@ -207,18 +229,22 @@ 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) current_app.logger.debug("Updated service api") - - response = make_response(object=dict_to_camel_case(result), status=200) + service_api_description_updated = dict_to_camel_case(result) + response = make_response( + object=service_api_description_updated, status=200) + if response.status_code == 200: + RedisEvent("SERVICE_API_UPDATE", "serviceAPIDescriptions", [ + service_api_description_updated]).send_event() return response except Exception as e: exception = "An exception occurred in update service" current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - 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 aee7098c5de2963bb41ce27b110b5e0fbbd6394a..e37ecde5a07a1f6bd10de046189543967ea02e74 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 @@ -117,10 +117,7 @@ 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) - if res.status_code == 204: - current_app.logger.info("Permissions revoked") - publish_ops.publish_message("events", "API_INVOKER_AUTHORIZATION_REVOKED") - + return res @cert_validation() 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 fd9c328e47b82191780c4b88d69f4ab9fdd5b524..8bb8574b6b109361008aff091078519e7a021e21 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 @@ -8,6 +8,7 @@ from threading import Thread from .internal_security_ops import InternalSecurityOps from flask import current_app + class Subscriber(): def __init__(self): @@ -21,12 +22,7 @@ class Subscriber(): 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: + 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]) - - - - - diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/redis_event.py b/services/TS29222_CAPIF_Security_API/capif_security/core/redis_event.py new file mode 100644 index 0000000000000000000000000000000000000000..aadbdbb6b3116cf288648de41effdb12b9ca9143 --- /dev/null +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/redis_event.py @@ -0,0 +1,41 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.EVENTS_ENUM = [ + 'SERVICE_API_AVAILABLE', + 'SERVICE_API_UNAVAILABLE', + 'SERVICE_API_UPDATE', + 'API_INVOKER_ONBOARDED', + 'API_INVOKER_OFFBOARDED', + 'SERVICE_API_INVOCATION_SUCCESS', + 'SERVICE_API_INVOCATION_FAILURE', + 'ACCESS_CONTROL_POLICY_UPDATE', + 'ACCESS_CONTROL_POLICY_UNAVAILABLE', + 'API_INVOKER_AUTHORIZATION_REVOKED', + 'API_INVOKER_UPDATED', + 'API_TOPOLOGY_HIDING_CREATED', + 'API_TOPOLOGY_HIDING_REVOKED'] + if event not in self.EVENTS_ENUM: + raise Exception( + "Event (" + event + ") is not on event enum (" + ','.join(self.EVENTS_ENUM) + ")") + self.redis_event = { + "event": event + } + if event_detail_key != None and information != 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("events", self.to_string()) + + def __call__(self): + return self.redis_event diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py b/services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py index 69475ac99a1d3fd4fca004bea2e98913571e2e68..8f48465eddcb2254f53b46545aaa9517ac3450f3 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py @@ -25,19 +25,22 @@ from .responses import not_found_error, make_response, bad_request_error, intern from .notification import Notifications from .resources import Resource import os +from .redis_event import RedisEvent publish_ops = Publisher() security_context_not_found_detail = "Security context not found" api_invoker_no_context_cause = "API Invoker has no security context" + class SecurityOperations(Resource): def __check_invoker(self, api_invoker_id): invokers_col = self.db.get_col_by_name(self.db.capif_invokers) - current_app.logger.debug("Checking api invoker with id: " + api_invoker_id) - invoker = invokers_col.find_one({"api_invoker_id": api_invoker_id}) + current_app.logger.debug( + "Checking api invoker with id: " + api_invoker_id) + invoker = invokers_col.find_one({"api_invoker_id": api_invoker_id}) if invoker is None: current_app.logger.error("Invoker not found") return not_found_error(detail="Invoker not found", cause="API Invoker not exists or invalid ID") @@ -52,12 +55,14 @@ class SecurityOperations(Resource): header = scope[0:4] if header != "3gpp": current_app.logger.error("Bad format scope") - token_error = AccessTokenErr(error="invalid_scope", error_description="The first characters must be '3gpp'") + token_error = AccessTokenErr( + error="invalid_scope", error_description="The first characters must be '3gpp'") return make_response(object=token_error, status=400) _, body = scope.split("#") - capif_service_col = self.db.get_col_by_name(self.db.capif_service_col) + capif_service_col = self.db.get_col_by_name( + self.db.capif_service_col) security_info = security_context["security_info"] aef_security_context = [info["aef_id"] for info in security_info] @@ -65,22 +70,28 @@ class SecurityOperations(Resource): for group in groups: aef_id, api_names = group.split(":") if aef_id not in aef_security_context: - current_app.logger.error("Bad format Scope, not valid aef id ") - token_error = AccessTokenErr(error="invalid_scope", error_description="One of aef_id not belongs of your security context") + current_app.logger.error( + "Bad format Scope, not valid aef id ") + token_error = AccessTokenErr( + error="invalid_scope", error_description="One of aef_id not belongs of your security context") return make_response(object=token_error, status=400) api_names = api_names.split(",") for api_name in api_names: - service = capif_service_col.find_one({"$and": [{"api_name":api_name},{self.filter_aef_id:aef_id}]}) + service = capif_service_col.find_one( + {"$and": [{"api_name": api_name}, {self.filter_aef_id: aef_id}]}) if service is None: - current_app.logger.error("Bad format Scope, not valid api name") - token_error = AccessTokenErr(error="invalid_scope", error_description="One of the api names does not exist or is not associated with the aef id provided") + current_app.logger.error( + "Bad format Scope, not valid api name") + token_error = AccessTokenErr( + error="invalid_scope", error_description="One of the api names does not exist or is not associated with the aef id provided") return make_response(object=token_error, status=400) return None except Exception as e: current_app.logger.error("Bad format Scope: " + e) - token_error = AccessTokenErr(error="invalid_scope", error_description="malformed scope") + token_error = AccessTokenErr( + error="invalid_scope", error_description="malformed scope") return make_response(object=token_error, status=400) def __init__(self): @@ -93,16 +104,18 @@ class SecurityOperations(Resource): try: - current_app.logger.debug("Obtainig security context with id: " + api_invoker_id) + current_app.logger.debug( + "Obtainig security context with id: " + api_invoker_id) result = self.__check_invoker(api_invoker_id) if result != None: return result else: - services_security_object = mycol.find_one({"api_invoker_id": api_invoker_id}, {"_id":0, "api_invoker_id":0}) + services_security_object = mycol.find_one({"api_invoker_id": api_invoker_id}, { + "_id": 0, "api_invoker_id": 0}) if services_security_object is None: current_app.logger.error("Not found security context") - return not_found_error(detail= security_context_not_found_detail, cause=api_invoker_no_context_cause) + return not_found_error(detail=security_context_not_found_detail, cause=api_invoker_no_context_cause) if not authentication_info: for security_info_obj in services_security_object['security_info']: @@ -111,12 +124,15 @@ class SecurityOperations(Resource): for security_info_obj in services_security_object['security_info']: del security_info_obj['authorization_info'] - properyly_json= json.dumps(services_security_object, default=json_util.default) - my_service_security = dict_to_camel_case(json.loads(properyly_json)) + properyly_json = json.dumps( + services_security_object, default=json_util.default) + my_service_security = dict_to_camel_case( + json.loads(properyly_json)) my_service_security = clean_empty(my_service_security) - current_app.logger.debug("Obtained security context from database") - + current_app.logger.debug( + "Obtained security context from database") + res = make_response(object=my_service_security, status=200) return res @@ -125,7 +141,6 @@ class SecurityOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - def create_servicesecurity(self, api_invoker_id, service_security): mycol = self.db.get_col_by_name(self.db.security_info) @@ -139,43 +154,54 @@ class SecurityOperations(Resource): if rfc3987.match(service_security.notification_destination, rule="URI") is None: current_app.logger.error("Bad url format") - return bad_request_error(detail="Bad Param", cause = "Detected Bad format of param", invalid_params=[{"param": "notificationDestination", "reason": "Not valid URL format"}]) + return bad_request_error(detail="Bad Param", cause="Detected Bad format of param", invalid_params=[{"param": "notificationDestination", "reason": "Not valid URL format"}]) - services_security_object = mycol.find_one({"api_invoker_id": api_invoker_id}) + services_security_object = mycol.find_one( + {"api_invoker_id": api_invoker_id}) if services_security_object is not None: - current_app.logger.error("Already security context defined with same api invoker id") + current_app.logger.error( + "Already security context defined with same api invoker id") return forbidden_error(detail="Security method already defined", cause="Identical AEF Profile IDs") - for service_instance in service_security.security_info: if service_instance.interface_details is not None: security_methods = service_instance.interface_details.security_methods pref_security_methods = service_instance.pref_security_methods - valid_security_method = set(security_methods) & set(pref_security_methods) + valid_security_method = set( + security_methods) & set(pref_security_methods) else: - capif_service_col = self.db.get_col_by_name(self.db.capif_service_col) - services_security_object = capif_service_col.find_one({"api_id":service_instance.api_id, self.filter_aef_id: service_instance.aef_id}, {"aef_profiles.security_methods.$":1}) + capif_service_col = self.db.get_col_by_name( + self.db.capif_service_col) + services_security_object = capif_service_col.find_one( + {"api_id": service_instance.api_id, self.filter_aef_id: service_instance.aef_id}, {"aef_profiles.security_methods.$": 1}) if services_security_object is None: - current_app.logger.error("Not found service with this aef id: " + service_instance.aef_id) + current_app.logger.error( + "Not found service with this aef id: " + service_instance.aef_id) return not_found_error(detail="Service with this aefId not found", cause="Not found Service") pref_security_methods = service_instance.pref_security_methods - valid_security_methods = [security_method for array_methods in services_security_object["aef_profiles"] for security_method in array_methods["security_methods"]] - valid_security_method = set(valid_security_methods) & set(pref_security_methods) + valid_security_methods = [security_method for array_methods in services_security_object["aef_profiles"] + for security_method in array_methods["security_methods"]] + valid_security_method = set( + valid_security_methods) & set(pref_security_methods) if len(list(valid_security_method)) == 0: - current_app.logger.error("Not found comptaible security method with pref security method") + current_app.logger.error( + "Not found comptaible security method with pref security method") return bad_request_error(detail="Not found compatible security method with pref security method", cause="Error pref security method", invalid_params=[{"param": "prefSecurityMethods", "reason": "pref security method not compatible with security method available"}]) - service_instance.sel_security_method = list(valid_security_method)[0] + service_instance.sel_security_method = list( + valid_security_method)[0] # Send service instance to ACL current_app.logger.debug("Sending message to create ACL") - publish_ops.publish_message("acls-messages", "create-acl:"+str(api_invoker_id)+":"+str(service_instance.api_id)+":"+str(service_instance.aef_id)) - current_app.logger.debug("Inserted security context in database") + publish_ops.publish_message("acls-messages", "create-acl:"+str( + api_invoker_id)+":"+str(service_instance.api_id)+":"+str(service_instance.aef_id)) + current_app.logger.debug( + "Inserted security context in database") rec = dict() rec['api_invoker_id'] = api_invoker_id @@ -183,7 +209,8 @@ class SecurityOperations(Resource): mycol.insert_one(rec) res = make_response(object=service_security, status=201) - res.headers['Location'] = "https://{}/capif-security/v1/trustedInvokers/{}".format(os.getenv('CAPIF_HOSTNAME'),str(api_invoker_id)) + res.headers['Location'] = "https://{}/capif-security/v1/trustedInvokers/{}".format( + os.getenv('CAPIF_HOSTNAME'), str(api_invoker_id)) return res except Exception as e: @@ -191,7 +218,6 @@ class SecurityOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - def delete_servicesecurity(self, api_invoker_id): mycol = self.db.get_col_by_name(self.db.security_info) @@ -210,19 +236,22 @@ class SecurityOperations(Resource): if services_security_count == 0: current_app.logger.error(security_context_not_found_detail) return not_found_error(detail=security_context_not_found_detail, cause=api_invoker_no_context_cause) - + mycol.delete_many(my_query) - publish_ops.publish_message("acls-messages", "remove-acl:"+api_invoker_id) + publish_ops.publish_message( + "acls-messages", "remove-acl:"+api_invoker_id) - current_app.logger.debug("Removed security context from database") - out= "The security info of Netapp with Netapp ID " + api_invoker_id + " were deleted.", 204 + current_app.logger.debug( + "Removed security context from database") + out = "The security info of Netapp with Netapp ID " + \ + api_invoker_id + " were deleted.", 204 return make_response(out, status=204) except Exception as e: exception = "An exception occurred in create security info" current_app.logger.error(exception + "::" + str(e)) - return internal_server_error(detail=exception, cause = str(e)) + return internal_server_error(detail=exception, cause=str(e)) def delete_intern_servicesecurity(self, api_invoker_id): @@ -240,34 +269,41 @@ class SecurityOperations(Resource): invokers_col = self.db.get_col_by_name(self.db.capif_invokers) - current_app.logger.debug("Checking api invoker with id: " + access_token_req["client_id"]) - invoker = invokers_col.find_one({"api_invoker_id": access_token_req["client_id"]}) + current_app.logger.debug( + "Checking api invoker with id: " + access_token_req["client_id"]) + invoker = invokers_col.find_one( + {"api_invoker_id": access_token_req["client_id"]}) if invoker is None: - client_id_error = AccessTokenErr(error="invalid_client", error_description="Client Id not found") + client_id_error = AccessTokenErr( + error="invalid_client", error_description="Client Id not found") return make_response(object=client_id_error, status=400) - if access_token_req["grant_type"] != "client_credentials": - client_id_error = AccessTokenErr(error="unsupported_grant_type", error_description="Invalid value for `grant_type` ({0}), must be one of ['client_credentials'] - 'grant_type'" - .format(access_token_req["grant_type"])) + client_id_error = AccessTokenErr(error="unsupported_grant_type", error_description="Invalid value for `grant_type` ({0}), must be one of ['client_credentials'] - 'grant_type'" + .format(access_token_req["grant_type"])) return make_response(object=client_id_error, status=400) service_security = mycol.find_one({"api_invoker_id": security_id}) if service_security is None: - current_app.logger.error("Not found securoty context with id: " + security_id) - return not_found_error(detail= security_context_not_found_detail, cause=api_invoker_no_context_cause) + current_app.logger.error( + "Not found security context with id: " + security_id) + return not_found_error(detail=security_context_not_found_detail, cause=api_invoker_no_context_cause) - result = self.__check_scope(access_token_req["scope"], service_security) + result = self.__check_scope( + access_token_req["scope"], service_security) if result != None: return result expire_time = timedelta(minutes=10) - now=datetime.now() + now = datetime.now() - claims = AccessTokenClaims(iss = access_token_req["client_id"], scope=access_token_req["scope"], exp=int((now+expire_time).timestamp())) - access_token = create_access_token(identity = access_token_req["client_id"] , additional_claims=claims.to_dict()) - access_token_resp = AccessTokenRsp(access_token=access_token, token_type="Bearer", expires_in=int(expire_time.total_seconds()), scope=access_token_req["scope"]) + claims = AccessTokenClaims(iss=access_token_req["client_id"], scope=access_token_req["scope"], exp=int( + (now+expire_time).timestamp())) + access_token = create_access_token( + identity=access_token_req["client_id"], additional_claims=claims.to_dict()) + access_token_resp = AccessTokenRsp(access_token=access_token, token_type="Bearer", expires_in=int( + expire_time.total_seconds()), scope=access_token_req["scope"]) current_app.logger.debug("Created access token") @@ -278,7 +314,6 @@ class SecurityOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - def update_servicesecurity(self, api_invoker_id, service_security): mycol = self.db.get_col_by_name(self.db.security_info) try: @@ -291,38 +326,48 @@ class SecurityOperations(Resource): old_object = mycol.find_one({"api_invoker_id": api_invoker_id}) if old_object is None: - current_app.logger.error("Service api not found with id: " + api_invoker_id) + current_app.logger.error( + "Service api not found with id: " + api_invoker_id) return not_found_error(detail="Service API not existing", cause="Not exist securiy information for this invoker") for service_instance in service_security.security_info: if service_instance.interface_details is not None: security_methods = service_instance.interface_details.security_methods pref_security_methods = service_instance.pref_security_methods - valid_security_method = set(security_methods) & set(pref_security_methods) - service_instance.sel_security_method = list(valid_security_method)[0] + valid_security_method = set( + security_methods) & set(pref_security_methods) + service_instance.sel_security_method = list( + valid_security_method)[0] else: - capif_service_col = self.db.get_col_by_name(self.db.capif_service_col) - services_security_object = capif_service_col.find_one({self.filter_aef_id: service_instance.aef_id}, {"aef_profiles.security_methods.$":1}) + capif_service_col = self.db.get_col_by_name( + self.db.capif_service_col) + services_security_object = capif_service_col.find_one( + {self.filter_aef_id: service_instance.aef_id}, {"aef_profiles.security_methods.$": 1}) if services_security_object is None: - current_app.logger.error("Service api with this aefId not found: " + service_instance.aef_id) + current_app.logger.error( + "Service api with this aefId not found: " + service_instance.aef_id) return not_found_error(detail="Service with this aefId not found", cause="Not found Service") pref_security_methods = service_instance.pref_security_methods - valid_security_methods = [security_method for array_methods in services_security_object["aef_profiles"] for security_method in array_methods["security_methods"]] - valid_security_method = set(valid_security_methods) & set(pref_security_methods) - service_instance.sel_security_method = list(valid_security_method)[0] + valid_security_methods = [security_method for array_methods in services_security_object["aef_profiles"] + for security_method in array_methods["security_methods"]] + valid_security_method = set( + valid_security_methods) & set(pref_security_methods) + service_instance.sel_security_method = list( + valid_security_method)[0] service_security = service_security.to_dict() service_security = clean_empty(service_security) - result = mycol.find_one_and_update(old_object, {"$set":service_security}, projection={'_id': 0, "api_invoker_id":0},return_document=ReturnDocument.AFTER ,upsert=False) + result = mycol.find_one_and_update(old_object, {"$set": service_security}, projection={ + '_id': 0, "api_invoker_id": 0}, return_document=ReturnDocument.AFTER, upsert=False) result = clean_empty(result) current_app.logger.debug("Updated security context") - res= make_response(object=dict_to_camel_case(result), status=200) + res = make_response(object=dict_to_camel_case(result), status=200) res.headers['Location'] = "https://${CAPIF_HOSTNAME}/capif-security/v1/trustedInvokers/" + str( api_invoker_id) return res @@ -331,7 +376,6 @@ class SecurityOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - def revoke_api_authorization(self, api_invoker_id, security_notification): mycol = self.db.get_col_by_name(self.db.security_info) @@ -352,10 +396,12 @@ class SecurityOperations(Resource): updated_security_context = services_security_context.copy() for context in services_security_context["security_info"]: - index = services_security_context["security_info"].index(context) + index = services_security_context["security_info"].index( + context) if security_notification.aef_id == context["aef_id"] or context["api_id"] in security_notification.api_ids: current_app.logger.debug("Sending message.") - publish_ops.publish_message("acls-messages", "remove-acl:"+str(api_invoker_id)+":"+str(context["api_id"])+":"+str(security_notification.aef_id)) + publish_ops.publish_message("acls-messages", "remove-acl:"+str( + api_invoker_id)+":"+str(context["api_id"])+":"+str(security_notification.aef_id)) current_app.logger.debug("message sended.") updated_security_context["security_info"].pop(index) @@ -364,13 +410,16 @@ class SecurityOperations(Resource): if len(updated_security_context["security_info"]) == 0: mycol.delete_many(my_query) - #self.notification.send_notification(services_security_context["notification_destination"], security_notification) - current_app.logger.debug("Revoked security context") - out= "Netapp with ID " + api_invoker_id + " was revoked by some APIs.", 204 - return make_response(out, status=204) + out = "Netapp with ID " + api_invoker_id + " was revoked by some APIs.", 204 + res = make_response(out, status=204) + if res.status_code == 204: + current_app.logger.info("Permissions revoked") + RedisEvent("API_INVOKER_AUTHORIZATION_REVOKED").send_event() + + return res except Exception as e: exception = "An exception occurred in revoke security auth" 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/clean_capif_docker_services.sh b/services/clean_capif_docker_services.sh index fb8949784d4eb81d5a0b958c197765f82118d60d..1bc6d6dc1a1a341e40029d97c12af679237a9b87 100755 --- a/services/clean_capif_docker_services.sh +++ b/services/clean_capif_docker_services.sh @@ -74,4 +74,6 @@ done docker network rm capif-network +docker volume prune --all --force + echo "Clean complete." diff --git a/services/run_capif_tests.sh b/services/run_capif_tests.sh index b73893a45ca40cacecb4dca088999461a9de55de..8f9f414d4cb8d46ce3745f4f66e9ea1e33720da9 100755 --- a/services/run_capif_tests.sh +++ b/services/run_capif_tests.sh @@ -9,13 +9,34 @@ RESULT_FOLDER=$REPOSITORY_BASE_FOLDER/results ROBOT_DOCKER_FILE_FOLDER=$REPOSITORY_BASE_FOLDER/tools/robot # nginx Hostname and http port (80 by default) to reach for tests +# CAPIF_REGISTER=registercapif.mobilesandbox.cloud +CAPIF_REGISTER=capifcore +# CAPIF_REGISTER_PORT=37211 +CAPIF_REGISTER_PORT=8084 +# CAPIF_HOSTNAME=capif.mobilesandbox.cloud CAPIF_HOSTNAME=capifcore CAPIF_HTTP_PORT=8080 CAPIF_HTTPS_PORT=443 -echo "HOSTNAME = $CAPIF_HOSTNAME" +# VAULT access configuration +# CAPIF_VAULT=vault.5gnacar.int +CAPIF_VAULT=vault +CAPIF_VAULT_PORT=8200 +# CAPIF_VAULT_TOKEN=dev-only-token +CAPIF_VAULT_TOKEN=read-ca-token + + +MOCK_SERVER_URL=http://192.168.0.11:9090 + + +echo "CAPIF_HOSTNAME = $CAPIF_HOSTNAME" +echo "CAPIF_REGISTER = $CAPIF_REGISTER" echo "CAPIF_HTTP_PORT = $CAPIF_HTTP_PORT" echo "CAPIF_HTTPS_PORT = $CAPIF_HTTPS_PORT" +echo "CAPIF_VAULT = $CAPIF_VAULT" +echo "CAPIF_VAULT_PORT = $CAPIF_VAULT_PORT" +echo "CAPIF_VAULT_TOKEN = $CAPIF_VAULT_TOKEN" +echo "MOCK_SERVER_URL = $MOCK_SERVER_URL" docker >/dev/null 2>/dev/null if [[ $? -ne 0 ]] @@ -45,8 +66,15 @@ docker run -ti --rm --network="host" \ --add-host host.docker.internal:host-gateway \ --add-host vault:host-gateway \ --add-host register:host-gateway \ + --add-host mockserver:host-gateway \ -v $TEST_FOLDER:/opt/robot-tests/tests \ -v $RESULT_FOLDER:/opt/robot-tests/results ${DOCKER_ROBOT_IMAGE}:${DOCKER_ROBOT_IMAGE_VERSION} \ --variable CAPIF_HOSTNAME:$CAPIF_HOSTNAME \ --variable CAPIF_HTTP_PORT:$CAPIF_HTTP_PORT \ - --variable CAPIF_HTTPS_PORT:$CAPIF_HTTPS_PORT $@ + --variable CAPIF_HTTPS_PORT:$CAPIF_HTTPS_PORT \ + --variable CAPIF_REGISTER:$CAPIF_REGISTER \ + --variable CAPIF_REGISTER_PORT:$CAPIF_REGISTER_PORT \ + --variable CAPIF_VAULT:$CAPIF_VAULT \ + --variable CAPIF_VAULT_PORT:$CAPIF_VAULT_PORT \ + --variable CAPIF_VAULT_TOKEN:$CAPIF_VAULT_TOKEN \ + --variable MOCK_SERVER_URL:$MOCK_SERVER_URL $@ diff --git a/tests/features/CAPIF Api Access Control Policy/capif_api_access_control_policy.robot b/tests/features/CAPIF Api Access Control Policy/capif_api_access_control_policy.robot index 04d789839c2fb6ed6212ede4fd1abf5228b09a55..ec0f0821b4ae9eff2896b2e6dcd18dc9874137eb 100644 --- a/tests/features/CAPIF Api Access Control Policy/capif_api_access_control_policy.robot +++ b/tests/features/CAPIF Api Access Control Policy/capif_api_access_control_policy.robot @@ -238,7 +238,6 @@ Retrieve ACL with security context created by two different Invokers ... username=${AEF_PROVIDER_USERNAME} Check Response Variable Type And Values ${resp} 200 AccessControlPolicyList - # Check returned values Should Not Be Empty ${resp.json()['apiInvokerPolicies']} Length Should Be ${resp.json()['apiInvokerPolicies']} 2 diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index f2a596689598ea7761ca25304e4e8f0e469f6c8e..d5e02d26d1b9d7592d5cc84e489bfecf2bc1dd0e 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -2,6 +2,7 @@ Resource /opt/robot-tests/tests/resources/common.resource Library /opt/robot-tests/tests/libraries/bodyRequests.py Library XML +Library String Resource /opt/robot-tests/tests/resources/common/basicRequests.robot Resource ../../resources/common.resource @@ -137,3 +138,665 @@ Deletes an individual CAPIF Event Subscription with invalid SubscriptionId ... title=Unauthorized ... detail=User not authorized ... cause=You are not the owner of this resource + +Invoker receives Service API Invocation events + [Tags] capif_api_events-6 mockserver + + # Initialize Mock server + Init Mock Server + + # Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Subscribe to events + ${events_list}= Create List SERVICE_API_INVOCATION_SUCCESS SERVICE_API_INVOCATION_FAILURE + ${aef_ids}= Create List ${register_user_info['aef_id']} + ${event_filter}= Create Capif Event Filter aefIds=${aef_ids} + ${event_filters}= Create List ${event_filter} + + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ... eventFilters=${event_filters} + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + # Create Log Entry, emulate success and failure api invocation + ${results}= Create List 200 400 + ${request_body}= Create Log Entry + ... ${register_user_info['aef_id']} + ... ${register_user_info_invoker['api_invoker_id']} + ... ${api_ids} + ... ${api_names} + ... results=${results} + ${resp}= Post Request Capif + ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 InvocationLog + ${resource_url}= Check Location Header ${resp} ${LOCATION_LOGGING_RESOURCE_REGEX} + + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${events_expected}= Create Events From InvocationLogs + ... ${subscription_id} + ... ${request_body} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} + +Invoker subscribe to Service API Available and Unavailable events + [Tags] capif_api_events-7 mockserver + + # Initialize Mock server + Init Mock Server + + # Register APF + ${register_user_info_provider}= Provider Default Registration + + # Publish one api + ${service_api_description_published_1} ${resource_url_1} ${request_body}= Publish Service Api + ... ${register_user_info_provider} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Subscribe to events + ${events_list}= Create List SERVICE_API_AVAILABLE SERVICE_API_UNAVAILABLE + ${aef_ids}= Create List ${register_user_info_provider['aef_id']} + ${event_filter}= Create Capif Event Filter aefIds=${aef_ids} + ${event_filters}= Create List ${event_filter} + + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ... eventFilters=${event_filters} + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + # Provider publish new API + ${service_api_description_published_2} ${resource_url_2} ${request_body}= Publish Service Api + ... ${register_user_info_provider} + ... service_2 + + # Provider Remove service_1 published API + ${resp}= Delete Request Capif + ... ${resource_url_1.path} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${APF_PROVIDER_USERNAME} + + Status Should Be 204 ${resp} + + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${service_api_available_resources}= Create List ${resource_url_2} + ${service_api_unavailable_resources}= Create List ${resource_url_1} + ${events_expected}= Create Expected Events For Service API Notifications + ... subscription_id=${subscription_id} + ... service_api_available_resources=${service_api_available_resources} + ... service_api_unavailable_resources=${service_api_unavailable_resources} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} + +Invoker subscribe to Service API Update + [Tags] capif_api_events-8 mockserver + + # Initialize Mock server + Init Mock Server + + # Register APF + ${register_user_info_provider}= Provider Default Registration + + # Publish one api + ${service_api_description_published} ${resource_url} ${request_body}= Publish Service Api + ... ${register_user_info_provider} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Subscribe to events + ${events_list}= Create List SERVICE_API_UPDATE + ${aef_ids}= Create List ${register_user_info_provider['aef_id']} + ${event_filter}= Create Capif Event Filter aefIds=${aef_ids} + ${event_filters}= Create List ${event_filter} + + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ... eventFilters=${event_filters} + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + # Update Service API + ${service_api_description_modified}= Create Service Api Description service_1_modified + ${resp}= Put Request Capif + ... ${resource_url.path} + ... json=${service_api_description_modified} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${APF_PROVIDER_USERNAME} + + Check Response Variable Type And Values ${resp} 200 ServiceAPIDescription + ... apiName=service_1_modified + + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${events_expected}= Create Expected Service Update Event ${subscription_id} ${resource_url} ${service_api_description_modified} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} + +Provider subscribe to API Invoker events + [Tags] capif_api_events-9 mockserver + + # Initialize Mock server + Init Mock Server + + # Register APF + ${register_user_info_provider}= Provider Default Registration + + # Subscribe to events + ${events_list}= Create List API_INVOKER_ONBOARDED API_INVOKER_UPDATED API_INVOKER_OFFBOARDED + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_provider['amf_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AMF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + # Update Invoker onboarded information + ${new_notification_destination}= Set Variable + ... http://${CAPIF_CALLBACK_IP}:${CAPIF_CALLBACK_PORT}/netapp_new_callback + Set To Dictionary + ... ${request_body} + ... notificationDestination=${new_notification_destination} + ${resp}= Put Request Capif + ... ${url.path} + ... ${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Update + Check Response Variable Type And Values ${resp} 200 APIInvokerEnrolmentDetails + ... notificationDestination=${new_notification_destination} + + # Remove Invoker from CCF + ${resp}= Delete Request Capif + ... ${url.path} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + Call Method ${CAPIF_USERS} remove_capif_users_entry ${url.path} + + # Check Remove + Should Be Equal As Strings ${resp.status_code} 204 + + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${events_expected}= Create Expected Api Invoker Events + ... ${subscription_id} + ... ${register_user_info_invoker['api_invoker_id']} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} + +Invoker subscribed to ACL update event + [Tags] capif_api_events-10 mockserver + + # Initialize Mock server + Init Mock Server + + # Register APF + ${register_user_info_provider}= Provider Default Registration + + # Publish one api + ${service_api_description_published} ${resource_url} ${request_body}= Publish Service Api + ... ${register_user_info_provider} + + # Store apiId1 + ${service_api_id}= Set Variable ${service_api_description_published['apiId']} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + # Subscribe to events + ${events_list}= Create List ACCESS_CONTROL_POLICY_UPDATE + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_provider['amf_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AMF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + # Test + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']}&aef-id=${register_user_info_provider['aef_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + Check Response Variable Type And Values ${discover_response} 200 DiscoveredAPIs + + # create Security Context + ${request_service_security_body}= Create Service Security From Discover Response + ... http://${CAPIF_HOSTNAME}:${CAPIF_HTTP_PORT}/test + ... ${discover_response} + ${resp}= Put Request Capif + ... /capif-security/v1/trustedInvokers/${register_user_info_invoker['api_invoker_id']} + ... json=${request_service_security_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Service Security + Check Response Variable Type And Values ${resp} 201 ServiceSecurity + ${resource_url}= Check Location Header ${resp} ${LOCATION_SECURITY_RESOURCE_REGEX} + + ${resp}= Get Request Capif + ... /access-control-policy/v1/accessControlPolicyList/${service_api_id}?aef-id=${register_user_info_provider['aef_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + Check Response Variable Type And Values ${resp} 200 AccessControlPolicyList + # Check returned values + Should Not Be Empty ${resp.json()['apiInvokerPolicies']} + Length Should Be ${resp.json()['apiInvokerPolicies']} 1 + Should Be Equal As Strings + ... ${resp.json()['apiInvokerPolicies'][0]['apiInvokerId']} + ... ${register_user_info_invoker['api_invoker_id']} + + ${api_invoker_policies}= Set Variable ${resp.json()['apiInvokerPolicies']} + + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${events_expected}= Create Expected Access Control Policy Update Event + ... ${subscription_id} + ... ${service_api_id} + ... ${api_invoker_policies} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} + +Provider receives an ACL unavailable event when invoker remove Security Context. + [Tags] capif_api_events-11 mockserver + + # Initialize Mock server + Init Mock Server + + # Register APF + ${register_user_info_provider}= Provider Default Registration + + # Publish one api + ${service_api_description_published} ${resource_url} ${request_body}= Publish Service Api + ... ${register_user_info_provider} + + # Store apiId1 + ${serviceApiId}= Set Variable ${service_api_description_published['apiId']} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + # Subscribe to events + ${events_list}= Create List ACCESS_CONTROL_POLICY_UNAVAILABLE + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_provider['amf_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + # Test + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']}&aef-id=${register_user_info_provider['aef_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + Check Response Variable Type And Values ${discover_response} 200 DiscoveredAPIs + + # create Security Context + ${request_service_security_body}= Create Service Security From Discover Response + ... http://${CAPIF_HOSTNAME}:${CAPIF_HTTP_PORT}/test + ... ${discover_response} + ${resp}= Put Request Capif + ... /capif-security/v1/trustedInvokers/${register_user_info_invoker['api_invoker_id']} + ... json=${request_service_security_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Service Security + Check Response Variable Type And Values ${resp} 201 ServiceSecurity + ${resource_url}= Check Location Header ${resp} ${LOCATION_SECURITY_RESOURCE_REGEX} + + # Remove Security Context by Provider + ${resp}= Delete Request Capif + ... /capif-security/v1/trustedInvokers/${register_user_info_invoker['api_invoker_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + Status Should Be 204 ${resp} + + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${events_expected}= Create Expected Access Control Policy Unavailable ${subscription_id} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} + +Invoker receives an Invoker Authorization Revoked and ACL unavailable event when Provider revoke Invoker Authorization. + [Tags] capif_api_events-12 mockserver + + # Initialize Mock server + Init Mock Server + + # Register APF + ${register_user_info_provider}= Provider Default Registration + + # Publish one api + ${service_api_description_published} ${resource_url} ${request_body}= Publish Service Api + ... ${register_user_info_provider} + + # Store apiId1 + ${serviceApiId}= Set Variable ${service_api_description_published['apiId']} + + # Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + # Subscribe to events + ${events_list}= Create List ACCESS_CONTROL_POLICY_UNAVAILABLE API_INVOKER_AUTHORIZATION_REVOKED + ${request_body}= Create Events Subscription + ... events=@{events_list} + ... notificationDestination=${MOCK_SERVER_URL}/testing + ${resp}= Post Request Capif + ... /capif-events/v1/${register_user_info_provider['amf_id']}/subscriptions + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 201 EventSubscription + ${subscriber_id} ${subscription_id}= Check Event Location Header ${resp} + + # Test + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']}&aef-id=${register_user_info_provider['aef_id']} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + Check Response Variable Type And Values ${discover_response} 200 DiscoveredAPIs + + ${api_ids}= Get Api Ids From Discover Response ${discover_response} + + # create Security Context + ${request_service_security_body}= Create Service Security From Discover Response + ... http://${CAPIF_HOSTNAME}:${CAPIF_HTTP_PORT}/test + ... ${discover_response} + ${resp}= Put Request Capif + ... /capif-security/v1/trustedInvokers/${register_user_info_invoker['api_invoker_id']} + ... json=${request_service_security_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Check Service Security + Check Response Variable Type And Values ${resp} 201 ServiceSecurity + ${resource_url}= Check Location Header ${resp} ${LOCATION_SECURITY_RESOURCE_REGEX} + + # Revoke Security Context by Provider + ${request_body}= Create Security Notification Body + ... ${register_user_info_invoker['api_invoker_id']} + ... ${api_ids} + ${resp}= Post Request Capif + ... /capif-security/v1/trustedInvokers/${register_user_info_invoker['api_invoker_id']}/delete + ... json=${request_body} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + # Check Results + Status Should Be 204 ${resp} + + # Check Event Notifications + ## Create check Events to ensure all notifications were received + ${events_expected}= Create Expected Access Control Policy Unavailable ${subscription_id} + ${events_expected}= Create Expected Api Invoker Authorization Revoked + ... ${subscription_id} + ... events_expected=${events_expected} + ## Check Events Expected towards received notifications at mock server + Check Mock Server Notification Events ${events_expected} + + +*** Keywords *** +Create Events From InvocationLogs + [Arguments] ${subscription_id} ${invocation_log} ${events_expected}=${NONE} + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END + + # Now we create the expected events received at notification server according to logs sent to loggin service in order to check if all are present. + ${invocation_log_base}= Copy Dictionary ${invocation_log} deepcopy=True + # Store log array because each log will be notified in one Event Notification + ${invocation_log_logs}= Copy List ${invocation_log_base['logs']} + # Remove logs array from invocationLog data + Remove From Dictionary ${invocation_log_base} logs + + FOR ${log} IN @{invocation_log_logs} + Log Dictionary ${log} + ${invocation_logs}= Copy Dictionary ${invocation_log_base} deepcopy=True + + # Get Event Enum for this result + ${event_enum}= Set Variable + IF ${log['result']} >= 200 and ${log['result']} < 300 + ${event_enum}= Set Variable SERVICE_API_INVOCATION_SUCCESS + ELSE + ${event_enum}= Set Variable SERVICE_API_INVOCATION_FAILURE + END + # Create a log array with only one component + ${log_list}= Create List ${log} + # Setup logs array with previously created list + Set To Dictionary ${invocation_logs} logs=${log_list} + ${event_expected}= Create Notification Event ${subscription_id} ${event_enum} invocationLogs=${invocation_logs} + Append To List ${events_expected} ${event_expected} + END + + RETURN ${events_expected} +Create Expected Events For Service API Notifications + [Arguments] + ... ${subscription_id} + ... ${service_api_available_resources}=${NONE} + ... ${service_api_unavailable_resources}=${NONE} + ... ${events_expected}=${NONE} + + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END + + FOR ${service_api_available_resource} IN @{service_api_available_resources} + Log ${service_api_available_resource} + ${api_id}= Fetch From Right ${service_api_available_resource.path} / + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... SERVICE_API_AVAILABLE + ... apiIds=${api_id} + Append To List ${events_expected} ${event_expected} + END + + FOR ${service_api_unavailable_resource} IN @{service_api_unavailable_resources} + Log ${service_api_unavailable_resource} + ${api_id}= Fetch From Right ${service_api_unavailable_resource.path} / + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... SERVICE_API_UNAVAILABLE + ... apiIds=${api_id} + Append To List ${events_expected} ${event_expected} + END + + RETURN ${events_expected} + +Create Expected Api Invoker Events + [Arguments] ${subscription_id} ${api_invoker_id} ${events_expected}=${NONE} + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END + ## Create events expected + # Create Notification Events expected to be received for Onboard event + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... API_INVOKER_ONBOARDED + ... apiInvokerIds=${api_invoker_id} + Append To List ${events_expected} ${event_expected} + + # Create Notification Events expected to be received for Updated event + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... API_INVOKER_UPDATED + ... apiInvokerIds=${api_invoker_id} + Append To List ${events_expected} ${event_expected} + + # Create Notification Events expected to be received for Offboard event + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... API_INVOKER_OFFBOARDED + ... apiInvokerIds=${api_invoker_id} + Append To List ${events_expected} ${event_expected} + + RETURN ${events_expected} + +Create Expected Access Control Policy Update Event + [Arguments] ${subscription_id} ${service_api_id} ${api_invoker_policies} ${events_expected}=${NONE} + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END + ${acc_ctrl_pol_list}= Create Dictionary apiId=${service_api_id} apiInvokerPolicies=${api_invoker_policies} + Check Variable ${acc_ctrl_pol_list} AccessControlPolicyListExt + + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... ACCESS_CONTROL_POLICY_UPDATE + ... accCtrlPolList=${acc_ctrl_pol_list} + Append To List ${events_expected} ${event_expected} + + RETURN ${events_expected} + +Create Expected Access Control Policy Unavailable + [Arguments] ${subscription_id} ${events_expected}=${NONE} + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... ACCESS_CONTROL_POLICY_UNAVAILABLE + Append To List ${events_expected} ${event_expected} + + RETURN ${events_expected} + +Create Expected Api Invoker Authorization Revoked + [Arguments] ${subscription_id} ${events_expected}=${NONE} + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... API_INVOKER_AUTHORIZATION_REVOKED + Append To List ${events_expected} ${event_expected} + RETURN ${events_expected} + +Create Expected Service Update Event + [Arguments] ${subscription_id} ${service_api_resource} ${service_api_descriptions} ${events_expected}=${NONE} + IF ${events_expected} == ${NONE} + ${events_expected}= Create List + END + ${api_id}= Fetch From Right ${service_api_resource.path} / + Set To Dictionary ${service_api_descriptions} apiId=${api_id} + ${events_expected}= Create List + ${event_expected}= Create Notification Event + ... ${subscription_id} + ... SERVICE_API_UPDATE + ... serviceAPIDescriptions=${service_api_descriptions} + Append To List ${events_expected} ${event_expected} + RETURN ${events_expected} \ No newline at end of file diff --git a/tests/libraries/api_events/bodyRequests.py b/tests/libraries/api_events/bodyRequests.py index ab6e4dae3d646bccbb9ac3f994c7f71112d9e2dc..17fd1b57f743adeecd6da459a8d0a3179b11af90 100644 --- a/tests/libraries/api_events/bodyRequests.py +++ b/tests/libraries/api_events/bodyRequests.py @@ -1,32 +1,97 @@ -def create_events_subscription(): +def create_events_subscription(events=["SERVICE_API_AVAILABLE", "API_INVOKER_ONBOARDED"], notificationDestination="http://robot.testing", eventFilters=None, eventReq=None, requestTestNotification=None, supportedFeatures=None, websockNotifConfig=None): + event_subscription = { + "events": events, + "notificationDestination": notificationDestination, + } + if eventFilters != None: + event_subscription['eventFilters'] = eventFilters + if eventReq != None: + event_subscription['eventReq'] = eventReq + if requestTestNotification != None: + event_subscription['requestTestNotification'] = requestTestNotification + if supportedFeatures != None: + event_subscription['supportedFeatures'] = supportedFeatures + if websockNotifConfig != None: + event_subscription['websockNotifConfig'] = websockNotifConfig + + return event_subscription + + +def create_capif_event_filter(aefIds=None, apiIds=None, apiInvokerIds=None): + if aefIds == None and apiIds == None and apiInvokerIds: + raise ("Error, no data present to create event filter") + capif_event_filter = dict() + if aefIds != None: + capif_event_filter['aefIds'] = aefIds + if apiIds != None: + capif_event_filter['apiIds'] = apiIds + if apiInvokerIds != None: + capif_event_filter['apiInvokerIds'] = apiInvokerIds + return capif_event_filter + + +def create_default_event_req(): + return { + "grpRepTime": 5, + "immRep": True, + "maxReportNbr": 0, + "monDur": "2000-01-23T04:56:07+00:00", + "partitionCriteria": ["TAC", "GEOAREA"], + "repPeriod": 6, + "sampRatio": 15 + } + + +def create_websock_notif_config_default(): return { - "eventFilters": [ - { - "aefIds": ["aefIds", "aefIds"], - "apiIds": ["apiIds", "apiIds"], - "apiInvokerIds": ["apiInvokerIds", "apiInvokerIds"] - }, - { - "aefIds": ["aefIds", "aefIds"], - "apiIds": ["apiIds", "apiIds"], - "apiInvokerIds": ["apiInvokerIds", "apiInvokerIds"] - } - ], - "eventReq": { - "grpRepTime": 5, - "immRep": True, - "maxReportNbr": 0, - "monDur": "2000-01-23T04:56:07+00:00", - "partitionCriteria": ["TAC", "GEOAREA"], - "repPeriod": 6, - "sampRatio": 15 - }, - "events": ["SERVICE_API_AVAILABLE", "API_INVOKER_ONBOARDED"], - "notificationDestination": "http://robot.testing", - "requestTestNotification": True, - "supportedFeatures": "aaa", - "websockNotifConfig": { - "requestWebsocketUri": True, - "websocketUri": "websocketUri" - } + "requestWebsocketUri": True, + "websocketUri": "websocketUri" + } + + +def create_notification_event(subscriptionId, event, serviceAPIDescriptions=None, apiIds=None, apiInvokerIds=None, accCtrlPolList=None, invocationLogs=None, apiTopoHide=None): + result = { + "subscriptionId": subscriptionId, + "events": event, + "eventDetail": dict() } + count = 0 + if serviceAPIDescriptions != None: + if isinstance(serviceAPIDescriptions, list): + result['eventDetail']['serviceAPIDescriptions'] = serviceAPIDescriptions + else: + result['eventDetail']['serviceAPIDescriptions'] = [ + serviceAPIDescriptions] + count = count+1 + if apiIds != None: + if isinstance(apiIds, list): + result['eventDetail']['apiIds'] = apiIds + else: + result['eventDetail']['apiIds'] = [apiIds] + count = count+1 + if apiInvokerIds != None: + if isinstance(apiInvokerIds, list): + result['eventDetail']['apiInvokerIds'] = apiInvokerIds + else: + result['eventDetail']['apiInvokerIds'] = [apiInvokerIds] + count = count+1 + if accCtrlPolList != None: + result['eventDetail']['accCtrlPolList'] = accCtrlPolList + count = count+1 + if invocationLogs != None: + if isinstance(invocationLogs, list): + result['eventDetail']['invocationLogs'] = invocationLogs + else: + result['eventDetail']['invocationLogs'] = [invocationLogs] + count = count+1 + if apiTopoHide != None: + if isinstance(apiTopoHide, list): + result['eventDetail']['apiTopoHide'] = apiTopoHide + else: + result['eventDetail']['apiTopoHide'] = [apiTopoHide] + count = count+1 + + if count == 0: + del result['eventDetail'] + + return result diff --git a/tests/libraries/api_logging_service/bodyRequests.py b/tests/libraries/api_logging_service/bodyRequests.py index fe3faf1dd9dbc1ccd0fb96ce96e5b73d0cd805f9..a4d2a18770dca3d2d43742010bf9d336aaf728d3 100644 --- a/tests/libraries/api_logging_service/bodyRequests.py +++ b/tests/libraries/api_logging_service/bodyRequests.py @@ -1,84 +1,21 @@ -def create_log_entry(aefId=None, apiInvokerId=None, apiId=None, apiName=None): +def create_log_entry(aefId, apiInvokerId, apiId, apiName, results=['200','500'],api_versions=['v1','v2']): data = { "aefId": aefId, "apiInvokerId": apiInvokerId, - "logs": [ - { - "apiId": apiId[0], - "apiName": apiName[0], - "apiVersion": "v1", - "resourceName": "string", - "uri": "http://resource/endpoint", - "protocol": "HTTP_1_1", - "operation": "GET", - "result": "string", - "invocationTime": "2023-03-30T10:30:21.408Z", - "invocationLatency": 0, - "inputParameters": "string", - "outputParameters": "string", - "srcInterface": { - "ipv4Addr": "192.168.1.1", - "fqdn": "string", - "port": 65535, - "apiPrefix": "string", - "securityMethods": [ - "PSK", - "string" - ] - }, - "destInterface": { - "ipv4Addr": "192.168.1.23", - "fqdn": "string", - "port": 65535, - "apiPrefix": "string", - "securityMethods": [ - "PSK", - "string" - ] - }, - "fwdInterface": "string" - }, - { - "apiId": apiId[0], - "apiName": apiName[0], - "apiVersion": "v2", - "resourceName": "string", - "uri": "http://resource/endpoint", - "protocol": "HTTP_1_1", - "operation": "GET", - "result": "string", - "invocationTime": "2023-03-30T10:30:21.408Z", - "invocationLatency": 0, - "inputParameters": "string", - "outputParameters": "string", - "srcInterface": { - "ipv4Addr": "192.168.1.1", - "fqdn": "string", - "port": 65535, - "apiPrefix": "string", - "securityMethods": [ - "PSK", - "string" - ] - }, - "destInterface": { - "ipv4Addr": "192.168.1.23", - "fqdn": "string", - "port": 65535, - "apiPrefix": "string", - "securityMethods": [ - "PSK", - "string" - ] - }, - "fwdInterface": "string" - } - ], + "logs": [], "supportedFeatures": "ffff" } + if len(results) > 0: + count=0 + for result in results: + data['logs'].append(create_log(apiId,apiName,result,api_versions[count])) + count=count+1 + if count == len(api_versions): + count=0 + return data -def create_log_entry_bad_service(aefId=None, apiInvokerId=None): +def create_log_entry_bad_service(aefId, apiInvokerId, result='500'): data = { "aefId": aefId, "apiInvokerId": apiInvokerId, @@ -91,29 +28,25 @@ def create_log_entry_bad_service(aefId=None, apiInvokerId=None): "uri": "string", "protocol": "HTTP_1_1", "operation": "GET", - "result": "string", - "invocationTime": "2023-03-30T10:30:21.408Z", + "result": result, + "invocationTime": "2023-03-30T10:30:21.408000+00:00", "invocationLatency": 0, "inputParameters": "string", "outputParameters": "string", "srcInterface": { "ipv4Addr": "192.168.1.1", - "fqdn": "string", "port": 65535, - "apiPrefix": "string", "securityMethods": [ "PSK", - "string" + "PKI" ] }, "destInterface": { "ipv4Addr": "192.168.1.23", - "fqdn": "string", "port": 65535, - "apiPrefix": "string", "securityMethods": [ "PSK", - "string" + "PKI" ] }, "fwdInterface": "string" @@ -131,3 +64,29 @@ def get_api_ids_and_names_from_discover_response(discover_response): api_ids.append(service_api_description['apiId']) api_names.append(service_api_description['apiName']) return api_ids, api_names + + +def create_log(apiId, apiName, result, api_version='v1'): + log= { + "apiId": apiId[0], + "apiName": apiName[0], + "apiVersion": api_version, + "resourceName": "string", + "uri": "http://resource/endpoint", + "protocol": "HTTP_1_1", + "operation": "GET", + "result": result, + "invocationTime": "2023-03-30T10:30:21.408000+00:00", + "invocationLatency": 0, + "inputParameters": "string", + "outputParameters": "string", + "srcInterface": { + "ipv4Addr": "192.168.1.1", + "port": 65535, + "securityMethods": [ + "PSK", + "PKI" + ] + } + } + return log \ No newline at end of file diff --git a/tests/libraries/api_publish_service/bodyRequests.py b/tests/libraries/api_publish_service/bodyRequests.py index 17ac4a794a5ba1a38357ee229d04b3b4590bfdc8..69e7bb117f01b4c53ee7d1db74b7da09e20bf3a1 100644 --- a/tests/libraries/api_publish_service/bodyRequests.py +++ b/tests/libraries/api_publish_service/bodyRequests.py @@ -7,7 +7,7 @@ def create_service_api_description(api_name="service_1",aef_id="aef_id"): "versions": [ { "apiVersion": "v1", - "expiry": "2021-11-30T10:32:02.004000Z", + "expiry": "2021-11-30T10:32:02.004000+00:00", "resources": [ { "resourceName": "string", @@ -20,27 +20,12 @@ def create_service_api_description(api_name="service_1",aef_id="aef_id"): "description": "string" } ], - "custOperations": [ - { - "commType": "REQUEST_RESPONSE", - "custOpName": "string", - "operations": [ - "GET" - ], - "description": "string" - } - ] } ], "protocol": "HTTP_1_1", "dataFormat": "JSON", "securityMethods": ["PSK"], "interfaceDescriptions": [ - { - "ipv4Addr": "string", - "port": 65535, - "securityMethods": ["PSK"] - }, { "ipv4Addr": "string", "port": 65535, diff --git a/tests/libraries/common/bodyRequests.py b/tests/libraries/common/bodyRequests.py index 3f1e482d4717bc5d3ddfc298962afb8e42bc1a7e..1d4ec14f60759bfb221b686ba84df23b1386fa6c 100644 --- a/tests/libraries/common/bodyRequests.py +++ b/tests/libraries/common/bodyRequests.py @@ -15,7 +15,7 @@ def check_variable(input, data_type): if isinstance(input, list): for one in input: check_variable(one, data_type) - return True + return True if data_type == "string": if isinstance(input, str): return True diff --git a/tests/libraries/common/types.json b/tests/libraries/common/types.json index 4b683595c43c912f868856107d21ed84ad08032d..220dd0f5710796fd875ddda8d05dfb3e39a272cd 100644 --- a/tests/libraries/common/types.json +++ b/tests/libraries/common/types.json @@ -259,6 +259,67 @@ "aefIds": "string" } }, + "EventNotification": { + "mandatory_attributes": { + "subscriptionId": "string", + "events": "CAPIFEvent" + }, + "optional_attributes": { + "eventDetail": "CAPIFEventDetail" + } + }, + "CAPIFEventDetail": { + "mandatory_attributes": {}, + "optional_attributes": { + "serviceAPIDescriptions": "ServiceAPIDescription", + "apiIds": "string", + "apiInvokerIds": "string", + "accCtrlPolList": "AccessControlPolicyListExt", + "invocationLogs": "InvocationLog", + "apiTopoHide": "TopologyHiding" + } + }, + "AccessControlPolicyListExt":{ + "mandatory_attributes": { + "apiId": "string" + }, + "optional_attributes": { + "apiInvokerPolicies": "ApiInvokerPolicy" + } + }, + "TopologyHiding": { + "mandatory_attributes":{ + "apiId": "string", + "routingRules": "RoutingRule" + }, + "optional_attributes": {} + }, + "RoutingRule":{ + "mandatory_attributes": { + "aefProfile": "AefProfile" + }, + "optional_attributes": { + "ipv4AddrRanges": "Ipv4AddressRange", + "ipv6AddrRanges": "Ipv6AddressRange" + } + }, + "Ipv4AddressRange":{ + "mandatory_attributes": {}, + "optional_attributes": { + "start": "Ipv4Addr", + "stop": "Ipv4Addr" + } + }, + "Ipv4Addr": { + "regex": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$" + }, + "Ipv6AddressRange":{ + "mandatory_attributes":{ + "start": "string", + "end": "string" + }, + "optional_attributes":{} + }, "ReportingInformation": { "mandatory_attributes": {}, "optional_attributes": { diff --git a/tests/libraries/mock_server/mock_server.py b/tests/libraries/mock_server/mock_server.py new file mode 100644 index 0000000000000000000000000000000000000000..e45fae0f241f1801e7ddc9749399f4fa0bc5d796 --- /dev/null +++ b/tests/libraries/mock_server/mock_server.py @@ -0,0 +1,56 @@ +from flask import Flask, request +import logging +from logging.handlers import RotatingFileHandler +import os + +app = Flask(__name__) + +# Lista para almacenar las solicitudes recibidas +requests_received = [] + +def verbose_formatter(): + return logging.Formatter( + '{"timestamp": "%(asctime)s", "level": "%(levelname)s", "logger": "%(name)s", "function": "%(funcName)s", "line": %(lineno)d, "message": %(message)s}', + datefmt='%d/%m/%Y %H:%M:%S' + ) + + +def configure_logging(app): + del app.logger.handlers[:] + loggers = [app.logger, ] + handlers = [] + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.DEBUG) + console_handler.setFormatter(verbose_formatter()) + file_handler = RotatingFileHandler(filename="mock_server.log", maxBytes=1024 * 1024 * 100, backupCount=20) + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(verbose_formatter()) + handlers.append(console_handler) + handlers.append(file_handler) + + + for l in loggers: + for handler in handlers: + l.addHandler(handler) + l.propagate = False + l.setLevel(logging.DEBUG) + +@app.route('/testing', methods=['POST', 'GET']) +def index(): + if request.method == 'POST': + app.logger.debug(request.json) + app.logger.debug(request.headers) + requests_received.append(request.json) + return 'Mock Server is running' + +@app.route('/requests_list', methods=['GET','DELETE']) +def requests_list(): + if request.method == 'DELETE': + requests_received.clear() + return requests_received + + +configure_logging(app) + +if __name__ == '__main__': + app.run(host=os.environ.get("IP",'0.0.0.0'),port=os.environ.get("PORT",9090),debug=True) diff --git a/tests/libraries/mock_server/requirements.txt b/tests/libraries/mock_server/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..abf28620ee482efbabd6ccf8a2b8d8b554563b03 --- /dev/null +++ b/tests/libraries/mock_server/requirements.txt @@ -0,0 +1 @@ +flask==3.0.3 \ No newline at end of file diff --git a/tests/libraries/mock_server/run_mock_server.sh b/tests/libraries/mock_server/run_mock_server.sh new file mode 100755 index 0000000000000000000000000000000000000000..10c4ba2a7e35b44b23d2353eee017295876fb92b --- /dev/null +++ b/tests/libraries/mock_server/run_mock_server.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +help() { + echo "Usage: $1 " + echo " -i : Setup different host ip for mock server (default 0.0.0.0)" + echo " -p : Setup different port for mock server (default 9090)" + echo " -h : show this help" + exit 1 +} + +IP=0.0.0.0 +PORT=9090 + +# Read params +while getopts ":i:p:h" opt; do + case $opt in + i) + IP="$OPTARG" + ;; + p) + PORT=$OPTARG + ;; + h) + help + ;; + \?) + echo "Not valid option: -$OPTARG" >&2 + help + ;; + :) + echo "The -$OPTARG option requires an argument." >&2 + help + ;; + esac +done + +echo Robot Framework Mock Server will listen on $IP:$PORT +pip install -r requirements.txt + +IP=$IP PORT=$PORT python mock_server.py diff --git a/tests/requirements.txt b/tests/requirements.txt index c6d90325ac78628f7de12acc34a01497039f57f3..3119b5ca73095dfdc4a018dc35abaf83c5bc5ab2 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,8 +1,2 @@ # Requirements file for tests. -robotframework-mongodb-library==3.2 -requests==2.28.1 -configparser==5.3.0 -redis==4.3.4 -rfc3987==1.3.8 -robotframework-httpctrl -robotframework-archivelibrary == 0.4.2 \ No newline at end of file + diff --git a/tests/resources/common.resource b/tests/resources/common.resource index c46131b17388b006b9fa7f521033a3c32874c171..d4733b8ee4e12ac5477501b1e1c0ffab4a09f79d 100644 --- a/tests/resources/common.resource +++ b/tests/resources/common.resource @@ -1,5 +1,7 @@ *** Settings *** Library /opt/robot-tests/tests/libraries/helpers.py +Library Process +Library Collections Variables /opt/robot-tests/tests/libraries/environment.py Resource /opt/robot-tests/tests/resources/common/basicRequests.robot @@ -30,6 +32,9 @@ ${CAPIF_CALLBACK_PORT} 8086 ${REGISTER_ADMIN_USER} admin ${REGISTER_ADMIN_PASSWORD} password123 +${MOCK_SERVER_URL} + + ${DISCOVER_URL} /service-apis/v1/allServiceAPIs?api-invoker-id= @@ -85,3 +90,78 @@ Remove Keys From Object Test ${TEST NAME} Currently Not Supported Log Test "${TEST NAME}" Currently not supported WARN Skip Test "${TEST NAME}" Currently not supported + +Init Mock Server + Check Mock Server + Clean Mock Server + +Check Mock Server + Log Checking mock Server for Robot Framework. + + IF "${MOCK_SERVER_URL}" == "" + Fail Mock Server Url is not setup on Tests execution, check MOCK_SERVER_URL variable mockserver-not-ready + END + + Create Session mockserver ${MOCK_SERVER_URL} + + ${endpoint}= Set Variable /requests_list + + ${resp}= GET On Session + ... mockserver + ... ${endpoint} + ... expected_status=any + ... verify=False + + Status Should Be 200 ${resp} Mock Server response is not 200 OK at ${MOCK_SERVER_URL}, check server status. + +Clean Mock Server + Log Checking mock Server for Robot Framework. + + Create Session mockserver ${MOCK_SERVER_URL} + + ${endpoint}= Set Variable /requests_list + + ${resp}= DELETE On Session + ... mockserver + ... ${endpoint} + ... expected_status=any + ... verify=False + + Status Should Be 200 ${resp} + + +Get Mock Server Messages + Log Checking mock Server for Robot Framework. + + Create Session mockserver ${MOCK_SERVER_URL} + + ${endpoint}= Set Variable /requests_list + + ${resp}= GET On Session + ... mockserver + ... ${endpoint} + ... expected_status=any + ... verify=False + + Status Should Be 200 ${resp} + Log List ${resp.json()} + RETURN ${resp} + +Check Mock Server Notification Events + [Arguments] ${events_expected} + + Check Variable ${events_expected} EventNotification + # Check results + ${events_expected_length}= Get Length ${events_expected} + + Sleep 3s + # Get from Mock server the EventNotification Messages sent to callback setup on event subscription. + ${resp}= Get Mock Server Messages + ${notification_events_on_mock_server}= Set Variable ${resp.json()} + Check Variable ${notification_events_on_mock_server} EventNotification + + Length Should Be ${notification_events_on_mock_server} ${events_expected_length} + FOR ${event_expected} IN @{events_expected} + Log ${event_expected} + List Should Contain Value ${notification_events_on_mock_server} ${event_expected} + END \ No newline at end of file diff --git a/tests/resources/common/basicRequests.robot b/tests/resources/common/basicRequests.robot index ea3a96ffc432e86aa543ab51c796cbffb61bfc76..335743fdc1029a84d3a3e3d7592f081e9f6f4743 100644 --- a/tests/resources/common/basicRequests.robot +++ b/tests/resources/common/basicRequests.robot @@ -457,6 +457,7 @@ Get Auth For User ${resp}= GET On Session register_session /getauth auth=${auth} Should Be Equal As Strings ${resp.status_code} 200 + Log Dictionary ${resp.json()} RETURN ${resp.json()} @@ -744,3 +745,4 @@ Create Security Context Between invoker and provider ... username=${register_user_info_invoker['management_cert']} Check Response Variable Type And Values ${resp} 201 ServiceSecurity + diff --git a/tools/robot/Dockerfile b/tools/robot/Dockerfile index 49cea1714ace28c777903b7c30f30bad1ae674cf..347f226da0bf93d5e1f8a9adaa18901c5dafc000 100644 --- a/tools/robot/Dockerfile +++ b/tools/robot/Dockerfile @@ -25,6 +25,7 @@ VOLUME $ROBOT_RESULTS_DIRECTORY WORKDIR $ROBOT_DIRECTORY ENV DEBIAN_FRONTEND=noninteractive +RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections # Install dependencies RUN apt-get update @@ -49,14 +50,15 @@ RUN apt-get install -y --no-install-recommends \ python3-venv \ python2.7-dev \ libssl-dev \ - libldap2-dev libsasl2-dev ldap-utils slapd tox lcov valgrind\ - tshark + libldap2-dev libsasl2-dev ldap-utils slapd tox lcov valgrind \ + tshark \ + nodejs \ + npm -RUN add-apt-repository ppa:deadsnakes/ppa +RUN add-apt-repository -y ppa:deadsnakes/ppa RUN apt-get update RUN apt-get install -y --fix-missing python3.10 python3.10-venv python3.10-dev - RUN mkdir /opt/venv RUN python3.10 -m venv /opt/venv diff --git a/tools/robot/basicRequirements.txt b/tools/robot/basicRequirements.txt index 87752271253dc67c7e0c0d8e8f42d4768001b074..aa3e60e0f55ee408cb29605ef29446b3dd075d4f 100644 --- a/tools/robot/basicRequirements.txt +++ b/tools/robot/basicRequirements.txt @@ -13,7 +13,7 @@ certifi==2021.10.8 cffi==1.15.1 chardet==5.0.0 charset-normalizer==2.0.12 -click==8.0.1 +click==8.1.7 configparser==5.3.0 cookiecutter==2.1.1 coverage==4.5.4 @@ -25,8 +25,8 @@ docutils==0.19 exceptiongroup==1.0.0rc9 filelock==3.8.0 flake8==3.9.2 +flask==3.0.3 h11==0.14.0 -robotframework-httpctrl==0.3.1 idna==3.4 iniconfig==1.1.1 invoke==1.6.0 @@ -69,10 +69,12 @@ redis==4.3.4 rellu==0.7 requests==2.28.1 rfc3987==1.3.8 -robotframework==6.0 +robotframework==7.0 +robotframework-archivelibrary == 0.4.2 +robotframework-httpctrl==0.3.1 robotframework-lint==1.1 robotframework-mongodb-library==3.2 -robotframework-pythonlibcore==3.0.0 +robotframework-pythonlibcore==4.4.1 robotframework-requests==0.9.3 robotframework-seleniumlibrary==6.0.0 robotframework-sshlibrary==3.8.0 @@ -91,11 +93,11 @@ tox==3.26.0 tqdm==4.64.1 trio==0.22.0 trio-websocket==0.9.2 -typing-extensions==3.10.0.2 +typing-extensions==4.11.0 urllib3==1.26.12 virtualenv==20.16.5 -watchdog==0.9.0 +watchdog==4.0.0 webdrivermanager==0.10.0 -wrapt==1.14.1 +wrapt==1.15.0 wsproto==1.2.0 -xlrd==2.0.1 \ No newline at end of file +xlrd==2.0.1 diff --git a/tools/robot/basicRobotInstall.sh b/tools/robot/basicRobotInstall.sh index 511821fbab10d55f80dd2a13af1d60035ca0bd40..ae0bd6bd8b1bdedccc34b4d6152bffc8bb5f467b 100644 --- a/tools/robot/basicRobotInstall.sh +++ b/tools/robot/basicRobotInstall.sh @@ -1,7 +1,6 @@ #!/bin/bash echo "Installing basic software related with robotFramework" -source /opt/venv/bin/activate; +source /opt/venv/bin/activate pip install --upgrade pip -pip install --upgrade robotframework; -pip install -r $1 +pip install -r $1 echo "Robot framework installed"