diff --git a/services/TS29222_CAPIF_Auditing_API/config.yaml b/services/TS29222_CAPIF_Auditing_API/config.yaml index 6943b30350583619fc33cb6c7fc4da415d281da0..116076c1c55bf7dc29a7ffd687ff0684ac8a7ae0 100644 --- a/services/TS29222_CAPIF_Auditing_API/config.yaml +++ b/services/TS29222_CAPIF_Auditing_API/config.yaml @@ -4,6 +4,7 @@ mongo: { 'db': 'capif', 'logs_col': 'invocationlogs', 'capif_users_col': "user", + 'certs_col': "certs", 'host': 'mongo', 'port': "27017" } diff --git a/services/TS29222_CAPIF_Auditing_API/logs/controllers/default_controller.py b/services/TS29222_CAPIF_Auditing_API/logs/controllers/default_controller.py index 0ff05f0dea9ebd53fa57866f6b87a085b9284c07..0f2866e5066a67cf500c31fb5310897acbb5ef73 100644 --- a/services/TS29222_CAPIF_Auditing_API/logs/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Auditing_API/logs/controllers/default_controller.py @@ -3,13 +3,45 @@ from logs import util from logs.models.interface_description import InterfaceDescription # noqa: E501 from logs.models.operation import Operation # noqa: E501 from logs.models.protocol import Protocol # noqa: E501 +from functools import wraps +from cryptography import x509 +from cryptography.hazmat.backends import default_backend from ..core.auditoperations import AuditOperations from ..core.responses import bad_request_error +from ..core.validate_user import ControlAccess audit_operations = AuditOperations() +valid_user = ControlAccess() +def cert_validation(): + def _cert_validation(f): + @wraps(f) + def __cert_validation(*args, **kwargs): + + args = request.view_args + 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()) + + cn = cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value.strip() + + if cn != "superadmin": + cert_signature = cert.signature.hex() + result = valid_user.validate_user_cert(cert_signature) + + if result is not None: + return result + + result = f(**kwargs) + return result + return __cert_validation + return _cert_validation + + +@cert_validation() def api_invocation_logs_get(aef_id=None, api_invoker_id=None, time_range_start=None, time_range_end=None, api_id=None, api_name=None, api_version=None, protocol=None, operation=None, result=None, resource_name=None, src_interface=None, dest_interface=None, supported_features=None): # noqa: E501 """api_invocation_logs_get diff --git a/services/TS29222_CAPIF_Auditing_API/logs/core/validate_user.py b/services/TS29222_CAPIF_Auditing_API/logs/core/validate_user.py new file mode 100644 index 0000000000000000000000000000000000000000..545642e15ef13ac9db99679c02e6a2346de65db4 --- /dev/null +++ b/services/TS29222_CAPIF_Auditing_API/logs/core/validate_user.py @@ -0,0 +1,31 @@ +import json + +from flask import Response, current_app + +from ..encoder import CustomJSONEncoder +from ..models.problem_details import ProblemDetails +from ..util import serialize_clean_camel_case +from .resources import Resource +from .responses import internal_server_error + + +class ControlAccess(Resource): + + def validate_user_cert(self, cert_signature): + + cert_col = self.db.get_col_by_name(self.db.certs_col) + + try: + my_query = {'cert_signature': cert_signature} + cert_entry = cert_col.find_one(my_query) + + if cert_entry is not None: + if cert_entry["role"] != "AMF": + prob = ProblemDetails(title="Unauthorized", detail="User not authorized", cause="You are not the owner of this resource") + prob = serialize_clean_camel_case(prob) + return Response(json.dumps(prob, cls=CustomJSONEncoder), status=401, mimetype="application/json") + + except Exception as e: + exception = "An exception occurred in validate invoker" + current_app.logger.error(exception + "::" + str(e)) + return internal_server_error(detail=exception, cause=str(e)) \ No newline at end of file diff --git a/services/TS29222_CAPIF_Auditing_API/logs/db/db.py b/services/TS29222_CAPIF_Auditing_API/logs/db/db.py index 4520c84691184c8ab699bce82cfc967abb02b948..108793c1ff715d120dd3547ca2bfb46a7aafe0b2 100644 --- a/services/TS29222_CAPIF_Auditing_API/logs/db/db.py +++ b/services/TS29222_CAPIF_Auditing_API/logs/db/db.py @@ -19,6 +19,7 @@ class MongoDatabse(): self.db = self.__connect() self.invocation_logs = self.config['mongo']['logs_col'] self.capif_users = self.config['mongo']['capif_users_col'] + self.certs_col = self.config['mongo']['certs_col'] def get_col_by_name(self, name): return self.db[name] diff --git a/services/TS29222_CAPIF_Discover_Service_API/config.yaml b/services/TS29222_CAPIF_Discover_Service_API/config.yaml index 6257115b4f972041d4b01b50419283f8ce1657a3..47851c027ad2a0d7ea39123055e48214b5c172e7 100644 --- a/services/TS29222_CAPIF_Discover_Service_API/config.yaml +++ b/services/TS29222_CAPIF_Discover_Service_API/config.yaml @@ -5,6 +5,7 @@ mongo: { 'col': 'serviceapidescriptions', 'invokers_col': 'invokerdetails', 'capif_users_col': "user", + 'certs_col': "certs", 'host': 'mongo', 'port': "27017" } diff --git a/services/TS29222_CAPIF_Discover_Service_API/service_apis/controllers/default_controller.py b/services/TS29222_CAPIF_Discover_Service_API/service_apis/controllers/default_controller.py index d19229470084af0d4d0cc367ff62d0845d0b729f..f742f499500f488c5a78f871d9b98eb66e3edc77 100644 --- a/services/TS29222_CAPIF_Discover_Service_API/service_apis/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Discover_Service_API/service_apis/controllers/default_controller.py @@ -1,13 +1,44 @@ import json - +from functools import wraps +from cryptography import x509 +from cryptography.hazmat.backends import default_backend from flask import current_app, request from service_apis.models.discovered_apis import DiscoveredAPIs # noqa: E501 from ..core.discoveredapis import DiscoverApisOperations, return_negotiated_supp_feat_dict +from ..core.validate_user import ControlAccess discover_apis = DiscoverApisOperations() +valid_user = ControlAccess() + +def cert_validation(): + def _cert_validation(f): + @wraps(f) + def __cert_validation(*args, **kwargs): + + args = request.view_args + 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()) + + cn = cert.subject.get_attributes_for_oid(x509.OID_COMMON_NAME)[0].value.strip() + + if cn != "superadmin": + cert_signature = cert.signature.hex() + current_app.logger.debug(request.args) + result = valid_user.validate_user_cert(request.args["api-invoker-id"], cert_signature) + + if result is not None: + return result + + result = f(**kwargs) + return result + return __cert_validation + return _cert_validation +@cert_validation() def all_service_apis_get(api_invoker_id, api_name=None, api_version=None, comm_type=None, protocol=None, aef_id=None, data_format=None, api_cat=None, preferred_aef_loc=None, req_api_prov_name=None, supported_features=None, api_supported_features=None, ue_ip_addr=None, service_kpis=None, grant_types=None): # noqa: E501 """all_service_apis_get diff --git a/services/TS29222_CAPIF_Discover_Service_API/service_apis/core/validate_user.py b/services/TS29222_CAPIF_Discover_Service_API/service_apis/core/validate_user.py new file mode 100644 index 0000000000000000000000000000000000000000..6d2ea40f361cccaf4182100cef3b59f7c5929a1c --- /dev/null +++ b/services/TS29222_CAPIF_Discover_Service_API/service_apis/core/validate_user.py @@ -0,0 +1,32 @@ +import json + +from flask import Response, current_app + +from ..encoder import CustomJSONEncoder +from ..models.problem_details import ProblemDetails +from ..util import serialize_clean_camel_case +from .resources import Resource +from .responses import internal_server_error + + +class ControlAccess(Resource): + + def validate_user_cert(self, api_invoker_id, cert_signature): + + cert_col = self.db.get_col_by_name(self.db.certs_col) + + try: + + my_query = {'id': api_invoker_id} + cert_entry = cert_col.find_one(my_query) + + if cert_entry is not None: + if cert_entry["cert_signature"] != cert_signature: + prob = ProblemDetails(title="Unauthorized", detail="User not authorized", cause="You are not the owner of this resource") + prob = serialize_clean_camel_case(prob) + return Response(json.dumps(prob, cls=CustomJSONEncoder), status=401, mimetype="application/json") + + except Exception as e: + exception = "An exception occurred in validate invoker" + current_app.logger.error(exception + "::" + str(e)) + return internal_server_error(detail=exception, cause=str(e)) \ No newline at end of file diff --git a/services/TS29222_CAPIF_Discover_Service_API/service_apis/db/db.py b/services/TS29222_CAPIF_Discover_Service_API/service_apis/db/db.py index 39b1889d6b77c982f18986385e4fc04a7178d583..15e7e13816b03e53c5ceee36f67e6ccadc00ba4b 100644 --- a/services/TS29222_CAPIF_Discover_Service_API/service_apis/db/db.py +++ b/services/TS29222_CAPIF_Discover_Service_API/service_apis/db/db.py @@ -20,6 +20,7 @@ class MongoDatabse(): self.invoker_col = self.config['mongo']['invokers_col'] self.service_api_descriptions = self.config['mongo']['col'] self.capif_users = self.config['mongo']['capif_users_col'] + self.certs_col = self.config['mongo']['certs_col'] def get_col_by_name(self, name): diff --git a/services/TS29222_CAPIF_Events_API/capif_events/controllers/default_controller.py b/services/TS29222_CAPIF_Events_API/capif_events/controllers/default_controller.py index f55aeb4065e858b33c2f5cd93123d50dffa16974..5e6cba2d4dbfe35e3d36889516257fe31105ca98 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/controllers/default_controller.py @@ -27,7 +27,10 @@ def cert_validation(): if cn != "superadmin": cert_signature = cert.signature.hex() - result = valid_user.validate_user_cert(args["subscriptionId"], args["subscriberId"], cert_signature) + if request.method != 'POST': + result = valid_user.validate_user_cert(args["subscriptionId"], args["subscriberId"], cert_signature) + else: + result = valid_user.validate_user_cert(None, args["subscriberId"], cert_signature) if result is not None: return result @@ -37,6 +40,8 @@ def cert_validation(): return __cert_validation return _cert_validation + +@cert_validation() def subscriber_id_subscriptions_post(subscriber_id, body): # noqa: E501 """subscriber_id_subscriptions_post @@ -76,7 +81,7 @@ def subscriber_id_subscriptions_subscription_id_delete(subscriber_id, subscripti return res - +@cert_validation() def subscriber_id_subscriptions_subscription_id_patch(subscriber_id, subscription_id, body): # noqa: E501 """subscriber_id_subscriptions_subscription_id_patch @@ -97,7 +102,7 @@ def subscriber_id_subscriptions_subscription_id_patch(subscriber_id, subscriptio res = events_ops.patch_event(body, subscriber_id, subscription_id) return res - +@cert_validation() def subscriber_id_subscriptions_subscription_id_put(subscriber_id, subscription_id, body): # noqa: E501 """subscriber_id_subscriptions_subscription_id_put diff --git a/services/TS29222_CAPIF_Events_API/capif_events/core/validate_user.py b/services/TS29222_CAPIF_Events_API/capif_events/core/validate_user.py index 2357408ad19ba5f87716e395dbe9f41419609cc3..8d1f8b05fad5eda954a723ae355e22214dc27574 100644 --- a/services/TS29222_CAPIF_Events_API/capif_events/core/validate_user.py +++ b/services/TS29222_CAPIF_Events_API/capif_events/core/validate_user.py @@ -20,13 +20,17 @@ class ControlAccess(Resource): cert_entry = cert_col.find_one(my_query) if cert_entry is not None: - if cert_entry["cert_signature"] != cert_signature or "event_subscriptions" not in cert_entry["resources"] or event_id not in cert_entry["resources"]["event_subscriptions"]: - prob = ProblemDetails(title="Unauthorized", detail="User not authorized", cause="You are not the owner of this resource") - prob = serialize_clean_camel_case(prob) + if (event_id is None and cert_entry["cert_signature"] != cert_signature): + prob = ProblemDetails(title="Unauthorized", detail="User not authorized", cause="You are not the owner of this resource") + prob = serialize_clean_camel_case(prob) - return Response(json.dumps(prob, cls=CustomJSONEncoder), status=401, mimetype="application/json") + return Response(json.dumps(prob, cls=CustomJSONEncoder), status=401, mimetype="application/json") + elif event_id is not None and (cert_entry["cert_signature"] != cert_signature or "event_subscriptions" not in cert_entry["resources"] or event_id not in cert_entry["resources"]["event_subscriptions"]): + prob = ProblemDetails(title="Unauthorized", detail="User not authorized", cause="You are not the owner of this resource") + prob = serialize_clean_camel_case(prob) + return Response(json.dumps(prob, cls=CustomJSONEncoder), status=401, mimetype="application/json") except Exception as e: exception = "An exception occurred in validate subscriber" 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_Logging_API_Invocation_API/api_invocation_logs/controllers/default_controller.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/controllers/default_controller.py index c91cce607df8537602853d68f63f36d4d09e1735..f23e60283788d794ceb6f33cb503bf09705d7291 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/controllers/default_controller.py @@ -2,6 +2,7 @@ from api_invocation_logs.models.invocation_log import InvocationLog # noqa: E50 from cryptography import x509 from cryptography.hazmat.backends import default_backend from flask import current_app, request +from functools import wraps from ..core.invocationlogs import LoggingInvocationOperations from ..core.validate_user import ControlAccess @@ -10,6 +11,7 @@ logging_invocation_operations = LoggingInvocationOperations() valid_user = ControlAccess() + def cert_validation(): def _cert_validation(f): @wraps(f) @@ -36,6 +38,7 @@ def cert_validation(): return _cert_validation +@cert_validation() def aef_id_logs_post(aef_id, body): # noqa: E501 """aef_id_logs_post diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/db/db.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/db/db.py index 1999a2daf6fe02fafa11228a93bfdd50bf1de243..5cf1f6c7c60b623c316bfa0385020fbb09484618 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/db/db.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/db/db.py @@ -22,6 +22,7 @@ class MongoDatabse(): self.provider_details = self.config['mongo']['prov_col'] self.service_apis = self.config['mongo']['serv_col'] self.capif_users = self.config['mongo']['capif_users_col'] + self.certs_col = self.config['mongo']['certs_col'] def get_col_by_name(self, name): return self.db[name] diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/config.yaml b/services/TS29222_CAPIF_Logging_API_Invocation_API/config.yaml index 0625fc64a5792a5246f81ea9ab556c23b984a384..8b8825bdb06039ebabccc4307e280f2bb9460b29 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/config.yaml +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/config.yaml @@ -7,6 +7,7 @@ mongo: { 'prov_col': 'providerenrolmentdetails', 'serv_col': 'serviceapidescriptions', 'capif_users_col': "user", + 'certs_col': "certs", 'host': 'mongo', 'port': "27017" } diff --git a/tests/features/CAPIF Api Events/capif_events_api.robot b/tests/features/CAPIF Api Events/capif_events_api.robot index 4b37d0a6bd2d162ede1c6c8f0df0c49a14f8b6af..4da4228d806396aad0188ea7cb78a321804e3a2e 100644 --- a/tests/features/CAPIF Api Events/capif_events_api.robot +++ b/tests/features/CAPIF Api Events/capif_events_api.robot @@ -532,7 +532,7 @@ Provider receives an ACL unavailable event when invoker remove Security Context. ... json=${request_body} ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt - ... username=${INVOKER_USERNAME} + ... username=${AMF_PROVIDER_USERNAME} # Check Results Check Response Variable Type And Values ${resp} 201 EventSubscription @@ -603,7 +603,7 @@ Invoker receives an Invoker Authorization Revoked and ACL unavailable event when ... notification_destination=${NOTIFICATION_DESTINATION_URL}/testing ... supported_features=4 ${resp}= Post Request Capif - ... /capif-events/v1/${register_user_info_provider['amf_id']}/subscriptions + ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions ... json=${request_body} ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt @@ -1069,7 +1069,7 @@ Provider receives an ACL unavailable event when invoker remove Security Context ... json=${request_body} ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt - ... username=${INVOKER_USERNAME} + ... username=${AMF_PROVIDER_USERNAME} # Check Results Check Response Variable Type And Values ${resp} 201 EventSubscription @@ -1141,7 +1141,7 @@ Invoker receives an Invoker Authorization Revoked and ACL unavailable event when ... notification_destination=${NOTIFICATION_DESTINATION_URL}/testing ... supported_features=0 ${resp}= Post Request Capif - ... /capif-events/v1/${register_user_info_provider['amf_id']}/subscriptions + ... /capif-events/v1/${register_user_info_invoker['api_invoker_id']}/subscriptions ... json=${request_body} ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt diff --git a/tests/features/Event Filter/event_filter.robot b/tests/features/Event Filter/event_filter.robot index 9853646b4fdf8acda651790e2ae0053694a18499..a480248eee5c4060d41813ece614d854810c0df9 100644 --- a/tests/features/Event Filter/event_filter.robot +++ b/tests/features/Event Filter/event_filter.robot @@ -1007,7 +1007,7 @@ Send Log Message to CAPIF ... json=${request_body} ... server=${CAPIF_HTTPS_URL} ... verify=ca.crt - ... username=${provider_info['amf_username']} + ... username=${provider_info['aef_username']} Check Response Variable Type And Values ${resp} 201 InvocationLog ${resource_url}= Check Location Header ${resp} ${LOCATION_LOGGING_RESOURCE_REGEX}