From e8d00c9e00b3b3890b4a5b5de03a38895d1caaeb Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Thu, 24 Oct 2024 12:57:24 +0200 Subject: [PATCH 1/6] Initial modification --- .../controllers/default_controller.py | 20 ++--- .../published_apis/core/resources.py | 5 +- .../core/serviceapidescriptions.py | 32 ++++---- .../published_apis/db/db.py | 8 +- services/run_capif_tests.sh | 3 +- .../capif_api_publish_service.robot | 61 ++++++++++++++ tests/features/__init__.robot | 11 +++ tests/libraries/bodyRequests.py | 2 +- tests/libraries/common/bodyRequests.py | 12 ++- tests/libraries/helpers.py | 27 ++++++- tests/resources/common.resource | 1 + tests/resources/common/basicRequests.robot | 80 +++++++++++++------ 12 files changed, 199 insertions(+), 63 deletions(-) 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 699208b..f15d41d 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/controllers/default_controller.py @@ -1,24 +1,18 @@ -import connexion -from ..models.service_api_description import ServiceAPIDescription # noqa: E501 from ..core.serviceapidescriptions import PublishServiceOperations +from ..models.service_api_description import ServiceAPIDescription # noqa: E501 -from published_apis.models.problem_details import ProblemDetails # noqa: E501 -from published_apis.models.service_api_description import ServiceAPIDescription # noqa: E501 -from published_apis import util - -from flask import Response, request, current_app +from flask import request, current_app from cryptography import x509 from cryptography.hazmat.backends import default_backend from ..core.validate_user import ControlAccess from functools import wraps -import asyncio - service_operations = PublishServiceOperations() valid_user = ControlAccess() + def cert_validation(): def _cert_validation(f): @wraps(f) @@ -41,12 +35,18 @@ def cert_validation(): if result is not None: return result + + result = service_operations.check_apf(args["apfId"]) + + if result is not None: + return result result = f(**kwargs) return result return __cert_validation return _cert_validation +# @cert_validation() def apf_id_service_apis_get(apf_id): # noqa: E501 """apf_id_service_apis_get @@ -62,7 +62,7 @@ def apf_id_service_apis_get(apf_id): # noqa: E501 return res - +# @cert_validation() def apf_id_service_apis_post(apf_id, body): # noqa: E501 """apf_id_service_apis_post diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/resources.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/resources.py index efbe3c2..641d402 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/resources.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/resources.py @@ -1,7 +1,8 @@ -from abc import ABC, abstractmethod +from abc import ABC from db.db import MongoDatabse + class Resource(ABC): def __init__(self): - self.db = MongoDatabse() \ No newline at end of file + self.db = MongoDatabse() diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py index c1792b4..165952f 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 @@ -18,7 +18,7 @@ service_api_not_found_message = "Service API not found" class PublishServiceOperations(Resource): - def __check_apf(self, apf_id): + def check_apf(self, apf_id): providers_col = self.db.get_col_by_name(self.db.capif_provider_col) current_app.logger.debug("Checking apf id") @@ -49,10 +49,10 @@ class PublishServiceOperations(Resource): current_app.logger.debug("Geting service apis") - result = self.__check_apf(apf_id) + # result = self.__check_apf(apf_id) - if result != None: - return result + # 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}) @@ -85,10 +85,10 @@ class PublishServiceOperations(Resource): try: current_app.logger.debug("Publishing service") - result = self.__check_apf(apf_id) + # result = self.__check_apf(apf_id) - if result != None: - return result + # if result != None: + # return result service = mycol.find_one( {"api_name": serviceapidescription.api_name}) @@ -144,10 +144,10 @@ class PublishServiceOperations(Resource): try: current_app.logger.debug( "Geting service api with id: " + service_api_id) - result = self.__check_apf(apf_id) + # result = self.__check_apf(apf_id) - if result != None: - return result + # 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, @@ -176,10 +176,10 @@ class PublishServiceOperations(Resource): current_app.logger.debug( "Removing api service with id: " + service_api_id) - result = self.__check_apf(apf_id) + # result = self.__check_apf(apf_id) - if result != None: - return result + # if result != None: + # return result my_query = {'apf_id': apf_id, 'api_id': service_api_id} serviceapidescription = mycol.find_one(my_query) @@ -217,10 +217,10 @@ class PublishServiceOperations(Resource): current_app.logger.debug( "Updating service api with id: " + service_api_id) - result = self.__check_apf(apf_id) + # result = self.__check_apf(apf_id) - if result != None: - return result + # if result != None: + # return result my_query = {'apf_id': apf_id, 'api_id': service_api_id} serviceapidescription = mycol.find_one(my_query) diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/db/db.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/db/db.py index 643dda4..3885c58 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/db/db.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/db/db.py @@ -13,6 +13,13 @@ if monitoring_value == "true": class MongoDatabse(): + # _instance = None + + # def __new__(cls): + # if cls._instance is None: + # cls._instance = super(MongoDatabse, cls).__new__(cls) + # return cls._instance + def __init__(self): self.config = Config().get_config() self.db = self.__connect() @@ -20,7 +27,6 @@ class MongoDatabse(): self.capif_provider_col = self.config['mongo']['capif_provider_col'] self.certs_col = self.config['mongo']['certs_col'] - def get_col_by_name(self, name): return self.db[name].with_options(codec_options=CodecOptions(tz_aware=True)) diff --git a/services/run_capif_tests.sh b/services/run_capif_tests.sh index 3316f18..b936f6f 100755 --- a/services/run_capif_tests.sh +++ b/services/run_capif_tests.sh @@ -18,8 +18,7 @@ CAPIF_HTTPS_PORT=443 # VAULT access configuration CAPIF_VAULT=vault CAPIF_VAULT_PORT=8200 -CAPIF_VAULT_TOKEN=read-ca-token - +CAPIF_VAULT_TOKEN=dev-only-token MOCK_SERVER_URL=http://mock-server:9100 NOTIFICATION_DESTINATION_URL=http://mock-server:9100 diff --git a/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot b/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot index 5df5c4c..7cc95b9 100644 --- a/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot +++ b/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot @@ -324,3 +324,64 @@ Delete APIs Published by NON Authorised apfId ... status=401 ... detail=User not authorized ... cause=Certificate not authorized + + +Retrieve single APIs Published by Authorised apfId TEST + [Tags] capif_api_publish_service-x + # Register APF + ${register_user_info}= Provider Default Registration + + ${service_api_description_published_1} ${resource_url} ${request_body}= Publish Service Api + ... ${register_user_info} + ... service_1 + ${service_api_description_published_2} ${resource_url} ${request_body}= Publish Service Api + ... ${register_user_info} + ... service_2 + + # Store apiId1 + ${serviceApiId1}= Set Variable ${service_api_description_published_1['apiId']} + ${serviceApiId2}= Set Variable ${service_api_description_published_2['apiId']} + + # Retrieve Services 1 + ${resp}= Get Request Capif + ... /published-apis/v1/${register_user_info['apf_id']}/service-apis/${serviceApiId1} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${APF_PROVIDER_USERNAME} + + Check Response Variable Type And Values ${resp} 200 ServiceAPIDescription + Dictionaries Should Be Equal ${resp.json()} ${service_api_description_published_1} + + # Retrieve Services 1 + ${resp}= Get Request Capif + ... /published-apis/v1/${register_user_info['apf_id']}/service-apis/${serviceApiId2} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${APF_PROVIDER_USERNAME} + + Check Response Variable Type And Values ${resp} 200 ServiceAPIDescription + Dictionaries Should Be Equal ${resp.json()} ${service_api_description_published_2} + + ${resp}= Get Request Capif + ... /helper/getServices + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${SUPERADMIN_USERNAME} + + Log Dictionary ${resp.json()} + + ${resp}= Delete Request Capif + ... ${register_user_info['resource_url'].path} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${AMF_PROVIDER_USERNAME} + + Call Method ${CAPIF_USERS} remove_capif_users_entry ${register_user_info['resource_url'].path} + + ${resp}= Get Request Capif + ... /helper/getServices + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${SUPERADMIN_USERNAME} + + Log Dictionary ${resp.json()} diff --git a/tests/features/__init__.robot b/tests/features/__init__.robot index 19e0fdb..3ac73ab 100644 --- a/tests/features/__init__.robot +++ b/tests/features/__init__.robot @@ -45,6 +45,8 @@ Prepare environment END # Obtain ca root certificate Retrieve Ca Root + + Retrieve Superadmin Cert Reset Testing Environment @@ -54,3 +56,12 @@ Retrieve Ca Root Status Should Be 200 ${resp} Log ${resp.json()['data']['data']['ca']} Store In File ca.crt ${resp.json()['data']['data']['ca']} + +Retrieve Superadmin Cert + [Documentation] This keyword retrieve ca.root from CAPIF and store it at ca.crt in order to use at TLS communications + ${resp}= Obtain Superadmin Cert From Vault /v1/pki_int/sign/my-ca ${CAPIF_HTTP_VAULT_URL} + Status Should Be 200 ${resp} + Log Dictionary ${resp.json()} + Log ${resp.json()['data']['certificate']} + Store In File ${SUPERADMIN_USERNAME}.crt ${resp.json()['data']['certificate']} + diff --git a/tests/libraries/bodyRequests.py b/tests/libraries/bodyRequests.py index 5e45124..b458689 100644 --- a/tests/libraries/bodyRequests.py +++ b/tests/libraries/bodyRequests.py @@ -5,4 +5,4 @@ from api_publish_service.bodyRequests import * from api_events.bodyRequests import * from security_api.bodyRequests import * from api_provider_management.bodyRequests import * - +from vault_requests.bodyRequests import * diff --git a/tests/libraries/common/bodyRequests.py b/tests/libraries/common/bodyRequests.py index 1d4ec14..c1c3061 100644 --- a/tests/libraries/common/bodyRequests.py +++ b/tests/libraries/common/bodyRequests.py @@ -1,4 +1,3 @@ -from operator import contains import re import json from xmlrpc.client import boolean @@ -116,6 +115,7 @@ def check_uri(input,rule): else: raise Exception(rule + " is not accomplish rfc3986 rule ("+input+")") + def check_regex(input, regex): matched = re.match(regex, input) is_match = bool(matched) @@ -123,3 +123,13 @@ def check_regex(input, regex): print("Regex match") else: raise Exception("Input(" + input + ") not match regex (" + regex + ")") + + +def vault_sign_superadmin_certificate_body(csr_request): + data = { + 'format': 'pem_bundle', + 'ttl': '43000h', + 'csr': csr_request, + 'common_name': "superadmin" + } + return data diff --git a/tests/libraries/helpers.py b/tests/libraries/helpers.py index c7806f1..1b974f2 100644 --- a/tests/libraries/helpers.py +++ b/tests/libraries/helpers.py @@ -1,13 +1,10 @@ -import requests import re -import pandas as pd from urllib.parse import urlparse from OpenSSL.crypto import (dump_certificate_request, dump_privatekey, PKey, TYPE_RSA, X509Req) from OpenSSL.SSL import FILETYPE_PEM import socket import copy -import json import pickle @@ -142,23 +139,45 @@ def create_scope(aef_id, api_name): return data + def read_dictionary(file_path): with open(file_path, 'rb') as fp: data = pickle.load(fp) print('Dictionary loaded') return data + def write_dictionary(file_path, data): with open(file_path, 'wb') as fp: pickle.dump(data, fp) print('dictionary saved successfully to file ' + file_path) + def filter_users_by_prefix_username(users, prefix): if prefix.strip() == "": raise Exception('prefix value must contain some value') - filtered_users=list() + filtered_users = list() for user in users: if user['username'].startswith(prefix): filtered_users.append(user['username']) return filtered_users + + +def sign_csr_body(username, public_key): + data = { + "csr": public_key.decode("utf-8"), + "mode": "client", + "filename": username + } + return data + + +def vault_sign_superadmin_certificate_body(csr_request): + data = { + "format": "pem_bundle", + "ttl": "43000h", + "csr": csr_request.decode("utf-8"), + "common_name": "superadmin" + } + return data diff --git a/tests/resources/common.resource b/tests/resources/common.resource index 0acd867..d5cc35e 100644 --- a/tests/resources/common.resource +++ b/tests/resources/common.resource @@ -8,6 +8,7 @@ Resource /opt/robot-tests/tests/resources/common/basicRequests.robot *** Variables *** +${SUPERADMIN_USERNAME} ROBOT_TESTING_SUPERADMIN ${INVOKER_USERNAME} ROBOT_TESTING_INVOKER ${PROVIDER_USERNAME} ROBOT_TESTING_PROVIDER ${APF_PROVIDER_USERNAME} APF_${PROVIDER_USERNAME} diff --git a/tests/resources/common/basicRequests.robot b/tests/resources/common/basicRequests.robot index 838086f..9d6b29b 100644 --- a/tests/resources/common/basicRequests.robot +++ b/tests/resources/common/basicRequests.robot @@ -6,7 +6,7 @@ Library Collections Library OperatingSystem Library XML Library Telnet -Library String +Library String *** Variables *** @@ -85,7 +85,7 @@ Create Register Admin Session ## NEW REQUESTS TO REGISTER Post Request Admin Register - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${json}=${NONE} @@ -115,7 +115,7 @@ Post Request Admin Register RETURN ${resp} Get Request Admin Register - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${server}=${NONE} @@ -149,7 +149,7 @@ Delete User Admin Register Request # NEW REQUESTS END Post Request Capif - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${json}=${NONE} @@ -179,7 +179,7 @@ Post Request Capif RETURN ${resp} Get Request Capif - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${server}=${NONE} @@ -205,7 +205,7 @@ Get Request Capif RETURN ${resp} Get CA Vault - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${server}=${NONE} @@ -229,10 +229,39 @@ Get CA Vault ... verify=${verify} ... cert=${cert} RETURN ${resp} - RETURN ${response} + +Obtain Superadmin Cert From Vault + [Timeout] ${REQUESTS_TIMEOUT} + [Arguments] + ... ${endpoint} + ... ${server}=${NONE} + ... ${access_token}=${NONE} + ... ${auth}=${NONE} + ... ${verify}=${FALSE} + ... ${cert}=${NONE} + ... ${username}=${NONE} + + ${headers}= Create CAPIF Session ${server} ${access_token} vault_token=${CAPIF_VAULT_TOKEN} + + IF '${username}' != '${NONE}' + ${cert}= Set variable ${{ ('${username}.crt','${username}.key') }} + END + + ${csr_request}= Create User Csr ${SUPERADMIN_USERNAME} cn=superadmin + ${json}= Vault Sign Superadmin Certificate Body ${csr_request} + + ${resp}= Post On Session + ... apisession + ... ${endpoint} + ... json=${json} + ... headers=${headers} + ... expected_status=any + ... verify=${verify} + ... cert=${cert} + RETURN ${resp} Put Request Capif - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${json}=${NONE} @@ -261,7 +290,7 @@ Put Request Capif RETURN ${resp} Patch Request Capif - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${json}=${NONE} @@ -274,7 +303,7 @@ Patch Request Capif ${headers}= Create CAPIF Session ${server} ${access_token} - Set To Dictionary ${headers} Content-Type application/merge-patch+json + Set To Dictionary ${headers} Content-Type application/merge-patch+json IF '${username}' != '${NONE}' ${cert}= Set variable ${{ ('${username}.crt','${username}.key') }} @@ -292,7 +321,7 @@ Patch Request Capif RETURN ${resp} Delete Request Capif - [Timeout] ${REQUESTS_TIMEOUT} + [Timeout] ${REQUESTS_TIMEOUT} [Arguments] ... ${endpoint} ... ${server}=${NONE} @@ -439,22 +468,23 @@ Delete User At Register [Documentation] (Administrator) This Keyword delete a user from register. [Arguments] ${username}=${NONE} ${uuid}=${NONE} ${user_uuid}= Set Variable ${uuid} - ${environment_users}= Set Variable ${TRUE} + ${environment_users}= Set Variable ${TRUE} IF "${username}" != "${NONE}" ${user_uuid}= Call Method ${CAPIF_USERS} get_user_uuid ${username} END - IF "${user_uuid}" == "${NONE}" - ${user_uuid}= Get User Uuid At Register ${username} - ${environment_users}= Set Variable ${FALSE} + IF "${user_uuid}" == "${NONE}" + ${user_uuid}= Get User Uuid At Register ${username} + ${environment_users}= Set Variable ${FALSE} END - ${resp}= Delete User Admin Register Request ${user_uuid} + ${resp}= Delete User Admin Register Request ${user_uuid} Should Be Equal As Strings ${resp.status_code} 204 + q - IF ${environment_users} + IF ${environment_users} Call Method ${CAPIF_USERS} remove_register_users_entry ${user_uuid} END @@ -473,23 +503,22 @@ Get List of Users At Register Get User Uuid At Register [Documentation] (Administrator) This Keyword retrieve a list of users and search uuid of user passed by parameters [Arguments] ${username} - ${users}= Get List of Users At Register + ${users}= Get List of Users At Register # Find the first user with username indicated ${user_uuid}= Set Variable &{EMPTY} - FOR ${user} IN @{users} - IF "${user['username']}" == "${username}" - ${user_uuid}= Set Variable ${user['uuid']} + FOR ${user} IN @{users} + IF "${user['username']}" == "${username}" + ${user_uuid}= Set Variable ${user['uuid']} BREAK END END - IF "${user_uuid}" == "${EMPTY}" + IF "${user_uuid}" == "${EMPTY}" Log ${username} not found in Register END - - RETURN ${user_uuid} + RETURN ${user_uuid} Get Auth For User [Documentation] (User) This Keyword retrieve token to be used by user towards first interaction with CCF. @@ -567,7 +596,7 @@ Remove Resource ... verify=ca.crt ... username=${management_cert} - Run Keyword and Continue On Failure Status Should Be 204 ${resp} + Run Keyword and Continue On Failure Status Should Be 204 ${resp} Delete User At Register username=${username} @@ -780,4 +809,3 @@ Create Security Context Between invoker and provider ... username=${register_user_info_invoker['management_cert']} Check Response Variable Type And Values ${resp} 201 ServiceSecurity - -- GitLab From a30f71545ae14ce020e4333b037afe5e41c6c749 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Fri, 25 Oct 2024 14:43:08 +0200 Subject: [PATCH 2/6] Checking 2 apfs with one serviceapi subscriber by each one and provider removed --- .../capif_api_provider_management.robot | 2 +- .../capif_api_publish_service.robot | 24 ++- tests/resources/common/basicRequests.robot | 162 ++++++++++++++---- 3 files changed, 149 insertions(+), 39 deletions(-) diff --git a/tests/features/CAPIF Api Provider Management/capif_api_provider_management.robot b/tests/features/CAPIF Api Provider Management/capif_api_provider_management.robot index 9e4d81e..bda7373 100644 --- a/tests/features/CAPIF Api Provider Management/capif_api_provider_management.robot +++ b/tests/features/CAPIF Api Provider Management/capif_api_provider_management.robot @@ -19,7 +19,7 @@ Register Api Provider [Tags] capif_api_provider_management-1 # Register Provider User An create Certificates for each function ${register_user_info}= Register User At Jwt Auth Provider - ... username=${PROVIDER_USERNAME} role=${PROVIDER_ROLE} + ... username=${PROVIDER_USERNAME} # Create provider Registration Body ${apf_func_details}= Create Api Provider Function Details diff --git a/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot b/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot index 7cc95b9..d91daa4 100644 --- a/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot +++ b/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot @@ -329,7 +329,7 @@ Delete APIs Published by NON Authorised apfId Retrieve single APIs Published by Authorised apfId TEST [Tags] capif_api_publish_service-x # Register APF - ${register_user_info}= Provider Default Registration + ${register_user_info}= Provider Default Registration total_apf_roles=2 ${service_api_description_published_1} ${resource_url} ${request_body}= Publish Service Api ... ${register_user_info} @@ -337,6 +337,7 @@ Retrieve single APIs Published by Authorised apfId TEST ${service_api_description_published_2} ${resource_url} ${request_body}= Publish Service Api ... ${register_user_info} ... service_2 + ... apf_username=${APF_PROVIDER_USERNAME}_1 # Store apiId1 ${serviceApiId1}= Set Variable ${service_api_description_published_1['apiId']} @@ -354,10 +355,10 @@ Retrieve single APIs Published by Authorised apfId TEST # Retrieve Services 1 ${resp}= Get Request Capif - ... /published-apis/v1/${register_user_info['apf_id']}/service-apis/${serviceApiId2} + ... /published-apis/v1/${register_user_info['apf_roles']['${APF_PROVIDER_USERNAME}_1']['apf_id']}/service-apis/${serviceApiId2} ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt - ... username=${APF_PROVIDER_USERNAME} + ... username=${APF_PROVIDER_USERNAME}_1 Check Response Variable Type And Values ${resp} 200 ServiceAPIDescription Dictionaries Should Be Equal ${resp.json()} ${service_api_description_published_2} @@ -385,3 +386,20 @@ Retrieve single APIs Published by Authorised apfId TEST ... username=${SUPERADMIN_USERNAME} Log Dictionary ${resp.json()} + + Run Keyword And Continue On Failure Length Should Be ${resp.json()['services']} 0 + + ${resp}= Delete Request Capif + ... /published-apis/v1/${register_user_info['apf_roles']['${APF_PROVIDER_USERNAME}_1']['apf_id']}/service-apis/${serviceApiId2} + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${SUPERADMIN_USERNAME} + + ${resp}= Get Request Capif + ... /helper/getServices + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${SUPERADMIN_USERNAME} + + Log Dictionary ${resp.json()} + Length Should Be ${resp.json()['services']} 0 \ No newline at end of file diff --git a/tests/resources/common/basicRequests.robot b/tests/resources/common/basicRequests.robot index 9d6b29b..0e37eef 100644 --- a/tests/resources/common/basicRequests.robot +++ b/tests/resources/common/basicRequests.robot @@ -9,6 +9,7 @@ Library Telnet Library String + *** Variables *** ${CAPIF_AUTH} ${EMPTY} ${CAPIF_BEARER} ${EMPTY} @@ -396,19 +397,65 @@ Register User At Jwt Auth RETURN ${register_user_info} Register User At Jwt Auth Provider - [Arguments] ${username} ${role} ${password}=password ${description}=Testing + [Arguments] + ... ${username} + ... ${password}=password + ... ${description}=Testing + ... ${total_apf_roles}=1 + ... ${total_aef_roles}=1 + ... ${total_amf_roles}=1 + + ${apf_roles}= Create Dictionary + ${default_apf_username}= Set Variable APF_${username} + FOR ${index} IN RANGE ${total_apf_roles} + ${apf_username}= Set Variable ${default_apf_username}_${index} + IF ${index} == 0 + ${apf_username}= Set Variable ${default_apf_username} + END + ${apf_csr_request}= Create User Csr ${apf_username} apf + ${apf_role}= + ... Create Dictionary + ... username=${apf_username} + ... csr_request=${apf_csr_request} + ... role=APF + Set To Dictionary ${apf_roles} ${apf_username}=${apf_role} + END - ${apf_username}= Set Variable APF_${username} - ${aef_username}= Set Variable AEF_${username} - ${amf_username}= Set Variable AMF_${username} + ${aef_roles}= Create Dictionary + ${default_aef_username}= Set Variable AEF_${username} + FOR ${index} IN RANGE ${total_aef_roles} + ${aef_username}= Set Variable ${default_aef_username}_${index} + IF ${index} == 0 + ${aef_username}= Set Variable ${default_aef_username} + END + ${aef_csr_request}= Create User Csr ${aef_username} aef + ${aef_role}= + ... Create Dictionary + ... username=${aef_username} + ... csr_request=${aef_csr_request} + ... role=AEF + Set To Dictionary ${aef_roles} ${aef_username}=${aef_role} + END + + ${amf_roles}= Create Dictionary + ${default_amf_username}= Set Variable AMF_${username} + FOR ${index} IN RANGE ${total_amf_roles} + ${amf_username}= Set Variable ${default_amf_username}_${index} + IF ${index} == 0 + ${amf_username}= Set Variable ${default_amf_username} + END + ${amf_csr_request}= Create User Csr ${amf_username} amf + ${amf_role}= + ... Create Dictionary + ... username=${amf_username} + ... csr_request=${amf_csr_request} + ... role=AMF + Set To Dictionary ${amf_roles} ${amf_username}=${amf_role} + END # Create a certificate for each kind of role under provider ${csr_request}= Create User Csr ${username} provider - ${apf_csr_request}= Create User Csr ${apf_username} apf - ${aef_csr_request}= Create User Csr ${aef_username} aef - ${amf_csr_request}= Create User Csr ${amf_username} amf - # Register provider ${resp}= Create User At Register ... ${username} @@ -423,12 +470,15 @@ Register User At Jwt Auth Provider ${register_user_info}= Create Dictionary ... netappID=${resp.json()['uuid']} ... csr_request=${csr_request} - ... apf_csr_request=${apf_csr_request} - ... aef_csr_request=${aef_csr_request} - ... amf_csr_request=${amf_csr_request} - ... apf_username=${apf_username} - ... aef_username=${aef_username} - ... amf_username=${amf_username} + ... apf_username=${default_apf_username} + ... aef_username=${default_aef_username} + ... amf_username=${default_amf_username} + ... apf_csr_request=${apf_roles['${default_apf_username}']['csr_request']} + ... aef_csr_request=${aef_roles['${default_aef_username}']['csr_request']} + ... amf_csr_request=${amf_roles['${default_amf_username}']['csr_request']} + ... apf_roles=${apf_roles} + ... aef_roles=${aef_roles} + ... amf_roles=${amf_roles} ... &{resp.json()} ... &{get_auth_response} @@ -640,20 +690,16 @@ Invoker Default Onboarding Provider Registration [Arguments] ${register_user_info} + ${api_prov_funcs}= Create List + # Create provider Registration Body - ${apf_func_details}= Create Api Provider Function Details - ... ${register_user_info['apf_username']} - ... ${register_user_info['apf_csr_request']} - ... APF - ${aef_func_details}= Create Api Provider Function Details - ... ${register_user_info['aef_username']} - ... ${register_user_info['aef_csr_request']} - ... AEF - ${amf_func_details}= Create Api Provider Function Details - ... ${register_user_info['amf_username']} - ... ${register_user_info['amf_csr_request']} - ... AMF - ${api_prov_funcs}= Create List ${apf_func_details} ${aef_func_details} ${amf_func_details} + FOR ${key} ${value} IN &{register_user_info['apf_roles']} &{register_user_info['aef_roles']} &{register_user_info['amf_roles']} + ${func_details}= Create Api Provider Function Details + ... ${key} + ... ${value['csr_request']} + ... ${value['role']} + Append To List ${api_prov_funcs} ${func_details} + END ${request_body}= Create Api Provider Enrolment Details Body ... ${register_user_info['access_token']} @@ -676,12 +722,29 @@ Provider Registration FOR ${prov} IN @{resp.json()['apiProvFuncs']} Log Dictionary ${prov} Store In File ${prov['apiProvFuncInfo']}.crt ${prov['regInfo']['apiProvCert']} + Log Dictionary ${register_user_info} + Log ${register_user_info['apf_username']} IF "${prov['apiProvFuncRole']}" == "APF" - Set To Dictionary ${register_user_info} apf_id=${prov['apiProvFuncId']} + IF "${prov['apiProvFuncInfo']}" == "${register_user_info['apf_username']}" + Set To Dictionary ${register_user_info} apf_id=${prov['apiProvFuncId']} + END + Set To Dictionary + ... ${register_user_info['apf_roles']['${prov['apiProvFuncInfo']}']} + ... apf_id=${prov['apiProvFuncId']} ELSE IF "${prov['apiProvFuncRole']}" == "AEF" - Set To Dictionary ${register_user_info} aef_id=${prov['apiProvFuncId']} + IF "${prov['apiProvFuncInfo']}" == "${register_user_info['aef_username']}" + Set To Dictionary ${register_user_info} aef_id=${prov['apiProvFuncId']} + END + Set To Dictionary + ... ${register_user_info['aef_roles']['${prov['apiProvFuncInfo']}']} + ... aef_id=${prov['apiProvFuncId']} ELSE IF "${prov['apiProvFuncRole']}" == "AMF" - Set To Dictionary ${register_user_info} amf_id=${prov['apiProvFuncId']} + IF "${prov['apiProvFuncInfo']}" == "${register_user_info['amf_username']}" + Set To Dictionary ${register_user_info} amf_id=${prov['apiProvFuncId']} + END + Set To Dictionary + ... ${register_user_info['amf_roles']['${prov['apiProvFuncInfo']}']} + ... amf_id=${prov['apiProvFuncId']} ELSE Fail "${prov['apiProvFuncRole']} is not valid role" END @@ -703,10 +766,20 @@ Provider Registration RETURN ${register_user_info} Provider Default Registration - [Arguments] ${provider_username}=${PROVIDER_USERNAME} + [Arguments] + ... ${provider_username}=${PROVIDER_USERNAME} + ... ${total_apf_roles}=1 + ... ${total_aef_roles}=1 + ... ${total_amf_roles}=1 + ... ${apf_id}=${NONE} + ... ${apf_username}=${NONE} + # Register Provider ${register_user_info}= Register User At Jwt Auth Provider - ... username=${provider_username} role=${PROVIDER_ROLE} + ... username=${provider_username} + ... total_apf_roles=${total_apf_roles} + ... total_aef_roles=${total_aef_roles} + ... total_amf_roles=${total_amf_roles} ${register_user_info}= Provider Registration ${register_user_info} @@ -715,15 +788,34 @@ Provider Default Registration RETURN ${register_user_info} Publish Service Api - [Arguments] ${register_user_info_provider} ${service_name}=service_1 + [Arguments] + ... ${register_user_info_provider} + ... ${service_name}=service_1 + ... ${apf_id}=${NONE} + ... ${apf_username}=${NONE} + + ${apf_id_to_use}= Set Variable ${register_user_info_provider['apf_id']} + ${apf_username_to_use}= Set Variable ${register_user_info_provider['apf_username']} + IF "${apf_id}" != "${NONE}" and "${apf_id}" != "${register_user_info_provider['apf_id']}" + FOR ${apf_username} ${apf_role} IN &{register_user_info_provider['apf_roles']} + IF "${apf_role['apf_id']}" == "${apf_id}" + ${apf_id_to_use}= Set Variable ${apf_id} + ${apf_username_to_use}= Set Variable ${apf_username} + BREAK + END + END + ELSE IF "${apf_username}" != "${NONE}" and "${apf_username}" != "${register_user_info_provider['apf_username']}" + ${apf_id_to_use}= Set Variable ${register_user_info_provider['apf_roles']['${apf_username}']['apf_id']} + ${apf_username_to_use}= Set Variable ${apf_username} + END ${request_body}= Create Service Api Description ${service_name} ${register_user_info_provider['aef_id']} ${resp}= Post Request Capif - ... /published-apis/v1/${register_user_info_provider['apf_id']}/service-apis + ... /published-apis/v1/${apf_id_to_use}/service-apis ... json=${request_body} ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt - ... username=${register_user_info_provider['apf_username']} + ... username=${apf_username_to_use} Check Response Variable Type And Values ${resp} 201 ServiceAPIDescription Dictionary Should Contain Key ${resp.json()} apiId -- GitLab From 68ec4e064726db9f531e28537442acebe3f30327 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Mon, 28 Oct 2024 16:19:38 +0100 Subject: [PATCH 3/6] Updated all related logic of cert check and authorization on publish, and minor fix on provider --- .../core/provider_enrolment_details_api.py | 5 +- .../controllers/default_controller.py | 23 ++-- .../core/serviceapidescriptions.py | 110 +++++++++++------- .../published_apis/core/validate_user.py | 30 ++++- .../capif_api_publish_service.robot | 8 +- .../capif_security_api.robot | 8 +- 6 files changed, 117 insertions(+), 67 deletions(-) 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 30760b9..f1b2a0d 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 @@ -84,6 +84,9 @@ class ProviderManagementOperations(Resource): if isinstance(result, Response): return result + func_ids = list() + for provider_func in result["api_prov_funcs"]: + func_ids.append(provider_func['api_prov_func_id']) apf_id = [ provider_func['api_prov_func_id'] for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'APF' ] aef_id = [ provider_func['api_prov_func_id'] for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'AEF' ] amf_id = [ provider_func['api_prov_func_id'] for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'AMF' ] @@ -92,7 +95,7 @@ class ProviderManagementOperations(Resource): out = "The provider matching apiProvDomainId " + api_prov_dom_id + " was offboarded." current_app.logger.debug("Removed provider domain from database") - self.auth_manager.remove_auth_provider([apf_id[0], aef_id[0], amf_id[0]]) + self.auth_manager.remove_auth_provider(func_ids) 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) 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 f15d41d..193dae4 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 @@ -30,12 +30,15 @@ def cert_validation(): if cn != "superadmin": cert_signature = cert.signature.hex() + service_api_id = None + if 'serviceApiId' in args: + service_api_id = args["serviceApiId"] result = valid_user.validate_user_cert( - args["apfId"], args["serviceApiId"], cert_signature) + args["apfId"], cert_signature, service_api_id) if result is not None: return result - + result = service_operations.check_apf(args["apfId"]) if result is not None: @@ -46,7 +49,8 @@ def cert_validation(): return __cert_validation return _cert_validation -# @cert_validation() + +@cert_validation() def apf_id_service_apis_get(apf_id): # noqa: E501 """apf_id_service_apis_get @@ -62,7 +66,8 @@ def apf_id_service_apis_get(apf_id): # noqa: E501 return res -# @cert_validation() + +@cert_validation() def apf_id_service_apis_post(apf_id, body): # noqa: E501 """apf_id_service_apis_post @@ -77,20 +82,22 @@ def apf_id_service_apis_post(apf_id, body): # noqa: E501 """ current_app.logger.info("Publishing service") - supp_feat_dict = ServiceAPIDescription.return_supp_feat_dict(body['supportedFeatures']) + supp_feat_dict = ServiceAPIDescription.return_supp_feat_dict( + body['supportedFeatures']) vendor_specific = [] if request.is_json: if supp_feat_dict['VendorExt']: aef_profile_array = body['aefProfiles'] - for (profile,i) in zip(aef_profile_array, range(len(aef_profile_array))): + for (profile, i) in zip(aef_profile_array, range(len(aef_profile_array))): for key, val in profile.items(): if 'vendorSpecific' in key: vendor_specific.append((i, key, val)) body = ServiceAPIDescription.from_dict(request.get_json()) - res = service_operations.add_serviceapidescription(apf_id, body, vendor_specific) + res = service_operations.add_serviceapidescription( + apf_id, body, vendor_specific) return res @@ -114,6 +121,7 @@ def apf_id_service_apis_service_api_id_delete(service_api_id, apf_id): # noqa: return res + @cert_validation() def apf_id_service_apis_service_api_id_get(service_api_id, apf_id): # noqa: E501 """apf_id_service_apis_service_api_id_get @@ -132,6 +140,7 @@ def apf_id_service_apis_service_api_id_get(service_api_id, apf_id): # noqa: E50 return res + @cert_validation() def apf_id_service_apis_service_api_id_put(service_api_id, apf_id, body): # noqa: E501 """apf_id_service_apis_service_api_id_put diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py index 165952f..df4559d 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py @@ -1,10 +1,10 @@ from pymongo import ReturnDocument import secrets -from flask import current_app, Flask, Response +from flask import current_app from .resources import Resource from datetime import datetime -from ..util import dict_to_camel_case, clean_empty, serialize_clean_camel_case,clean_n_camel_case +from ..util import dict_to_camel_case, clean_empty, clean_n_camel_case from .responses import internal_server_error, forbidden_error, not_found_error, unauthorized_error, make_response from .auth_manager import AuthManager from .redis_event import RedisEvent @@ -27,13 +27,17 @@ class PublishServiceOperations(Resource): if provider is None: current_app.logger.error("Publisher not exist") - return unauthorized_error(detail="Publisher not existing", cause="Publisher id not found") + return unauthorized_error( + detail="Publisher not existing", + cause="Publisher id not found") list_apf_ids = [func["api_prov_func_id"] for func in provider["api_prov_funcs"] if func["api_prov_func_role"] == "APF"] if apf_id not in list_apf_ids: current_app.logger.debug("This id not belongs to APF") - return unauthorized_error(detail="You are not a publisher", cause="This API is only available for publishers") + return unauthorized_error( + detail="You are not a publisher", + cause="This API is only available for publishers") return None @@ -49,13 +53,19 @@ class PublishServiceOperations(Resource): current_app.logger.debug("Geting service apis") - # result = self.__check_apf(apf_id) - - # if result != None: - # return result - - service = mycol.find({"apf_id": apf_id}, {"_id": 0, "api_name": 1, "api_id": 1, "aef_profiles": 1, "description": 1, - "supported_features": 1, "shareable_info": 1, "service_api_category": 1, "api_supp_feats": 1, "pub_api_path": 1, "ccf_id": 1}) + service = mycol.find( + {"apf_id": apf_id}, + {"_id": 0, + "api_name": 1, + "api_id": 1, + "aef_profiles": 1, + "description": 1, + "supported_features": 1, + "shareable_info": 1, + "service_api_category": 1, + "api_supp_feats": 1, + "pub_api_path": 1, + "ccf_id": 1}) current_app.logger.debug(service) if service is None: current_app.logger.error("Not found services for this apf id") @@ -83,12 +93,7 @@ class PublishServiceOperations(Resource): mycol = self.db.get_col_by_name(self.db.service_api_descriptions) try: - current_app.logger.debug("Publishing service") - # result = self.__check_apf(apf_id) - - # if result != None: - # return result service = mycol.find_one( {"api_name": serviceapidescription.api_name}) @@ -105,7 +110,7 @@ class PublishServiceOperations(Resource): serviceapidescription_dict = serviceapidescription.to_dict() if vendor_specific: for vend_spec in vendor_specific: - for (profile,i) in zip(serviceapidescription_dict['aef_profiles'], range(len(serviceapidescription_dict['aef_profiles']))): + for (profile, i) in zip(serviceapidescription_dict['aef_profiles'], range(len(serviceapidescription_dict['aef_profiles']))): if i == vend_spec[0]: profile[vend_spec[1]] = vend_spec[2] rec.update(serviceapidescription_dict) @@ -116,20 +121,21 @@ class PublishServiceOperations(Resource): current_app.logger.debug("Service inserted in database") - res = make_response(object=clean_n_camel_case(serviceapidescription_dict), status=201) + res = make_response(object=clean_n_camel_case( + serviceapidescription_dict), status=201) res.headers['Location'] = "http://localhost:8080/published-apis/v1/" + \ - str(apf_id) + "/service-apis/" + str(api_id) + str(apf_id) + "/service-apis/" + str(api_id) if res.status_code == 201: current_app.logger.info("Service published") if serviceapidescription.api_status is None or len(serviceapidescription.api_status.aef_ids) > 0: current_app.logger.info("Service available") RedisEvent("SERVICE_API_AVAILABLE", "apiIds", - [str(api_id)]).send_event() + [str(api_id)]).send_event() else: current_app.logger.info("Service unavailable") RedisEvent("SERVICE_API_UNAVAILABLE", "apiIds", - [str(api_id)]).send_event() + [str(api_id)]).send_event() return res except Exception as e: @@ -144,17 +150,24 @@ class PublishServiceOperations(Resource): try: current_app.logger.debug( "Geting service api with id: " + service_api_id) - # result = self.__check_apf(apf_id) - - # if result != None: - # return result my_query = {'apf_id': apf_id, 'api_id': service_api_id} - service_api = mycol.find_one(my_query, {"_id": 0, "api_name": 1, "api_id": 1, "aef_profiles": 1, "description": 1, - "supported_features": 1, "shareable_info": 1, "service_api_category": 1, "api_supp_feats": 1, "pub_api_path": 1, "ccf_id": 1}) + service_api = mycol.find_one(my_query, {"_id": 0, + "api_name": 1, + "api_id": 1, + "aef_profiles": 1, + "description": 1, + "supported_features": 1, + "shareable_info": 1, + "service_api_category": 1, + "api_supp_feats": 1, + "pub_api_path": 1, + "ccf_id": 1}) if service_api is None: current_app.logger.error(service_api_not_found_message) - return not_found_error(detail=service_api_not_found_message, cause="No Service with specific credentials exists") + return not_found_error( + detail=service_api_not_found_message, + cause="No Service with specific credentials exists") my_service_api = dict_to_camel_case(service_api) my_service_api = clean_empty(my_service_api) @@ -176,17 +189,15 @@ class PublishServiceOperations(Resource): current_app.logger.debug( "Removing api service with id: " + service_api_id) - # result = self.__check_apf(apf_id) - - # if result != None: - # return result my_query = {'apf_id': apf_id, 'api_id': service_api_id} serviceapidescription = mycol.find_one(my_query) if serviceapidescription is None: current_app.logger.error(service_api_not_found_message) - return not_found_error(detail="Service API not existing", cause="Service API id not found") + return not_found_error( + detail="Service API not existing", + cause="Service API id not found") mycol.delete_one(my_query) @@ -208,7 +219,9 @@ class PublishServiceOperations(Resource): current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - def update_serviceapidescription(self, service_api_id, apf_id, service_api_description): + def update_serviceapidescription(self, + service_api_id, apf_id, + service_api_description): mycol = self.db.get_col_by_name(self.db.service_api_descriptions) @@ -217,11 +230,6 @@ class PublishServiceOperations(Resource): current_app.logger.debug( "Updating service api with id: " + service_api_id) - # result = self.__check_apf(apf_id) - - # if result != None: - # return result - my_query = {'apf_id': apf_id, 'api_id': service_api_id} serviceapidescription = mycol.find_one(my_query) @@ -232,8 +240,21 @@ class PublishServiceOperations(Resource): service_api_description = service_api_description.to_dict() service_api_description = clean_empty(service_api_description) - result = mycol.find_one_and_update(serviceapidescription, {"$set": service_api_description}, projection={"_id": 0, "api_name": 1, "api_id": 1, "aef_profiles": 1, "description": 1, - "supported_features": 1, "shareable_info": 1, "service_api_category": 1, "api_supp_feats": 1, "pub_api_path": 1, "ccf_id": 1}, return_document=ReturnDocument.AFTER, upsert=False) + result = mycol.find_one_and_update( + serviceapidescription, + {"$set": service_api_description}, + projection={"_id": 0, + "api_name": 1, + "api_id": 1, + "aef_profiles": 1, + "description": 1, + "supported_features": 1, + "shareable_info": 1, + "service_api_category": 1, + "api_supp_feats": 1, + "pub_api_path": 1, + "ccf_id": 1}, + return_document=ReturnDocument.AFTER, upsert=False) result = clean_empty(result) @@ -243,19 +264,18 @@ class PublishServiceOperations(Resource): 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() if "apiStatus" not in service_api_description_updated or len(service_api_description_updated["apiStatus"]["aefIds"]) > 0: current_app.logger.info("Service available") RedisEvent("SERVICE_API_AVAILABLE", "apiIds", - [str(service_api_id)]).send_event() + [str(service_api_id)]).send_event() else: current_app.logger.info("Service unavailable") RedisEvent("SERVICE_API_UNAVAILABLE", "apiIds", - [str(service_api_id)]).send_event() - + [str(service_api_id)]).send_event() return response diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/validate_user.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/validate_user.py index e73cf8a..9d0c69c 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/validate_user.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/validate_user.py @@ -9,21 +9,39 @@ from ..util import serialize_clean_camel_case class ControlAccess(Resource): - def validate_user_cert(self, apf_id, service_id, cert_signature): + def validate_user_cert(self, apf_id, cert_signature, service_id=None): cert_col = self.db.get_col_by_name(self.db.certs_col) try: - my_query = {'id':apf_id} + my_query = {'id': apf_id} cert_entry = cert_col.find_one(my_query) if cert_entry is not None: - if cert_entry["cert_signature"] != cert_signature or "services" not in cert_entry["resources"] or service_id not in cert_entry["resources"]["services"]: - prob = ProblemDetails(title="Unauthorized", detail="User not authorized", cause="You are not the owner of this resource") + is_user_owner = True + if cert_entry["cert_signature"] != cert_signature: + is_user_owner = False + elif service_id: + if "services" not in cert_entry["resources"]: + is_user_owner = False + elif cert_entry.get("resources") and cert_entry["resources"].get("services"): + if service_id not in cert_entry["resources"].get("services"): + is_user_owner = False + if is_user_owner == False: + current_app.logger.info("STEP3") + prob = ProblemDetails( + title="Unauthorized", + detail="User not authorized", + cause="You are not the owner of this resource") + current_app.logger.info("STEP4") prob = serialize_clean_camel_case(prob) - return Response(json.dumps(prob, cls=CustomJSONEncoder), status=401, mimetype="application/json") + current_app.logger.info("STEP5") + return Response( + json.dumps(prob, cls=CustomJSONEncoder), + status=401, + mimetype="application/json") except Exception as e: exception = "An exception occurred in validate apf" current_app.logger.error(exception + "::" + str(e)) - return internal_server_error(detail=exception, cause=str(e)) \ No newline at end of file + return internal_server_error(detail=exception, cause=str(e)) diff --git a/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot b/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot index d91daa4..892a400 100644 --- a/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot +++ b/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot @@ -284,10 +284,10 @@ Delete API Published by Authorised apfId with valid serviceApiId ... verify=ca.crt ... username=${APF_PROVIDER_USERNAME} - Check Response Variable Type And Values ${resp} 401 ProblemDetails - ... title=Unauthorized - ... detail=User not authorized - ... cause=You are not the owner of this resource + Check Response Variable Type And Values ${resp} 404 ProblemDetails + ... title=Not Found + ... detail=Service API not found + ... cause=No Service with specific credentials exists Delete APIs Published by Authorised apfId with invalid serviceApiId [Tags] capif_api_publish_service-12 diff --git a/tests/features/CAPIF Security Api/capif_security_api.robot b/tests/features/CAPIF Security Api/capif_security_api.robot index eb2d725..85b26ee 100644 --- a/tests/features/CAPIF Security Api/capif_security_api.robot +++ b/tests/features/CAPIF Security Api/capif_security_api.robot @@ -835,7 +835,6 @@ Retrieve access token with invalid client_id Retrieve access token with unsupported grant_type [Tags] capif_security_api-24 - Skip Test ${TEST_NAME} is not currently supported by CAPIF # Default Invoker Registration and Onboarding # Register APF ${register_user_info_provider}= Provider Default Registration @@ -890,9 +889,10 @@ Retrieve access token with unsupported grant_type Check Response Variable Type And Values ... ${resp} ... 400 - ... AccessTokenErr - ... error=unsupported_grant_type - ... error_description=Invalid value for `grant_type` \\(${grant_type}\\), must be one of \\['client_credentials'\\] - 'grant_type' + ... ProblemDetails + ... title=Bad Request + ... detail='not_valid' is not one of \\['client_credentials', 'authorization_code'\\] - 'grant_type' + ... status=400 Retrieve access token with invalid scope [Tags] capif_security_api-25 -- GitLab From 13cbf5950d05739f2937cf8a9dafdd1e933e7382 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Mon, 28 Oct 2024 16:44:39 +0100 Subject: [PATCH 4/6] new test on publish to check if all services related with provider where removed if use 2 apfs --- .../capif_api_publish_service.robot | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot b/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot index 892a400..d453068 100644 --- a/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot +++ b/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot @@ -16,7 +16,7 @@ ${SERVICE_API_ID_NOT_VALID} not-valid *** Test Cases *** Publish API by Authorised API Publisher - [Tags] capif_api_publish_service-1 smoke + [Tags] capif_api_publish_service-1 smoke # Register APF ${register_user_info}= Provider Default Registration @@ -325,12 +325,12 @@ Delete APIs Published by NON Authorised apfId ... detail=User not authorized ... cause=Certificate not authorized - -Retrieve single APIs Published by Authorised apfId TEST - [Tags] capif_api_publish_service-x - # Register APF +Check Two Published APIs with different APFs are removed when Provider is deleted + [Tags] capif_api_publish_service-14 + # Register APF with 2 APF roles ${register_user_info}= Provider Default Registration total_apf_roles=2 + # Publish APIs with both APFs ${service_api_description_published_1} ${resource_url} ${request_body}= Publish Service Api ... ${register_user_info} ... service_1 @@ -343,7 +343,7 @@ Retrieve single APIs Published by Authorised apfId TEST ${serviceApiId1}= Set Variable ${service_api_description_published_1['apiId']} ${serviceApiId2}= Set Variable ${service_api_description_published_2['apiId']} - # Retrieve Services 1 + # Retrieve Service1 ${resp}= Get Request Capif ... /published-apis/v1/${register_user_info['apf_id']}/service-apis/${serviceApiId1} ... server=${CAPIF_HTTPS_URL} @@ -353,7 +353,7 @@ Retrieve single APIs Published by Authorised apfId TEST Check Response Variable Type And Values ${resp} 200 ServiceAPIDescription Dictionaries Should Be Equal ${resp.json()} ${service_api_description_published_1} - # Retrieve Services 1 + # Retrieve Service2 ${resp}= Get Request Capif ... /published-apis/v1/${register_user_info['apf_roles']['${APF_PROVIDER_USERNAME}_1']['apf_id']}/service-apis/${serviceApiId2} ... server=${CAPIF_HTTPS_URL} @@ -363,8 +363,9 @@ Retrieve single APIs Published by Authorised apfId TEST Check Response Variable Type And Values ${resp} 200 ServiceAPIDescription Dictionaries Should Be Equal ${resp.json()} ${service_api_description_published_2} + # Get all services present at CCF ${resp}= Get Request Capif - ... /helper/getServices + ... /helper/getServices ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt ... username=${SUPERADMIN_USERNAME} @@ -378,16 +379,15 @@ Retrieve single APIs Published by Authorised apfId TEST ... username=${AMF_PROVIDER_USERNAME} Call Method ${CAPIF_USERS} remove_capif_users_entry ${register_user_info['resource_url'].path} - + ${resp}= Get Request Capif - ... /helper/getServices + ... /helper/getServices ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt ... username=${SUPERADMIN_USERNAME} Log Dictionary ${resp.json()} - - Run Keyword And Continue On Failure Length Should Be ${resp.json()['services']} 0 + ${services_present_on_ccf}= Get Length ${resp.json()['services']} ${resp}= Delete Request Capif ... /published-apis/v1/${register_user_info['apf_roles']['${APF_PROVIDER_USERNAME}_1']['apf_id']}/service-apis/${serviceApiId2} @@ -396,10 +396,13 @@ Retrieve single APIs Published by Authorised apfId TEST ... username=${SUPERADMIN_USERNAME} ${resp}= Get Request Capif - ... /helper/getServices + ... /helper/getServices ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt ... username=${SUPERADMIN_USERNAME} Log Dictionary ${resp.json()} - Length Should Be ${resp.json()['services']} 0 \ No newline at end of file + ${services_present_on_ccf_after_provider_deletion}= Get Length ${resp.json()['services']} + ${services_removed}= Evaluate ${services_present_on_ccf} - ${services_present_on_ccf_after_provider_deletion} + + Should Be Equal ${services_removed} 2 msg=Not all services removed after delete provider (removed) vs (expected) -- GitLab From 964782d7f90ae28e5a4bd94cd3df88047e67a40a Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Mon, 28 Oct 2024 17:06:35 +0100 Subject: [PATCH 5/6] simplify tests case 14 of publish test suite --- .../capif_api_publish_service.robot | 33 ++++++------------- tests/resources/common/basicRequests.robot | 12 +++++++ 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot b/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot index d453068..8808e7d 100644 --- a/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot +++ b/tests/features/CAPIF Api Publish Service/capif_api_publish_service.robot @@ -364,14 +364,9 @@ Check Two Published APIs with different APFs are removed when Provider is delete Dictionaries Should Be Equal ${resp.json()} ${service_api_description_published_2} # Get all services present at CCF - ${resp}= Get Request Capif - ... /helper/getServices - ... server=${CAPIF_HTTPS_URL} - ... verify=ca.crt - ... username=${SUPERADMIN_USERNAME} - - Log Dictionary ${resp.json()} + ${services_present_on_ccf_after_publish}= Get Number Of Services + # Delete Provider using AMF cert ${resp}= Delete Request Capif ... ${register_user_info['resource_url'].path} ... server=${CAPIF_HTTPS_URL} @@ -380,29 +375,21 @@ Check Two Published APIs with different APFs are removed when Provider is delete Call Method ${CAPIF_USERS} remove_capif_users_entry ${register_user_info['resource_url'].path} - ${resp}= Get Request Capif - ... /helper/getServices - ... server=${CAPIF_HTTPS_URL} - ... verify=ca.crt - ... username=${SUPERADMIN_USERNAME} + ${services_present_on_ccf_after_delete_provider}= Get Number Of Services - Log Dictionary ${resp.json()} - ${services_present_on_ccf}= Get Length ${resp.json()['services']} + ${services_removed}= Evaluate ${services_present_on_ccf_after_publish} - ${services_present_on_ccf_after_delete_provider} + Run Keyword And Continue On Failure Should Be Equal "${services_removed}" "2" msg=Not all services removed after delete provider (removed) vs (expected) + + # Remove service API by superadmin ${resp}= Delete Request Capif ... /published-apis/v1/${register_user_info['apf_roles']['${APF_PROVIDER_USERNAME}_1']['apf_id']}/service-apis/${serviceApiId2} ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt ... username=${SUPERADMIN_USERNAME} - ${resp}= Get Request Capif - ... /helper/getServices - ... server=${CAPIF_HTTPS_URL} - ... verify=ca.crt - ... username=${SUPERADMIN_USERNAME} + ${services_present_on_ccf_after_provider_deletion_superadmin}= Get Number Of Services - Log Dictionary ${resp.json()} - ${services_present_on_ccf_after_provider_deletion}= Get Length ${resp.json()['services']} - ${services_removed}= Evaluate ${services_present_on_ccf} - ${services_present_on_ccf_after_provider_deletion} + ${services_removed}= Evaluate ${services_present_on_ccf_after_publish} - ${services_present_on_ccf_after_provider_deletion_superadmin} - Should Be Equal ${services_removed} 2 msg=Not all services removed after delete provider (removed) vs (expected) + Run Keyword And Continue On Failure Should Be Equal "${services_removed}" "2" msg=Not all services removed after delete provider (removed) vs (expected) diff --git a/tests/resources/common/basicRequests.robot b/tests/resources/common/basicRequests.robot index 0e37eef..f9c1139 100644 --- a/tests/resources/common/basicRequests.robot +++ b/tests/resources/common/basicRequests.robot @@ -901,3 +901,15 @@ Create Security Context Between invoker and provider ... username=${register_user_info_invoker['management_cert']} Check Response Variable Type And Values ${resp} 201 ServiceSecurity + +Get Number Of Services + ${resp}= Get Request Capif + ... /helper/getServices + ... server=${CAPIF_HTTPS_URL} + ... verify=ca.crt + ... username=${SUPERADMIN_USERNAME} + + Log Dictionary ${resp.json()} + ${size}= Get Length ${resp.json()['services']} + + RETURN ${size} -- GitLab From 5238d4c4734900cad2f7827bd6bb528b341aaad6 Mon Sep 17 00:00:00 2001 From: Jorge Moratinos Salcines Date: Mon, 18 Nov 2024 17:13:32 +0100 Subject: [PATCH 6/6] Internal events modified to allow multiple ids over provider (more than one aef, apf or amf) --- .../core/apiinvokerenrolmentdetails.py | 90 ++++++++++++------- .../core/consumer_messager.py | 47 +++++++--- .../core/redis_internal_event.py | 35 ++++++++ .../core/provider_enrolment_details_api.py | 77 +++++++++++----- .../core/redis_internal_event.py | 35 ++++++++ .../capif_acl/core/consumer_messager.py | 36 +++++--- .../capif_events/core/consumer_messager.py | 24 +++-- .../published_apis/core/consumer_messager.py | 16 ++-- .../core/internal_service_ops.py | 9 +- .../controllers/default_controller.py | 51 ++++++----- .../capif_security/core/consumer_messager.py | 18 ++-- .../core/internal_security_ops.py | 20 +++-- .../core/redis_internal_event.py | 35 ++++++++ 13 files changed, 360 insertions(+), 133 deletions(-) create mode 100644 services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_internal_event.py create mode 100644 services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/redis_internal_event.py create mode 100644 services/TS29222_CAPIF_Security_API/capif_security/core/redis_internal_event.py 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 80988ca..9dd3df5 100644 --- a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/apiinvokerenrolmentdetails.py +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/apiinvokerenrolmentdetails.py @@ -3,30 +3,33 @@ from pymongo import ReturnDocument import secrets import requests from .responses import bad_request_error, not_found_error, forbidden_error, internal_server_error, make_response -from flask import current_app, Flask, Response +from flask import current_app, Response import json from datetime import datetime -from ..util import dict_to_camel_case, clean_empty, serialize_clean_camel_case +from ..util import dict_to_camel_case, serialize_clean_camel_case from .auth_manager import AuthManager from .resources import Resource from ..config import Config from api_invoker_management.models.api_invoker_enrolment_details import APIInvokerEnrolmentDetails from .redis_event import RedisEvent +from .redis_internal_event import RedisInternalEvent from .publisher import Publisher publisher_ops = Publisher() + + class InvokerManagementOperations(Resource): def __check_api_invoker_id(self, api_invoker_id): current_app.logger.debug("Cheking api invoker id") mycol = self.db.get_col_by_name(self.db.invoker_enrolment_details) - my_query = {'api_invoker_id':api_invoker_id} + my_query = {'api_invoker_id': api_invoker_id} old_values = mycol.find_one(my_query) if old_values is None: current_app.logger.error("Not found api invoker id") - return not_found_error(detail="Please provide an existing Network App ID", cause= "Not exist Network App ID" ) + return not_found_error(detail="Please provide an existing Network App ID", cause="Not exist Network App ID") return old_values @@ -35,13 +38,14 @@ class InvokerManagementOperations(Resource): url = f"http://{self.config['ca_factory']['url']}:{self.config['ca_factory']['port']}/v1/pki_int/sign/my-ca" headers = {'X-Vault-Token': self.config['ca_factory']['token']} data = { - 'format':'pem_bundle', + 'format': 'pem_bundle', 'ttl': '43000h', 'csr': publick_key, 'common_name': invoker_id } - response = requests.request("POST", url, headers=headers, data=data, verify = self.config["ca_factory"].get("verify", False)) + response = requests.request("POST", url, headers=headers, data=data, + verify=self.config["ca_factory"].get("verify", False)) print(response) response_payload = json.loads(response.text) @@ -52,51 +56,59 @@ class InvokerManagementOperations(Resource): self.auth_manager = AuthManager() self.config = Config().get_config() - def add_apiinvokerenrolmentdetail(self, apiinvokerenrolmentdetail, username, uuid): mycol = self.db.get_col_by_name(self.db.invoker_enrolment_details) - #try: + # try: current_app.logger.debug("Creating invoker resource") - res = mycol.find_one({'onboarding_information.api_invoker_public_key': apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key}) + res = mycol.find_one({'onboarding_information.api_invoker_public_key': + apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key}) if res is not None: - current_app.logger.error("Generating forbbiden error, invoker registered") - return forbidden_error(detail= "Invoker already registered", cause = "Identical invoker public key") + current_app.logger.error( + "Generating forbbiden error, invoker registered") + return forbidden_error(detail="Invoker already registered", cause="Identical invoker public key") if rfc3987.match(apiinvokerenrolmentdetail.notification_destination, rule="URI") is None: current_app.logger.error("Bad url format") - return bad_request_error(detail="Bad Param", cause = "Detected Bad formar of param", invalid_params=[{"param": "notificationDestination", "reason": "Not valid URL format"}]) + return bad_request_error(detail="Bad Param", cause="Detected Bad formar of param", invalid_params=[{"param": "notificationDestination", "reason": "Not valid URL format"}]) current_app.logger.debug("Signing Certificate") api_invoker_id = 'INV'+str(secrets.token_hex(15)) - cert = self.__sign_cert(apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key, api_invoker_id) + cert = self.__sign_cert( + apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key, api_invoker_id) apiinvokerenrolmentdetail.api_invoker_id = api_invoker_id current_app.logger.debug(cert) - apiinvokerenrolmentdetail.onboarding_information.api_invoker_certificate = cert['data']['certificate'] + apiinvokerenrolmentdetail.onboarding_information.api_invoker_certificate = cert[ + 'data']['certificate'] # Onboarding Date Record invoker_dict = apiinvokerenrolmentdetail.to_dict() invoker_dict["onboarding_date"] = datetime.now() - invoker_dict["username"]=username - invoker_dict["uuid"]=uuid + invoker_dict["username"] = username + invoker_dict["uuid"] = uuid mycol.insert_one(invoker_dict) current_app.logger.debug("Invoker inserted in database") current_app.logger.debug("Netapp onboarded sucessfuly") - self.auth_manager.add_auth_invoker(cert['data']['certificate'], api_invoker_id) + self.auth_manager.add_auth_invoker( + cert['data']['certificate'], api_invoker_id) - res = make_response(object=serialize_clean_camel_case(apiinvokerenrolmentdetail), status=201) - res.headers['Location'] = "/api-invoker-management/v1/onboardedInvokers/" + str(api_invoker_id) + res = make_response(object=serialize_clean_camel_case( + apiinvokerenrolmentdetail), status=201) + res.headers['Location'] = "/api-invoker-management/v1/onboardedInvokers/" + \ + str(api_invoker_id) if res.status_code == 201: current_app.logger.info("Invoker Created") - RedisEvent("API_INVOKER_ONBOARDED", ["apiInvokerIds"], [[str(api_invoker_id)]]).send_event() + RedisEvent("API_INVOKER_ONBOARDED", + ["apiInvokerIds"], + [[str(api_invoker_id)]]).send_event() return res def update_apiinvokerenrolmentdetail(self, onboard_id, apiinvokerenrolmentdetail): @@ -111,16 +123,23 @@ class InvokerManagementOperations(Resource): return result if apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key != result["onboarding_information"]["api_invoker_public_key"]: - cert = self.__sign_cert(apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key, result["api_invoker_id"]) - apiinvokerenrolmentdetail.onboarding_information.api_invoker_certificate = cert['data']['certificate'] - self.auth_manager.update_auth_invoker(cert['data']["certificate"], onboard_id) + cert = self.__sign_cert( + apiinvokerenrolmentdetail.onboarding_information.api_invoker_public_key, result["api_invoker_id"]) + apiinvokerenrolmentdetail.onboarding_information.api_invoker_certificate = cert[ + 'data']['certificate'] + self.auth_manager.update_auth_invoker( + cert['data']["certificate"], onboard_id) apiinvokerenrolmentdetail_update = apiinvokerenrolmentdetail.to_dict() apiinvokerenrolmentdetail_update = { key: value for key, value in apiinvokerenrolmentdetail_update.items() if value is not None } - result = mycol.find_one_and_update(result, {"$set":apiinvokerenrolmentdetail_update}, projection={'_id': 0},return_document=ReturnDocument.AFTER ,upsert=False) + result = mycol.find_one_and_update(result, + {"$set": apiinvokerenrolmentdetail_update}, + projection={'_id': 0}, + return_document=ReturnDocument.AFTER, + upsert=False) result = { key: value for key, value in result.items() if value is not None @@ -130,10 +149,13 @@ class InvokerManagementOperations(Resource): invoker_updated = APIInvokerEnrolmentDetails().from_dict(dict_to_camel_case(result)) - res = make_response(object=serialize_clean_camel_case(invoker_updated), status=200) + res = make_response(object=serialize_clean_camel_case( + invoker_updated), status=200) if res.status_code == 200: current_app.logger.info("Invoker Updated") - RedisEvent("API_INVOKER_UPDATED", ["apiInvokerIds"], [[onboard_id]]).send_event() + RedisEvent("API_INVOKER_UPDATED", + ["apiInvokerIds"], + [[onboard_id]]).send_event() return res except Exception as e: @@ -151,22 +173,26 @@ class InvokerManagementOperations(Resource): if isinstance(result, Response): return result - mycol.delete_one({'api_invoker_id':onboard_id}) + mycol.delete_one({'api_invoker_id': onboard_id}) self.auth_manager.remove_auth_invoker(onboard_id) current_app.logger.debug("Invoker resource removed from database") current_app.logger.debug("Netapp offboarded sucessfuly") - out = "The Network App matching onboardingId " + onboard_id + " was offboarded." + out = "The Network App matching onboardingId " + onboard_id + " was offboarded." res = make_response(out, status=204) if res.status_code == 204: current_app.logger.info("Invoker Removed") - RedisEvent("API_INVOKER_OFFBOARDED", ["apiInvokerIds"], [[onboard_id]]).send_event() - publisher_ops.publish_message("internal-messages", f"invoker-removed:{onboard_id}") + RedisEvent("API_INVOKER_OFFBOARDED", + ["apiInvokerIds"], + [[onboard_id]]).send_event() + RedisInternalEvent("INVOKER-REMOVED", + "invokerId", + { + "api_invoker_id": onboard_id + }).send_event() return res except Exception as e: exception = "An exception occurred in remove invoker" current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) - - diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/consumer_messager.py b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/consumer_messager.py index 26f8d6b..8d5c56f 100644 --- a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/consumer_messager.py +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/consumer_messager.py @@ -2,6 +2,8 @@ import redis from .invoker_internal_ops import InvokerInternalOperations from flask import current_app +import json + class Subscriber(): @@ -14,13 +16,38 @@ class Subscriber(): def listen(self): for raw_message in self.p.listen(): if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": - message, invoker_id, api_id = raw_message["data"].decode('utf-8').split(":") - if message == "security-context-created": - current_app.logger.debug("Internal message received, updating Api list on invoker") - self.invoker_ops.update_services_list(invoker_id, api_id) - if message == "security-context-removed": - current_app.logger.debug("Internal message received, removing service in Api list of invoker") - self.invoker_ops.remove_services_list(invoker_id, api_id) - - - + current_app.logger.info("New internal event received") + internal_redis_event = json.loads( + raw_message["data"].decode('utf-8')) + if internal_redis_event.get('event') == "SECURITY-CONTEXT-CREATED": + current_app.logger.debug( + "Internal message received, updating Api list on invoker") + security_context_information = internal_redis_event.get( + 'information', None) + if security_context_information is not None: + api_invoker_id = security_context_information.get( + 'api_invoker_id') + api_id = security_context_information.get('api_id') + self.invoker_ops.update_services_list( + api_invoker_id, api_id) + elif internal_redis_event.get('event') == "SECURITY-CONTEXT-REMOVED": + current_app.logger.debug( + "Internal message received, removing service in Api list of invoker") + security_context_information = internal_redis_event.get( + 'information', None) + if security_context_information is not None: + api_invoker_id = security_context_information.get( + 'api_invoker_id') + api_id = security_context_information.get('api_id') + self.invoker_ops.remove_services_list( + api_invoker_id, api_id) + elif internal_redis_event.get('event') == "INVOKER-REMOVED": + api_invoker_id = internal_redis_event.get( + 'information', {"api_invoker_id": None}).get('api_invoker_id') + if api_invoker_id is not None: + self.acls_ops.remove_invoker_acl(api_invoker_id) + elif internal_redis_event.get('event') == "PROVIDER-REMOVED": + aef_ids = internal_redis_event.get( + 'information', {"aef_ids": []}).get('aef_ids') + if len(aef_ids) > 0: + self.acls_ops.remove_provider_acls(aef_ids[0]) diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_internal_event.py b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_internal_event.py new file mode 100644 index 0000000..faa5ea1 --- /dev/null +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/api_invoker_management/core/redis_internal_event.py @@ -0,0 +1,35 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisInternalEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.INTERNAL_MESSAGES = [ + 'INVOKER-REMOVED', + 'PROVIDER-REMOVED', + 'SECURITY-CONTEXT-CREATED', + 'SECURITY-CONTEXT-REMOVED', + 'create-acl', + 'remove-acl', + ] + if event not in self.INTERNAL_MESSAGES: + raise Exception( + "Internal Message (" + event + ") is not on INTERNAL_MESSAGES enum (" + ','.join(self.INTERNAL_MESSAGES) + ")") + self.redis_event = { + "event": event + } + if event_detail_key is not None and information is not None: + self.redis_event['key'] = event_detail_key + self.redis_event['information'] = information + + def to_string(self): + return json.dumps(self.redis_event, cls=JSONEncoder) + + def send_event(self): + publisher_ops.publish_message("internal-messages", self.to_string()) + + def __call__(self): + return self.redis_event diff --git a/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/provider_enrolment_details_api.py b/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/provider_enrolment_details_api.py index f1b2a0d..de8b08f 100644 --- a/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/provider_enrolment_details_api.py +++ b/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/provider_enrolment_details_api.py @@ -10,6 +10,7 @@ from .resources import Resource from .auth_manager import AuthManager from api_provider_management.models.api_provider_enrolment_details import APIProviderEnrolmentDetails # noqa: E501 +from .redis_internal_event import RedisInternalEvent class ProviderManagementOperations(Resource): @@ -40,19 +41,24 @@ class ProviderManagementOperations(Resource): my_provider_enrolment_details = mycol.find_one(search_filter) if my_provider_enrolment_details is not None: - current_app.logger.error("Found provider registered with same id") + current_app.logger.error( + "Found provider registered with same id") return forbidden_error(detail="Provider already registered", cause="Identical provider reg sec") - api_provider_enrolment_details.api_prov_dom_id = secrets.token_hex(15) + api_provider_enrolment_details.api_prov_dom_id = secrets.token_hex( + 15) current_app.logger.debug("Generating certs to api prov funcs") for api_provider_func in api_provider_enrolment_details.api_prov_funcs: - api_provider_func.api_prov_func_id = api_provider_func.api_prov_func_role + str(secrets.token_hex(15)) - certificate = sign_certificate(api_provider_func.reg_info.api_prov_pub_key, api_provider_func.api_prov_func_id) + api_provider_func.api_prov_func_id = api_provider_func.api_prov_func_role + \ + str(secrets.token_hex(15)) + certificate = sign_certificate( + api_provider_func.reg_info.api_prov_pub_key, api_provider_func.api_prov_func_id) api_provider_func.reg_info.api_prov_cert = certificate - self.auth_manager.add_auth_provider(certificate, api_provider_func.api_prov_func_id, api_provider_func.api_prov_func_role, api_provider_enrolment_details.api_prov_dom_id) + self.auth_manager.add_auth_provider(certificate, api_provider_func.api_prov_func_id, + api_provider_func.api_prov_func_role, api_provider_enrolment_details.api_prov_dom_id) # Onboarding Date Record provider_dict = api_provider_enrolment_details.to_dict() @@ -61,12 +67,14 @@ class ProviderManagementOperations(Resource): provider_dict["uuid"] = uuid mycol.insert_one(provider_dict) - + current_app.logger.debug("Provider inserted in database") - res = make_response(object=serialize_clean_camel_case(api_provider_enrolment_details), status=201) + res = make_response(object=serialize_clean_camel_case( + api_provider_enrolment_details), status=201) - res.headers['Location'] = "/api-provider-management/v1/registrations/" + str(api_provider_enrolment_details.api_prov_dom_id) + res.headers['Location'] = "/api-provider-management/v1/registrations/" + \ + str(api_provider_enrolment_details.api_prov_dom_id) return res except Exception as e: @@ -87,17 +95,29 @@ class ProviderManagementOperations(Resource): func_ids = list() for provider_func in result["api_prov_funcs"]: func_ids.append(provider_func['api_prov_func_id']) - apf_id = [ provider_func['api_prov_func_id'] for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'APF' ] - aef_id = [ provider_func['api_prov_func_id'] for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'AEF' ] - amf_id = [ provider_func['api_prov_func_id'] for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'AMF' ] + apf_ids = [provider_func['api_prov_func_id'] + for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'APF'] + aef_ids = [provider_func['api_prov_func_id'] + for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'AEF'] + amf_ids = [provider_func['api_prov_func_id'] + for provider_func in result["api_prov_funcs"] if provider_func['api_prov_func_role'] == 'AMF'] mycol.delete_one({'api_prov_dom_id': api_prov_dom_id}) - out = "The provider matching apiProvDomainId " + api_prov_dom_id + " was offboarded." + out = "The provider matching apiProvDomainId " + \ + api_prov_dom_id + " was offboarded." current_app.logger.debug("Removed provider domain from database") self.auth_manager.remove_auth_provider(func_ids) - self.publish_ops.publish_message("internal-messages", f"provider-removed:{aef_id[0]}:{apf_id[0]}:{amf_id[0]}") + RedisInternalEvent("PROVIDER-REMOVED", + "providerIds", + { + "apf_ids": apf_ids, + "aef_ids": aef_ids, + "amf_ids": amf_ids, + "all_ids": apf_ids + aef_ids + amf_ids + }).send_event() + return make_response(object=out, status=204) except Exception as e: @@ -117,26 +137,33 @@ class ProviderManagementOperations(Resource): for func in api_provider_enrolment_details.api_prov_funcs: if func.api_prov_func_id is None: - func.api_prov_func_id = func.api_prov_func_role + str(secrets.token_hex(15)) - certificate = sign_certificate(func.reg_info.api_prov_pub_key, func.api_prov_func_id) + func.api_prov_func_id = func.api_prov_func_role + \ + str(secrets.token_hex(15)) + certificate = sign_certificate( + func.reg_info.api_prov_pub_key, func.api_prov_func_id) func.reg_info.api_prov_cert = certificate - self.auth_manager.update_auth_provider(certificate, func.api_prov_func_id, api_prov_dom_id, func.api_prov_func_role) + self.auth_manager.update_auth_provider( + certificate, func.api_prov_func_id, api_prov_dom_id, func.api_prov_func_role) else: api_prov_funcs = result["api_prov_funcs"] for api_func in api_prov_funcs: if func.api_prov_func_id == api_func["api_prov_func_id"]: if func.api_prov_func_role != api_func["api_prov_func_role"]: - return bad_request_error(detail="Bad Role in provider", cause="Different role in update reqeuest", invalid_params=[{"param":"api_prov_func_role","reason":"different role with same id"}]) + return bad_request_error(detail="Bad Role in provider", cause="Different role in update reqeuest", invalid_params=[{"param": "api_prov_func_role", "reason": "different role with same id"}]) if func.reg_info.api_prov_pub_key != api_func["reg_info"]["api_prov_pub_key"]: - certificate = sign_certificate(func.reg_info.api_prov_pub_key, api_func["api_prov_func_id"]) + certificate = sign_certificate( + func.reg_info.api_prov_pub_key, api_func["api_prov_func_id"]) func.reg_info.api_prov_cert = certificate - self.auth_manager.update_auth_provider(certificate, func.api_prov_func_id, api_prov_dom_id, func.api_prov_func_role) + self.auth_manager.update_auth_provider( + certificate, func.api_prov_func_id, api_prov_dom_id, func.api_prov_func_role) api_provider_enrolment_details = api_provider_enrolment_details.to_dict() - api_provider_enrolment_details = clean_empty(api_provider_enrolment_details) + api_provider_enrolment_details = clean_empty( + api_provider_enrolment_details) - result = mycol.find_one_and_update(result, {"$set":api_provider_enrolment_details}, projection={'_id': 0},return_document=ReturnDocument.AFTER ,upsert=False) + result = mycol.find_one_and_update(result, {"$set": api_provider_enrolment_details}, projection={ + '_id': 0}, return_document=ReturnDocument.AFTER, upsert=False) result = clean_empty(result) current_app.logger.debug("Provider domain updated in database") @@ -160,9 +187,11 @@ class ProviderManagementOperations(Resource): return result api_provider_enrolment_details_patch = api_provider_enrolment_details_patch.to_dict() - api_provider_enrolment_details_patch = clean_empty(api_provider_enrolment_details_patch) + api_provider_enrolment_details_patch = clean_empty( + api_provider_enrolment_details_patch) - result = mycol.find_one_and_update(result, {"$set":api_provider_enrolment_details_patch}, projection={'_id': 0},return_document=ReturnDocument.AFTER ,upsert=False) + result = mycol.find_one_and_update(result, {"$set": api_provider_enrolment_details_patch}, projection={ + '_id': 0}, return_document=ReturnDocument.AFTER, upsert=False) result = clean_empty(result) @@ -174,4 +203,4 @@ class ProviderManagementOperations(Resource): except Exception as e: exception = "An exception occurred in patch provider" current_app.logger.error(exception + "::" + str(e)) - return internal_server_error(detail=exception, cause=str(e)) \ No newline at end of file + return internal_server_error(detail=exception, cause=str(e)) diff --git a/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/redis_internal_event.py b/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/redis_internal_event.py new file mode 100644 index 0000000..faa5ea1 --- /dev/null +++ b/services/TS29222_CAPIF_API_Provider_Management_API/api_provider_management/core/redis_internal_event.py @@ -0,0 +1,35 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisInternalEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.INTERNAL_MESSAGES = [ + 'INVOKER-REMOVED', + 'PROVIDER-REMOVED', + 'SECURITY-CONTEXT-CREATED', + 'SECURITY-CONTEXT-REMOVED', + 'create-acl', + 'remove-acl', + ] + if event not in self.INTERNAL_MESSAGES: + raise Exception( + "Internal Message (" + event + ") is not on INTERNAL_MESSAGES enum (" + ','.join(self.INTERNAL_MESSAGES) + ")") + self.redis_event = { + "event": event + } + if event_detail_key is not None and information is not None: + self.redis_event['key'] = event_detail_key + self.redis_event['information'] = information + + def to_string(self): + return json.dumps(self.redis_event, cls=JSONEncoder) + + def send_event(self): + publisher_ops.publish_message("internal-messages", self.to_string()) + + def __call__(self): + return self.redis_event diff --git a/services/TS29222_CAPIF_Access_Control_Policy_API/capif_acl/core/consumer_messager.py b/services/TS29222_CAPIF_Access_Control_Policy_API/capif_acl/core/consumer_messager.py index 0da2440..fef5f8d 100644 --- a/services/TS29222_CAPIF_Access_Control_Policy_API/capif_acl/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Access_Control_Policy_API/capif_acl/core/consumer_messager.py @@ -3,13 +3,16 @@ import redis from config import Config from .internal_service_ops import InternalServiceOps from flask import current_app +import json + class Subscriber(): def __init__(self): self.config = Config().get_config() - #set this params using config params - self.r = redis.Redis(host=self.config["redis"]["host"], port=self.config["redis"]["port"], db=self.config["redis"]["db"]) + # set this params using config params + self.r = redis.Redis( + host=self.config["redis"]["host"], port=self.config["redis"]["port"], db=self.config["redis"]["db"]) self.acls_ops = InternalServiceOps() self.p = self.r.pubsub() self.p.subscribe("acls-messages", "internal-messages") @@ -19,19 +22,24 @@ class Subscriber(): if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "acls-messages": current_app.logger.info("acls-messages recived") message, *ids = raw_message["data"].decode('utf-8').split(":") - if message == "create-acl" and len(ids)==3: + if message == "create-acl" and len(ids) == 3: self.acls_ops.create_acl(ids[0], ids[1], ids[2]) - if message == "remove-acl" and len(ids)==3: + if message == "remove-acl" and len(ids) == 3: self.acls_ops.remove_acl(ids[0], ids[1], ids[2]) - if message == "remove-acl" and len(ids)==1: - self.acls_ops.remove_invoker_acl(ids[0]) - if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": - message, *ids = raw_message["data"].decode('utf-8').split(":") - if message == "invoker-removed" and len(ids)>0: + if message == "remove-acl" and len(ids) == 1: self.acls_ops.remove_invoker_acl(ids[0]) - if message == "provider-removed" or message == "service-removed" and len(ids) > 0: - self.acls_ops.remove_provider_acls(ids[0]) - - - + if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": + current_app.logger.info("New internal event received") + internal_redis_event = json.loads( + raw_message["data"].decode('utf-8')) + if internal_redis_event.get('event') == "INVOKER-REMOVED": + api_invoker_id = internal_redis_event.get( + 'information', {"api_invoker_id": None}).get('api_invoker_id') + if api_invoker_id is not None: + self.acls_ops.remove_invoker_acl(api_invoker_id) + elif internal_redis_event.get('event') == "PROVIDER-REMOVED": + aef_ids = internal_redis_event.get( + 'information', {"aef_ids": []}).get('aef_ids') + if len(aef_ids) > 0: + self.acls_ops.remove_provider_acls(aef_ids[0]) diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py b/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py index ce6479a..c3d095f 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/consumer_messager.py @@ -9,6 +9,7 @@ from .notifications import Notifications from .internal_event_ops import InternalEventOperations from flask import current_app + class Subscriber(): def __init__(self): @@ -23,15 +24,20 @@ class Subscriber(): current_app.logger.info(raw_message) if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "events": current_app.logger.info("Event received") - redis_event=json.loads(raw_message["data"].decode('utf-8')) + redis_event = json.loads(raw_message["data"].decode('utf-8')) current_app.logger.info(json.dumps(redis_event, indent=4)) self.notification.send_notifications(redis_event) elif raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": - message, *subscriber_ids = raw_message["data"].decode('utf-8').split(":") - if message == "invoker-removed" and len(subscriber_ids)>0: - self.event_ops.delete_all_events(subscriber_ids) - if message == "provider-removed" and len(subscriber_ids)>0: - self.event_ops.delete_all_events(subscriber_ids) - - - + current_app.logger.info("New internal event received") + internal_redis_event = json.loads( + raw_message["data"].decode('utf-8')) + if internal_redis_event.get('event') == "INVOKER-REMOVED": + api_invoker_id = internal_redis_event.get( + 'information', {"api_invoker_id": None}).get('api_invoker_id') + if api_invoker_id is not None: + self.event_ops.delete_all_events([api_invoker_id]) + elif internal_redis_event.get('event') == "PROVIDER-REMOVED": + all_ids = internal_redis_event.get( + 'information', {"all_ids": None}).get('all_ids') + if all_ids is not None: + self.event_ops.delete_all_events(all_ids) diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/consumer_messager.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/consumer_messager.py index f781ead..2d13802 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/consumer_messager.py @@ -2,6 +2,8 @@ import redis from .internal_service_ops import InternalServiceOps from flask import current_app +import json + class Subscriber(): @@ -15,10 +17,12 @@ class Subscriber(): current_app.logger.info("Listening publish messages") for raw_message in self.p.listen(): if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": - message, *ids = raw_message["data"].decode('utf-8').split(":") - if message == "provider-removed" and len(ids) > 0: - self.security_ops.delete_intern_service(ids[1]) - - - + current_app.logger.info("New internal event received") + internal_redis_event = json.loads( + raw_message["data"].decode('utf-8')) + if internal_redis_event.get('event') == "PROVIDER-REMOVED": + apf_ids = internal_redis_event.get( + 'information', {"apf_ids": []}).get('apf_ids') + if len(apf_ids) > 0: + self.security_ops.delete_intern_service(apf_ids) diff --git a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/internal_service_ops.py b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/internal_service_ops.py index 71c2759..d90174b 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/internal_service_ops.py +++ b/services/TS29222_CAPIF_Publish_Service_API/published_apis/core/internal_service_ops.py @@ -9,14 +9,15 @@ class InternalServiceOps(Resource): Resource.__init__(self) self.auth_manager = AuthManager() - def delete_intern_service(self, apf_id): + def delete_intern_service(self, apf_ids): current_app.logger.info("Provider removed, removing services published by APF") mycol = self.db.get_col_by_name(self.db.service_api_descriptions) - my_query = {'apf_id': apf_id} - mycol.delete_many(my_query) + for apf_id in apf_ids: + my_query = {'apf_id': apf_id} + mycol.delete_many(my_query) #We dont need remove all auth events, because when provider is removed, remove auth entry #self.auth_manager.remove_auth_all_service(apf_id) - current_app.logger.info("Removed service") \ No newline at end of file + current_app.logger.info("Removed service") diff --git a/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py b/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py index d56b02c..eec268a 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py @@ -1,15 +1,6 @@ -import connexion -from typing import Dict -from typing import Tuple -from typing import Union - -from capif_security.models.access_token_err import AccessTokenErr # noqa: E501 -from capif_security.models.access_token_rsp import AccessTokenRsp # noqa: E501 -from capif_security.models.problem_details import ProblemDetails # noqa: E501 from capif_security.models.res_owner_id import ResOwnerId # noqa: E501 from capif_security.models.security_notification import SecurityNotification # noqa: E501 from capif_security.models.service_security import ServiceSecurity # noqa: E501 -from capif_security import util from cryptography import x509 from cryptography.hazmat.backends import default_backend @@ -20,13 +11,16 @@ from ..core.publisher import Publisher from ..core.servicesecurity import SecurityOperations -from flask import Response, request, current_app +from flask import request, current_app + +from ..core.redis_internal_event import RedisInternalEvent service_security_ops = SecurityOperations() publish_ops = Publisher() valid_user = ControlAccess() + def cert_validation(): def _cert_validation(f): @wraps(f) @@ -36,17 +30,21 @@ def cert_validation(): cert_tmp = request.headers['X-Ssl-Client-Cert'] cert_raw = cert_tmp.replace('\t', '') - cert = x509.load_pem_x509_certificate(str.encode(cert_raw), default_backend()) + cert = x509.load_pem_x509_certificate( + str.encode(cert_raw), default_backend()) - cn = cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value.strip() + cn = cert.subject.get_attributes_for_oid( + x509.OID_COMMON_NAME)[0].value.strip() current_app.logger.info(f"CN: {cn}") - if cn != "superadmin" and "AEF" not in cn : + if cn != "superadmin" and "AEF" not in cn: cert_signature = cert.signature.hex() if "securityId" in args: - result = valid_user.validate_user_cert(args["securityId"], cert_signature) + result = valid_user.validate_user_cert( + args["securityId"], cert_signature) else: - result = valid_user.validate_user_cert(args["apiInvokerId"], cert_signature) + result = valid_user.validate_user_cert( + args["apiInvokerId"], cert_signature) if result is not None: return result @@ -56,6 +54,7 @@ def cert_validation(): return __cert_validation return _cert_validation + @cert_validation() def securities_security_id_token_post(security_id, body): # noqa: E501 """securities_security_id_token_post @@ -84,7 +83,7 @@ def securities_security_id_token_post(security_id, body): # noqa: E501 current_app.logger.info("Creating security token") if request.is_json: res_owner_id = ResOwnerId.from_dict(request.get_json()) # noqa: E501 - + # body={"security_id": security_id, # "grant_type": grant_type, # "client_id": client_id, @@ -95,12 +94,12 @@ def securities_security_id_token_post(security_id, body): # noqa: E501 # "redirect_uri": redirect_uri # } current_app.logger.debug(body) - - res = service_security_ops.return_token(security_id, body) + res = service_security_ops.return_token(security_id, body) return res + @cert_validation() def trusted_invokers_api_invoker_id_delete(api_invoker_id): # noqa: E501 """trusted_invokers_api_invoker_id_delete @@ -115,6 +114,7 @@ def trusted_invokers_api_invoker_id_delete(api_invoker_id): # noqa: E501 current_app.logger.info("Removing security context") return service_security_ops.delete_servicesecurity(api_invoker_id) + @cert_validation() def trusted_invokers_api_invoker_id_delete_post(api_invoker_id, body): # noqa: E501 """trusted_invokers_api_invoker_id_delete_post @@ -134,9 +134,10 @@ def trusted_invokers_api_invoker_id_delete_post(api_invoker_id, body): # noqa: current_app.logger.info("Revoking permissions") res = service_security_ops.revoke_api_authorization(api_invoker_id, body) - + return res + @cert_validation() def trusted_invokers_api_invoker_id_get(api_invoker_id, authentication_info=None, authorization_info=None): # noqa: E501 """trusted_invokers_api_invoker_id_get @@ -153,10 +154,12 @@ def trusted_invokers_api_invoker_id_get(api_invoker_id, authentication_info=None :rtype: Union[ServiceSecurity, Tuple[ServiceSecurity, int], Tuple[ServiceSecurity, int, Dict[str, str]] """ current_app.logger.info("Obtaining security context") - res = service_security_ops.get_servicesecurity(api_invoker_id, authentication_info, authorization_info) + res = service_security_ops.get_servicesecurity( + api_invoker_id, authentication_info, authorization_info) return res + @cert_validation() def trusted_invokers_api_invoker_id_put(api_invoker_id, body): # noqa: E501 """trusted_invokers_api_invoker_id_put @@ -179,10 +182,16 @@ def trusted_invokers_api_invoker_id_put(api_invoker_id, body): # noqa: E501 if res.status_code == 201: for service_instance in body.security_info: if service_instance.api_id is not None: - publish_ops.publish_message("internal-messages", "security-context-created:"+api_invoker_id+":"+service_instance.api_id ) + RedisInternalEvent("SECURITY-CONTEXT-CREATED", + "securityIds", + { + "api_invoker_id": api_invoker_id, + "api_id": service_instance.api_id + }).send_event() return res + @cert_validation() def trusted_invokers_api_invoker_id_update_post(api_invoker_id, body): # noqa: E501 """trusted_invokers_api_invoker_id_update_post diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py b/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py index 4344558..5a90b3e 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py @@ -2,6 +2,7 @@ import redis from .internal_security_ops import InternalSecurityOps from flask import current_app +import json class Subscriber(): @@ -16,8 +17,15 @@ class Subscriber(): current_app.logger.info("Listening security context messages") for raw_message in self.p.listen(): if raw_message["type"] == "message" and raw_message["channel"].decode('utf-8') == "internal-messages": - message, *ids = raw_message["data"].decode('utf-8').split(":") - if message == "invoker-removed" and len(ids) > 0: - self.security_ops.delete_intern_servicesecurity(ids[0]) - if message == "provider-removed" or message == "service-removed" and len(ids) > 0: - self.security_ops.update_intern_servicesecurity(ids[0]) + internal_redis_event = json.loads( + raw_message["data"].decode('utf-8')) + if internal_redis_event.get('event') == "INVOKER-REMOVED": + api_invoker_id = internal_redis_event.get( + 'information', {"api_invoker_id": None}).get('api_invoker_id') + if api_invoker_id is not None: + self.security_ops.delete_intern_servicesecurity(api_invoker_id) + elif internal_redis_event.get('event') == "PROVIDER-REMOVED": + aef_ids = internal_redis_event.get( + 'information', {"aef_ids": []}).get("aef_ids") + if len(aef_ids) > 0: + self.security_ops.update_intern_servicesecurity(aef_ids) diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/internal_security_ops.py b/services/TS29222_CAPIF_Security_API/capif_security/core/internal_security_ops.py index d1b28d5..1f08710 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/internal_security_ops.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/internal_security_ops.py @@ -1,6 +1,7 @@ from .resources import Resource + class InternalSecurityOps(Resource): def delete_intern_servicesecurity(self, api_invoker_id): @@ -9,13 +10,16 @@ class InternalSecurityOps(Resource): my_query = {'api_invoker_id': api_invoker_id} mycol.delete_many(my_query) - def update_intern_servicesecurity(self, id): + def update_intern_servicesecurity(self, ids): security_col = self.db.get_col_by_name(self.db.security_info) - - security_contexts = security_col.find({"$or":[{"security_info.aef_id":id}, {"security_info.api_id":id}]}) - - for security_context in security_contexts: - new_security_info = [info for info in security_context["security_info"] if info["aef_id"]!=id and info["api_id"] != id] - security_context["security_info"] = new_security_info - security_col.update_one({'api_invoker_id':security_context["api_invoker_id"]}, {"$set":security_context}) + for id in ids: + security_contexts = security_col.find( + {"$or": [{"security_info.aef_id": id}, {"security_info.api_id": id}]}) + + for security_context in security_contexts: + new_security_info = [info for info in security_context["security_info"] + if info["aef_id"] != id and info["api_id"] != id] + security_context["security_info"] = new_security_info + security_col.update_one({'api_invoker_id': security_context["api_invoker_id"]}, { + "$set": security_context}) diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/redis_internal_event.py b/services/TS29222_CAPIF_Security_API/capif_security/core/redis_internal_event.py new file mode 100644 index 0000000..faa5ea1 --- /dev/null +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/redis_internal_event.py @@ -0,0 +1,35 @@ +from ..encoder import JSONEncoder +from .publisher import Publisher +import json + +publisher_ops = Publisher() + + +class RedisInternalEvent(): + def __init__(self, event, event_detail_key=None, information=None) -> None: + self.INTERNAL_MESSAGES = [ + 'INVOKER-REMOVED', + 'PROVIDER-REMOVED', + 'SECURITY-CONTEXT-CREATED', + 'SECURITY-CONTEXT-REMOVED', + 'create-acl', + 'remove-acl', + ] + if event not in self.INTERNAL_MESSAGES: + raise Exception( + "Internal Message (" + event + ") is not on INTERNAL_MESSAGES enum (" + ','.join(self.INTERNAL_MESSAGES) + ")") + self.redis_event = { + "event": event + } + if event_detail_key is not None and information is not None: + self.redis_event['key'] = event_detail_key + self.redis_event['information'] = information + + def to_string(self): + return json.dumps(self.redis_event, cls=JSONEncoder) + + def send_event(self): + publisher_ops.publish_message("internal-messages", self.to_string()) + + def __call__(self): + return self.redis_event -- GitLab