diff --git a/README.md b/README.md index 2c05ce9c..567cc7b9 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,14 @@ Changes at Tests: * Delete automatically the entry in the security info of the security context if the provider that has the aef that published the service is deleted * Delete automatically the entry in the security info of the security context if the service on which that context was created is deleted +# CAPIF Tool Release 3.1.1 + +* Minor Fixes in Logging/Auditing Service +* Update redis version + +Changes at Tests: +* Add test plan to logging/auditing service +* Add tests to cover logging/auditing test plan diff --git a/docs/test_plan/README.md b/docs/test_plan/README.md index e52615ce..38cff4b4 100644 --- a/docs/test_plan/README.md +++ b/docs/test_plan/README.md @@ -8,6 +8,8 @@ List of Common API Services implemented: * [Api Discover Service](./api_discover_service/README.md) * [Api Events Service](./api_events_service/README.md) * [Api Security Service](./api_security_service/README.md) +* [Api Logging Service](./api_logging_service/README.md) +* [Api Auditing Service](./api_auditing_service/README.md) [Return To Main]: ../../README.md#test-plan-documentation \ No newline at end of file diff --git a/docs/test_plan/api_auditing_service/README.md b/docs/test_plan/api_auditing_service/README.md new file mode 100644 index 00000000..bd3204c0 --- /dev/null +++ b/docs/test_plan/api_auditing_service/README.md @@ -0,0 +1,244 @@ +[**[Return To All Test Plans]**] + +- [Test Plan for CAPIF Api Auditing Service](#test-plan-for-capif-api-auditing-service) +- [Tests](#tests) + - [Test Case 1: Get a CAPIF Log Entry.](#test-case-1-creates-a-new-individual-capif-log-entry) + + +# Test Plan for CAPIF Api Auditing Service +At this documentation you will have all information and related files and examples of test plan for this API. + +# Tests + +## Test Case 1: Get CAPIF Log Entry. +* Test ID: ***capif_api_auditing-1*** +* Description: + + This test case will check that a CAPIF AMF can get log entry to Logging Service +* Pre-Conditions: + + * CAPIF provider is pre-authorised (has valid AMF cert from CAPIF Authority) + * Service exist in CAPIF + * Invoker exist in CAPIF + * Log Entry exist in CAPIF + +* Information of Test: + + 1. Perform [provider onboarding], [invoker onboarding] + + 2. Publish Service API at CCF: + - Send Post to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis + - body [service api description] with apiName service_1 + - Use APF Certificate + + 3. Create Log Entry: + - Send POST to *https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs* + - body [log entry request body] + - Use AEF Certificate + + 4. Get Log: + 1. Send GET to *https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs?aef-id={aefId}&api-invoker-id={api-invoker-id}* + 2. Use AMF Certificate + +* Execution Steps: + 1. Register Provider and Invoker CCF + 2. Publish Service + 3. Create Log Entry + 4. Get Log Entry + +* Expected Result: + + 1. Response to Logging Service must accomplish: + 1. **200 OK** + 2. Response Body must follow **InvocationLog** data structure with: + * aefId + * apiInvokerId + * logs + +## Test Case 2: Get CAPIF Log Entry With no Log entry in CAPIF. +* Test ID: ***capif_api_auditing-2*** +* Description: + + This test case will check that a CAPIF AEF can create log entry to Logging Service +* Pre-Conditions: + + * CAPIF provider is pre-authorised (has valid AMF cert from CAPIF Authority) + * Service exist in CAPIF + * Invoker exist in CAPIF + + +* Information of Test: + + 1. Perform [provider onboarding], [invoker onboarding] + + 2. Publish Service API at CCF: + - Send Post to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis + - body [service api description] with apiName service_1 + - Use APF Certificate + + 4. Get Log: + 1. Send GET to *https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs?aef-id={aefId}&api-invoker-id={api-invoker-id}* + 2. Use AMF Certificate + +* Execution Steps: + 1. Register Provider and Invoker CCF + 2. Publish Service + 3. Get Log Entry + +* Expected Result: + + 1. Response to Logging Service must accomplish: + 1. **404 Not Found** + 2. Error Response Body must accomplish with **ProblemDetails** data structure with: + * status 404 + * title with message "Not Found Log Entry in CAPIF". + * cause with message "Not Exist Logs with the filters applied". + + +## Test Case 3: Get CAPIF Log Entry without aef-id and api-invoker-id. +* Test ID: ***capif_api_auditing-3*** +* Description: + + This test case will check that a CAPIF AEF can create log entry to Logging Service +* Pre-Conditions: + + * CAPIF provider is no pre-authorised (has no valid AMF cert from CAPIF Authority) + * Service exist in CAPIF + * Invoker exist in CAPIF + * Log Entry exist in CAPIF + +* Information of Test: + + 1. Perform [provider onboarding], [invoker onboarding] + + 2. Publish Service API at CCF: + - Send Post to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis + - body [service api description] with apiName service_1 + - Use APF Certificate + + 3. Create Log Entry: + - Send POST to *https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs* + - body [log entry request body] + - Use AEF Certificate + + 4. Get Log: + 1. Send GET to *https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs + 2. Use AMF Certificate + +* Execution Steps: + 1. Register Provider and Invoker CCF + 2. Publish Service + 3. Create Log Entry + 4. Get Log Entry + +* Expected Result: + + 1. Response to Logging Service must accomplish: + 1. **400 Bad Request** + 2. Error Response Body must accomplish with **ProblemDetails** data structure with: + * status 400 + * title with message "Bad Request" + * detail with message "aef_id and api_invoker_id parameters are mandatory". + * cause with message "Mandatory parameters missing". + + +## Test Case 4: Get CAPIF Log Entry with filtter api-version. +* Test ID: ***capif_api_auditing-4*** +* Description: + + This test case will check that a CAPIF AMF can get log entry to Logging Service +* Pre-Conditions: + + * CAPIF provider is pre-authorised (has valid AMF cert from CAPIF Authority) + * Service exist in CAPIF + * Invoker exist in CAPIF + * Log Entry exist in CAPIF + +* Information of Test: + + 1. Perform [provider onboarding], [invoker onboarding] + + 2. Publish Service API at CCF: + - Send Post to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis + - body [service api description] with apiName service_1 + - Use APF Certificate + + 3. Create Log Entry: + - Send POST to *https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs* + - body [log entry request body] + - Use AEF Certificate + + 4. Get Log: + 1. Send GET to *https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs?aef-id={aefId}&api-invoker-id={api-invoker-id}&api-version={v1}* + 2. Use AMF Certificate + +* Execution Steps: + 1. Register Provider and Invoker CCF + 2. Publish Service + 3. Create Log Entry + 4. Get Log Entry + +* Expected Result: + + 1. Response to Logging Service must accomplish: + 1. **200 OK** + 2. Response Body must follow **InvocationLog** data structure with: + * aefId + * apiInvokerId + * logs + + +## Test Case 5: Get CAPIF Log Entry with filter api-version but not exist in log entry. +* Test ID: ***capif_api_auditing-4*** +* Description: + + This test case will check that a CAPIF AMF can get log entry to Logging Service +* Pre-Conditions: + + * CAPIF provider is pre-authorised (has valid AMF cert from CAPIF Authority) + * Service exist in CAPIF + * Invoker exist in CAPIF + * Log Entry exist in CAPIF + +* Information of Test: + + 1. Perform [provider onboarding], [invoker onboarding] + + 2. Publish Service API at CCF: + - Send Post to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis + - body [service api description] with apiName service_1 + - Use APF Certificate + + 3. Create Log Entry: + - Send POST to *https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs* + - body [log entry request body] + - Use AEF Certificate + + 4. Get Log: + 1. Send GET to *https://{CAPIF_HOSTNAME}/logs/v1/apiInvocationLogs?aef-id={aefId}&api-invoker-id={api-invoker-id}&api-version={v58}* + 2. Use AMF Certificate + +* Execution Steps: + 1. Register Provider and Invoker CCF + 2. Publish Service + 3. Create Log Entry + 4. Get Log Entry + +* Expected Result: + + 1. Response to Logging Service must accomplish: + 1. **404 Not Found** + 2. Error Response Body must accomplish with **ProblemDetails** data structure with: + * status 404 + * detail with message "Parameters do not match any log entry" + * cause with message "No logs found". + + + +[log entry request body]: ../api_logging_service/invocation_log.json "Log Request Body" + +[invoker onboarding]: ../common_operations/README.md#register-an-invoker "Invoker Onboarding" + +[provider onboarding]: ../common_operations/README.md#register-a-provider "Provider Onboarding" + +[Return To All Test Plans]: ../README.md \ No newline at end of file diff --git a/docs/test_plan/api_logging_service/README.md b/docs/test_plan/api_logging_service/README.md new file mode 100644 index 00000000..913a652b --- /dev/null +++ b/docs/test_plan/api_logging_service/README.md @@ -0,0 +1,241 @@ +[**[Return To All Test Plans]**] + +- [Test Plan for CAPIF Api Logging Service](#test-plan-for-capif-api-logging-service) +- [Tests](#tests) + - [Test Case 1: Creates a new individual CAPIF Log Entry.](#test-case-1-creates-a-new-individual-capif-log-entry) + - [Test Case 2: Creates a new individual CAPIF Log Entry with Invalid aefID](#test-case-2-creates-a-new-individual-capif-log-entry-with-invalid-aefid) + - [Test Case 3: Creates a new individual CAPIF Log Entry with Invalid serviceAPI](#test-case-3-creates-a-new-individual-capif-log-entry-with-invalid-serviceapi) + - [Test Case 4: Creates a new individual CAPIF Log Entry with Invalid apiInvokerId](#test-case-4-creates-a-new-individual-capif-log-entry-with-invalid-apiinvokerid) + + - [Test Case 5: Creates a new individual CAPIF Log Entry with differnted aef_id in body and request](#test-case-5-creates-a-new-individual-capif-log-entry-with-invalid-aefid-in-body) + + +# Test Plan for CAPIF Api Logging Service +At this documentation you will have all information and related files and examples of test plan for this API. + +# Tests + +## Test Case 1: Creates a new individual CAPIF Log Entry. +* Test ID: ***capif_api_logging-1*** +* Description: + + This test case will check that a CAPIF AEF can create log entry to Logging Service +* Pre-Conditions: + + * CAPIF provider is pre-authorised (has valid aefId from CAPIF Authority) + * Service exist in CAPIF + * Invoker exist in CAPIF + +* Information of Test: + + 1. Perform [provider onboarding] and [invoker onboarding] + + 2. Publish Service API at CCF: + - Send Post to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis + - body [service api description] with apiName service_1 + - Use APF Certificate + + 3. Log Entry: + 1. Send POST to *https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs* + 2. body [log entry request body] + 3. Use AEF Certificate + +* Execution Steps: + 1. Register Provider and Invoker CCF + 2. Publish Service + 3. Create Log Entry + +* Expected Result: + + 1. Response to Logging Service must accomplish: + 1. **201 Created** + 2. Response Body must follow **InvocationLog** data structure with: + * aefId + * apiInvokerId + * logs + 3. Response Header **Location** must be received with URI to new resource created, following this structure: *{apiRoot}/api-invocation-logs/v1/{aefId}/logs/{logId}* + + + + +## Test Case 2: Creates a new individual CAPIF Log Entry with Invalid aefId +* Test ID: ***capif_api_logging-2*** +* Description: + + This test case will check that a CAPIF subscriber (AEF) cannot create Log Entry without valid aefId +* Pre-Conditions: + + * CAPIF provider is not pre-authorised (has not valid aefId from CAPIF Authority) + * Service exist in CAPIF + * Invoker exist in CAPIF + +* Information of Test: + + 1. Perform [provider onboarding] and [invoker onboarding] + + 2. Publish Service API at CCF: + - Send Post to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis + - body [service api description] with apiName service_1 + - Use APF Certificate + + 3. Log Entry: + 1. Send POST to *https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{not-valid-aefId}/logs* + 2. body [log entry request body] + 3. Use AEF Certificate + +* Execution Steps: + 1. Register Provider and Invoker CCF + 2. Publish Service + 3. Create Log Entry + +* Expected Result: + + 1. Response to Logging Service must accomplish: + 1. **404 Not Found** + 2. Error Response Body must accomplish with **ProblemDetails** data structure with: + * status 404 + * title with message "Not Found" + * detail with message "Exposer not exist". + * cause with message "Exposer id not found". + +## Test Case 3: Creates a new individual CAPIF Log Entry with Invalid serviceAPI +* Test ID: ***capif_api_logging-3*** +* Description: + + This test case will check that a CAPIF subscriber (AEF) cannot create Log Entry without valid aefId +* Pre-Conditions: + + * CAPIF subscriber is pre-authorised (has valid aefId from CAPIF Authority) + +* Information of Test: + + 1. Perform [provider onboarding] and [invoker onboarding] + + 2. Publish Service API at CCF: + - Send Post to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis + - body [service api description] with apiName service_1 + - Use APF Certificate + + 3. Log Entry: + 1. Send POST to *https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs* + 2. body [log entry request body with serviceAPI apiName apiId not valid] + 3. Use AEF Certificate + +* Execution Steps: + 1. Register Provider and Invoker CCF + 2. Publish Service + 3. Create Log Entry + +* Expected Result: + + 1. Response to Logging Service must accomplish: + 1. **404 Not Found** + 2. Error Response Body must accomplish with **ProblemDetails** data structure with: + * status 404 + * title with message "Not Found" + * detail with message "Invoker not exist". + * cause with message "Invoker id not found". + + + +## Test Case 4: Creates a new individual CAPIF Log Entry with Invalid apiInvokerId +* Test ID: ***capif_api_logging-4*** +* Description: + + This test case will check that a CAPIF subscriber (AEF) cannot create Log Entry without valid aefId +* Pre-Conditions: + + * CAPIF subscriber is pre-authorised (has valid aefId from CAPIF Authority) + +* Information of Test: + + 1. Perform [provider onboarding] and [invoker onboarding] + + 2. Publish Service API at CCF: + - Send Post to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis + - body [service api description] with apiName service_1 + - Use APF Certificate + + 3. Log Entry: + 1. Send POST to *https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs* + 2. body [log entry request body with invokerId not valid] + 3. Use AEF Certificate + +* Execution Steps: + 1. Register Provider and Invoker CCF + 2. Publish Service + 3. Create Log Entry + +* Expected Result: + + 1. Response to Onboard request must accomplish: + 1. **201 Created** response. + 2. body returned must accomplish **APIProviderEnrolmentDetails** data structure. + 3. For each **apiProvFuncs**, we must check: + 1. **apiProvFuncId** is set + 2. **apiProvCert** under **regInfo** is set properly + 5. Location Header must contain the new resource URL *{apiRoot}/api-provider-management/v1/registrations/{registrationId}* + + 2. Response to Logging Service must accomplish: + 1. **404 Not Found** + 2. Error Response Body must accomplish with **ProblemDetails** data structure with: + * status 404 + * title with message "Not Found" + * detail with message "Invoker not exist". + * cause with message "Invoker id not found". + + 3. Log Entry are not stored in CAPIF Database + + +## Test Case 5: Creates a new individual CAPIF Log Entry with Invalid aefId in body +* Test ID: ***capif_api_logging-5*** +* Description: + + This test case will check that a CAPIF subscriber (AEF) cannot create Log Entry without valid aefId in body +* Pre-Conditions: + + * CAPIF provider is pre-authorised (has valid apfId from CAPIF Authority) + * Service exist in CAPIF + * Invoker exist in CAPIF + +* Information of Test: + + 1. Perform [provider onboarding] and [invoker onboarding] + + 2. Publish Service API at CCF: + - Send Post to ccf_publish_url https://{CAPIF_HOSTNAME}/published-apis/v1/{apfId}/service-apis + - body [service api description] with apiName service_1 + - Use APF Certificate + + 3. Log Entry: + 1. Send POST to *https://{CAPIF_HOSTNAME}/api-invocation-logs/v1/{aefId}/logs* + 2. body [log entry request body with bad aefId] + 3. Use AEF Certificate + +* Execution Steps: + 1. Register Provider and Invoker CCF + 2. Publish Service + 3. Create Log Entry + +* Expected Result: + + 1. Response to Logging Service must accomplish: + 1. **401 Unauthorized** + 2. Error Response Body must accomplish with **ProblemDetails** data structure with: + * status 401 + * title with message "Unauthorized" + * detail with message "AEF id not matching in request and body". + * cause with message "Not identical AEF id". + + + + + + +[log entry request body]: ./invocation_log.json "Log Request Body" + +[invoker onboarding]: ../common_operations/README.md#register-an-invoker "Invoker Onboarding" + +[provider onboarding]: ../common_operations/README.md#register-a-provider "Provider Onboarding" + +[Return To All Test Plans]: ../README.md diff --git a/docs/test_plan/api_logging_service/invocation_log.json b/docs/test_plan/api_logging_service/invocation_log.json new file mode 100644 index 00000000..ceabcf02 --- /dev/null +++ b/docs/test_plan/api_logging_service/invocation_log.json @@ -0,0 +1,45 @@ +{ + "aefId": "aefId", + "apiInvokerId": "apiInvokerId", + "logs": [ + { + "apiId": "apiId", + "apiName": "apiName", + "apiVersion": "string", + "resourceName": "string", + "uri": "string", + "protocol": "HTTP_1_1", + "operation": "GET", + "result": "string", + "invocationTime": "2023-03-30T10:30:21.404Z", + "invocationLatency": 0, + "inputParameters": "string", + "outputParameters": "string", + "srcInterface": { + "ipv4Addr": "string", + "ipv6Addr": "string", + "fqdn": "string", + "port": 65535, + "apiPrefix": "string", + "securityMethods": [ + "PSK", + "Oauth" + ] + }, + "destInterface": { + "ipv4Addr": "string", + "ipv6Addr": "string", + "fqdn": "string", + "port": 65535, + "apiPrefix": "string", + "securityMethods": [ + "PSK", + "string" + ] + }, + "fwdInterface": "string" + } + ], + "supportedFeatures": "string" + } + \ No newline at end of file diff --git a/services/TS29222_CAPIF_API_Invoker_Management_API/requirements.txt b/services/TS29222_CAPIF_API_Invoker_Management_API/requirements.txt index de9d2229..8246329f 100644 --- a/services/TS29222_CAPIF_API_Invoker_Management_API/requirements.txt +++ b/services/TS29222_CAPIF_API_Invoker_Management_API/requirements.txt @@ -7,5 +7,5 @@ pymongo == 4.0.1 flask_jwt_extended == 4.4.4 pyopenssl == 23.0.0 rfc3987 == 1.3.8 -redis == 4.5.1 +redis == 4.5.4 flask_executor == 1.0.0 \ No newline at end of file diff --git a/services/TS29222_CAPIF_API_Provider_Management_API/requirements.txt b/services/TS29222_CAPIF_API_Provider_Management_API/requirements.txt index 84332fb4..41f824fa 100644 --- a/services/TS29222_CAPIF_API_Provider_Management_API/requirements.txt +++ b/services/TS29222_CAPIF_API_Provider_Management_API/requirements.txt @@ -4,7 +4,7 @@ python_dateutil >= 2.6.0 setuptools >= 21.0.0 Flask == 2.0.3 pymongo == 4.0.1 -redis == 4.5.1 +redis == 4.5.4 flask_executor == 1.0.0 flask_jwt_extended == 4.4.4 pyopenssl == 23.0.0 diff --git a/services/TS29222_CAPIF_Auditing_API/logs/__main__.py b/services/TS29222_CAPIF_Auditing_API/logs/__main__.py index 7b8eda5e..9ca5f5d5 100644 --- a/services/TS29222_CAPIF_Auditing_API/logs/__main__.py +++ b/services/TS29222_CAPIF_Auditing_API/logs/__main__.py @@ -37,4 +37,4 @@ def verbose_formatter(): configure_logging(app.app) if __name__ == '__main__': - app.run(debug=True, port=8080) + app.run(port=8080) 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 68dc6581..c330f47d 100644 --- a/services/TS29222_CAPIF_Auditing_API/logs/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Auditing_API/logs/controllers/default_controller.py @@ -16,6 +16,7 @@ from cryptography import x509 from cryptography.hazmat.backends import default_backend import pymongo +from ..core.responses import bad_request_error audit_operations = AuditOperations() @@ -59,6 +60,11 @@ def api_invocation_logs_get(aef_id=None, api_invoker_id=None, time_range_start=N current_app.logger.info("Audit logs") + if aef_id is None or api_invoker_id is None: + return bad_request_error(detail="aef_id and api_invoker_id parameters are mandatory", + cause="Mandatory parameters missing", invalid_params=[ + {"param": "aef_id or api_invoker_id", "reason": "missing"}]) + time_range_start = util.deserialize_datetime(time_range_start) time_range_end = util.deserialize_datetime(time_range_end) diff --git a/services/TS29222_CAPIF_Auditing_API/logs/core/auditoperations.py b/services/TS29222_CAPIF_Auditing_API/logs/core/auditoperations.py index 25418ead..ac83701c 100644 --- a/services/TS29222_CAPIF_Auditing_API/logs/core/auditoperations.py +++ b/services/TS29222_CAPIF_Auditing_API/logs/core/auditoperations.py @@ -2,10 +2,13 @@ from flask import current_app, Flask, Response import json - +import sys +from datetime import datetime from .resources import Resource from bson import json_util +from ..util import dict_to_camel_case, clean_empty from .responses import bad_request_error, not_found_error, forbidden_error, internal_server_error, make_response +from ..models.invocation_log import InvocationLog class AuditOperations (Resource): @@ -17,57 +20,48 @@ def get_logs(self, query_parameters): current_app.logger.debug("Find invocation logs") try: + result = mycol.find_one({'aef_id': query_parameters['aef_id'], 'api_invoker_id': query_parameters['api_invoker_id']}, {"_id": 0}) + + if result is None: + return not_found_error(detail="aefId or/and apiInvokerId do not match any InvocationLogs", cause="No log invocations found") + + logs = result['logs'].copy() + + query_params = dict((k,v) for k,v in query_parameters.items() if v is not None and k != 'aef_id' and k != 'api_invoker_id') - my_params = [] - my_query = {} - - query_params_name = { - "aef_id": "aef_id", - "api_invoker_id": "api_invoker_id", - "api_id": "logs.api_id", - "api_name": "logs.api_name", - "api_version": "logs.api_version", - "protocol": "logs.protocol", - "operation": "logs.operation", - "result": "logs.result", - "resource_name": "logs.resource_name", - "supported_features": "supported_features" - } - - for param in query_parameters: - if param in query_params_name and query_parameters[param] is not None: - my_params.append({query_params_name[param]: query_parameters[param]}) - - if query_parameters["time_range_start"] is not None and query_parameters["time_range_end"] is not None: - my_params.append({"logs.invocation_time": {'$gte': query_parameters["time_range_start"], '$lt': query_parameters["time_range_end"]}}) - elif query_parameters["time_range_start"] is not None: - my_params.append({"logs.invocation_time": {'$gte': query_parameters["time_range_start"]}}) - elif query_parameters["time_range_end"] is not None: - my_params.append({"logs.invocation_time": {'$lt': query_parameters["time_range_end"]}}) - - if query_parameters["src_interface"] is not None: - src_int_json = json.loads(query_parameters["src_interface"]) - ipv4_addr = src_int_json["ipv4Addr"] - port = src_int_json["port"] - security_methods = src_int_json["securityMethods"] - my_params.append({"logs.src_interface.ipv4_addr": ipv4_addr, "logs.src_interface.port": port, "logs.src_interface.security_methods": security_methods}) - - if query_parameters["dest_interface"] is not None: - dest_int_json = json.loads(query_parameters["dest_interface"]) - ipv4_addr = dest_int_json["ipv4Addr"] - port = dest_int_json["port"] - security_methods = dest_int_json["securityMethods"] - my_params.append({"logs.dest_interface.ipv4_addr": ipv4_addr, "logs.dest_interface.port": port, "logs.dest_interface.security_methods": security_methods}) - - if my_params: - my_query = {"$and": my_params} - - logs = mycol.find(my_query, {"_id":0}) - audit_logs = [] for log in logs: - audit_logs.append(log) - res = make_response(object=audit_logs, status=200) + for param in query_params: + if param == 'time_range_start': + if query_params[param] > log['invocation_time'].astimezone(query_params[param].tzinfo): + result['logs'].remove(log) + break + elif param == 'time_range_end': + if query_params[param] < log['invocation_time'].astimezone(query_params[param].tzinfo): + result['logs'].remove(log) + break + elif param == 'src_interface' or param == 'dest_interface': + interface = json.loads(query_params[param]) + if 'security_methods' not in interface: + return bad_request_error(detail="security_methods is mandatory", + cause="security_methods parameter missing", invalid_params=[ + {"param": "security_methods", "reason": "missing"}]) + for key in interface: + if log[param][key] != interface[key]: + result['logs'].remove(log) + break + elif log[param] != query_params[param]: + result['logs'].remove(log) + break + + if not result['logs']: + return not_found_error(detail="Parameters do not match any log entry", cause="No logs found") + + + result = dict_to_camel_case(clean_empty(result)) + invocation_log = InvocationLog(result['aefId'], result['apiInvokerId'], result['logs'], + result['supportedFeatures']) + res = make_response(object=invocation_log, status=200) current_app.logger.debug("Found invocation logs") return res diff --git a/services/TS29222_CAPIF_Auditing_API/logs/util.py b/services/TS29222_CAPIF_Auditing_API/logs/util.py index 2c21fc81..ff812577 100644 --- a/services/TS29222_CAPIF_Auditing_API/logs/util.py +++ b/services/TS29222_CAPIF_Auditing_API/logs/util.py @@ -4,6 +4,44 @@ import typing from logs import typing_utils +def clean_empty(d): + if isinstance(d, dict): + return { + k: v + for k, v in ((k, clean_empty(v)) for k, v in d.items()) + if v + } + if isinstance(d, list): + return [v for v in map(clean_empty, d) if v] + return d + +def dict_to_camel_case(my_dict): + + + result = {} + + for attr, value in my_dict.items(): + + my_key = ''.join(word.title() for word in attr.split('_')) + my_key= ''.join([my_key[0].lower(), my_key[1:]]) + if my_key == "serviceApiCategory": + my_key = "serviceAPICategory" + + if isinstance(value, list): + result[my_key] = list(map( + lambda x: dict_to_camel_case(x) if isinstance(x, dict) else x, value )) + + elif hasattr(value, "to_dict"): + result[my_key] = dict_to_camel_case(value) + + elif isinstance(value, dict): + value = dict_to_camel_case(value) + result[my_key] = value + else: + result[my_key] = value + + return result + def _deserialize(data, klass): """Deserializes dict, list, str into an object. diff --git a/services/TS29222_CAPIF_Events_API/requirements.txt b/services/TS29222_CAPIF_Events_API/requirements.txt index 279753e4..f88cc1f4 100644 --- a/services/TS29222_CAPIF_Events_API/requirements.txt +++ b/services/TS29222_CAPIF_Events_API/requirements.txt @@ -7,6 +7,6 @@ pymongo == 4.0.1 flask_jwt_extended == 4.4.4 pyopenssl == 23.0.0 rfc3987 == 1.3.8 -redis == 4.5.1 +redis == 4.5.4 flask_executor == 1.0.0 diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/__main__.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/__main__.py index ae4b997f..11e13fe8 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/__main__.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/__main__.py @@ -36,6 +36,5 @@ def verbose_formatter(): configure_logging(app.app) - if __name__ == '__main__': app.run(port=8080) diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py index 24b4c66e..e221b58f 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/core/invocationlogs.py @@ -8,22 +8,29 @@ from ..db.db import MongoDatabse from ..encoder import JSONEncoder from ..models.problem_details import ProblemDetails +from pymongo import ReturnDocument +from ..util import dict_to_camel_case, clean_empty from .resources import Resource from .responses import bad_request_error, internal_server_error, forbidden_error, not_found_error, unauthorized_error, make_response +from ..models.invocation_log import InvocationLog class LoggingInvocationOperations(Resource): - def __check_aef(self, aef_id): + def __check_aef(self, request_aef_id, body_aef_id): + prov_col = self.db.get_col_by_name(self.db.provider_details) current_app.logger.debug("Checking aef id") - # aef_res = prov_col.find_one({'api_prov_dom_id': aef_id}) - aef_res = prov_col.find_one({'api_prov_funcs': {'$elemMatch': {'api_prov_func_role': 'AEF', 'api_prov_func_id': aef_id}}}) + aef_res = prov_col.find_one({'api_prov_funcs': {'$elemMatch': {'api_prov_func_role': 'AEF', 'api_prov_func_id': request_aef_id}}}) if aef_res is None: current_app.logger.error("Exposer not exist") - return unauthorized_error(detail="Exposer not exist", cause="Exposer id not found") + return not_found_error(detail="Exposer not exist", cause="Exposer id not found") + + if request_aef_id != body_aef_id: + return unauthorized_error(detail="AEF id not matching in request and body", cause="Not identical AEF id") + return None @@ -35,7 +42,7 @@ def __check_invoker(self, invoker_id): if invoker_res is None: current_app.logger.error("Invoker not exist") - return unauthorized_error(detail="Invoker not exist", cause="Invoker id not found") + return not_found_error(detail="Invoker not exist", cause="Invoker id not found") return None @@ -49,7 +56,7 @@ def __check_service_apis(self, api_id, api_name): detail = "Service API not exist" cause = "Service API with id {} and name {} not found".format(api_id, api_name) current_app.logger.error(detail) - return unauthorized_error(detail=detail, cause=cause) + return not_found_error(detail=detail, cause=cause) return None @@ -59,35 +66,51 @@ def add_invocationlog(self, aef_id, invocationlog): try: current_app.logger.debug("Adding invocation logs") - result = self.__check_aef(aef_id) + current_app.logger.debug("Check request aef_id") + result = self.__check_aef(aef_id, invocationlog.aef_id) if result is not None: return result + current_app.logger.debug("Check request api_invoker_id") result = self.__check_invoker(invocationlog.api_invoker_id) if result is not None: return result - for i in range(0, len(invocationlog.logs)): - result = self.__check_service_apis(invocationlog.logs[i].api_id, invocationlog.logs[i].api_name) + current_app.logger.debug("Check service apis") + for log in invocationlog.logs: + result = self.__check_service_apis(log.api_id, log.api_name) if result is not None: return result - log_id = secrets.token_hex(15) - rec = dict() - rec['log_id'] = log_id - rec.update(invocationlog.to_dict()) - mycol.insert_one(rec) + current_app.logger.debug("Check existing logs") + my_query = {'aef_id': aef_id, 'api_invoker_id': invocationlog.api_invoker_id} + existing_invocationlog = mycol.find_one(my_query) + + if existing_invocationlog is None: + current_app.logger.debug("Create new log") + log_id = secrets.token_hex(15) + rec = dict() + rec['log_id'] = log_id + rec.update(invocationlog.to_dict()) + mycol.insert_one(rec) + else: + current_app.logger.debug("Update existing log") + log_id = existing_invocationlog['log_id'] + updated_invocation_logs = invocationlog.to_dict() + for updated_invocation_log in updated_invocation_logs['logs']: + existing_invocationlog['logs'].append(updated_invocation_log) + mycol.find_one_and_update(my_query, {"$set": existing_invocationlog}, projection={'_id': 0, 'log_id': 0}, return_document=ReturnDocument.AFTER, upsert=False) - current_app.logger.debug("Invocation Logs inserted in database") res = make_response(object=invocationlog, status=201) + current_app.logger.debug("Invocation Logs response ready") res.headers['Location'] = "https://{}/api-invocation-logs/v1/{}/logs/{}".format(os.getenv('CAPIF_HOSTNAME'), str(aef_id), str(log_id)) return res except Exception as e: - exception = "An exception occurred in inserting invovation logs" + exception = "An exception occurred in inserting invocation logs" current_app.logger.error(exception + "::" + str(e)) return internal_server_error(detail=exception, cause=str(e)) diff --git a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/util.py b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/util.py index 3cfd7732..af555673 100644 --- a/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/util.py +++ b/services/TS29222_CAPIF_Logging_API_Invocation_API/api_invocation_logs/util.py @@ -4,6 +4,44 @@ import typing from api_invocation_logs import typing_utils +def clean_empty(d): + if isinstance(d, dict): + return { + k: v + for k, v in ((k, clean_empty(v)) for k, v in d.items()) + if v + } + if isinstance(d, list): + return [v for v in map(clean_empty, d) if v] + return d + +def dict_to_camel_case(my_dict): + + + result = {} + + for attr, value in my_dict.items(): + + my_key = ''.join(word.title() for word in attr.split('_')) + my_key= ''.join([my_key[0].lower(), my_key[1:]]) + if my_key == "serviceApiCategory": + my_key = "serviceAPICategory" + + if isinstance(value, list): + result[my_key] = list(map( + lambda x: dict_to_camel_case(x) if isinstance(x, dict) else x, value )) + + elif hasattr(value, "to_dict"): + result[my_key] = dict_to_camel_case(value) + + elif isinstance(value, dict): + value = dict_to_camel_case(value) + result[my_key] = value + else: + result[my_key] = value + + return result + def _deserialize(data, klass): """Deserializes dict, list, str into an object. diff --git a/services/TS29222_CAPIF_Publish_Service_API/requirements.txt b/services/TS29222_CAPIF_Publish_Service_API/requirements.txt index acf2b9c8..59398f0b 100644 --- a/services/TS29222_CAPIF_Publish_Service_API/requirements.txt +++ b/services/TS29222_CAPIF_Publish_Service_API/requirements.txt @@ -6,6 +6,6 @@ Flask == 2.0.3 pymongo == 4.0.1 flask_jwt_extended == 4.4.4 pyopenssl == 23.0.0 -redis == 4.5.1 +redis == 4.5.4 flask_executor == 1.0.0 diff --git a/services/TS29222_CAPIF_Security_API/requirements.txt b/services/TS29222_CAPIF_Security_API/requirements.txt index 578188ff..62f591df 100644 --- a/services/TS29222_CAPIF_Security_API/requirements.txt +++ b/services/TS29222_CAPIF_Security_API/requirements.txt @@ -7,5 +7,5 @@ pymongo == 4.0.1 flask_jwt_extended == 4.4.4 pyopenssl == 23.0.0 rfc3987 == 1.3.8 -redis == 4.5.1 +redis == 4.5.4 flask_executor == 1.0.0 diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 9230207b..aea4b43d 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -17,8 +17,7 @@ services: environment: - REDIS_REPLICATION_MODE=master api-invoker-management: - build: - context: ./TS29222_CAPIF_API_Invoker_Management_API + build: TS29222_CAPIF_API_Invoker_Management_API/. expose: - "8080" volumes: @@ -36,15 +35,13 @@ services: depends_on: - redis - nginx - # access-control-policy: # build: TS29222_CAPIF_Access_Control_Policy_API/. # expose: # - "8080" # image: dockerhub.hi.inet/evolved-5g/capif/access_control_policy_api:latest logs: - build: - context: ./TS29222_CAPIF_Auditing_API + build: TS29222_CAPIF_Auditing_API/. expose: - "8080" volumes: @@ -52,8 +49,7 @@ services: restart: unless-stopped image: dockerhub.hi.inet/evolved-5g/capif/auditing_api:latest service-apis: - build: - context: ./TS29222_CAPIF_Discover_Service_API + build: TS29222_CAPIF_Discover_Service_API/. expose: - "8080" volumes: @@ -73,8 +69,7 @@ services: - redis - mongo api-invocation-logs: - build: - context: ./TS29222_CAPIF_Logging_API_Invocation_API + build: TS29222_CAPIF_Logging_API_Invocation_API/. expose: - "8080" volumes: diff --git a/services/nginx/nginx.conf b/services/nginx/nginx.conf index 2599f779..2c864c4c 100644 --- a/services/nginx/nginx.conf +++ b/services/nginx/nginx.conf @@ -41,6 +41,14 @@ http { default 'SUCCESS'; "~*.*:.*:ccf" '{"status":401, "title":"Unauthorized" ,"detail":"User not authorized", "cause":"Certificate not authorized"}'; } + map "$request_method:$uri:$ssl_client_s_dn_cn" $logs_error_message { + default 'SUCCESS'; + "~*.*:.*:(?!aef)(.*)" '{"status":401, "title":"Unauthorized" ,"detail":"Role not authorized for this API route", "cause":"User role must be aef"}'; + } + map "$request_method:$uri:$ssl_client_s_dn_cn" $audit_error_message { + default 'SUCCESS'; + "~*.*:.*:(?!amf)(.*)" '{"status":401, "title":"Unauthorized" ,"detail":"Role not authorized for this API route", "cause":"User role must be amf"}'; + } server { listen 8080; @@ -130,6 +138,10 @@ http { if ($ssl_client_verify != SUCCESS) { return 403; } + if ( $logs_error_message != SUCCESS ) { + add_header Content-Type 'application/problem+json'; + return 401 $logs_error_message; + } proxy_set_header X-SSL-Client-Cert $ssl_client_cert; proxy_pass http://api-invocation-logs:8080; } @@ -138,6 +150,10 @@ http { if ($ssl_client_verify != SUCCESS) { return 403; } + if ( $audit_error_message != SUCCESS ) { + add_header Content-Type 'application/problem+json'; + return 401 $audit_error_message; + } proxy_set_header X-SSL-Client-Cert $ssl_client_cert; proxy_pass http://logs:8080; } diff --git a/tests/features/CAPIF Api Auditing Service/__init__.robot b/tests/features/CAPIF Api Auditing Service/__init__.robot new file mode 100644 index 00000000..d4becb75 --- /dev/null +++ b/tests/features/CAPIF Api Auditing Service/__init__.robot @@ -0,0 +1,2 @@ +*** Settings *** +Force Tags capif_api_auditing_service \ No newline at end of file diff --git a/tests/features/CAPIF Api Auditing Service/capif_auditing_api.robot b/tests/features/CAPIF Api Auditing Service/capif_auditing_api.robot new file mode 100644 index 00000000..2881f14e --- /dev/null +++ b/tests/features/CAPIF Api Auditing Service/capif_auditing_api.robot @@ -0,0 +1,211 @@ +*** Settings *** +Resource /opt/robot-tests/tests/resources/common.resource +Library /opt/robot-tests/tests/libraries/bodyRequests.py +Library Collections +Resource /opt/robot-tests/tests/resources/common/basicRequests.robot +Resource ../../resources/common.resource + +Test Setup Reset Testing Environment + +*** Variables *** +${AEF_ID_NOT_VALID} aef-example +${SERVICE_API_ID_NOT_VALID} not-valid +${API_INVOKER_NOT_VALID} not-valid +${NOTIFICATION_DESTINATION} http://robot.testing:1080 +${API_VERSION_VALID} v1 +${API_VERSION_NOT_VALID} v58 + +*** Test Cases *** +Get Log Entry + [Tags] capif_api_auditing_service-1 + #Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + #Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Create Log Entry + ${request_body}= Create Log Entry ${register_user_info['aef_id']} ${register_user_info_invoker['api_invoker_id']} ${api_ids} ${api_names} + ${resp_1}= Post Request Capif + ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs + ... json=${request_body} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + + ${resp_2}= Get Request Capif + ... /logs/v1/apiInvocationLogs?aef-id=${register_user_info['aef_id']}&api-invoker-id=${register_user_info_invoker['api_invoker_id']} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AMF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp_2} 200 InvocationLog + Length Should Be ${resp_2.json()["logs"]} 2 + +Get a log entry without entry created + [Tags] capif_api_auditing_service-2 + #Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + #Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + + ${resp_1}= Get Request Capif + ... /logs/v1/apiInvocationLogs?aef-id=${register_user_info['aef_id']}&api-invoker-id=${register_user_info_invoker['api_invoker_id']} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AMF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp_1} 404 ProblemDetails + ... title=Not Found + ... status=404 + ... detail=aefId or/and apiInvokerId do not match any InvocationLogs + ... cause=No log invocations found + + +Get a log entry withut aefid and apiInvokerId + [Tags] capif_api_auditing_service-3 + #Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + #Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Create Log Entry + ${request_body}= Create Log Entry ${register_user_info['aef_id']} ${register_user_info_invoker['api_invoker_id']} ${api_ids} ${api_names} + ${resp_1}= Post Request Capif + ... /api-invocation-logs/v1/${AEF_ID_NOT_VALID}/logs + ... json=${request_body} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + + ${resp_2}= Get Request Capif + ... /logs/v1/apiInvocationLogs + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AMF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp_2} 400 ProblemDetails + ... title=Bad Request + ... status=400 + ... detail=aef_id and api_invoker_id parameters are mandatory + ... cause=Mandatory parameters missing + + +Get Log Entry with apiVersion filter + [Tags] capif_api_auditing_service-4 + #Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + #Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Create Log Entry + ${request_body}= Create Log Entry ${register_user_info['aef_id']} ${register_user_info_invoker['api_invoker_id']} ${api_ids} ${api_names} + ${resp_1}= Post Request Capif + ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs + ... json=${request_body} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + + ${resp_2}= Get Request Capif + ... /logs/v1/apiInvocationLogs?aef-id=${register_user_info['aef_id']}&api-invoker-id=${register_user_info_invoker['api_invoker_id']}&api-version=${API_VERSION_VALID} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AMF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp_2} 200 InvocationLog + Length Should Be ${resp_2.json()["logs"]} 1 + +Get Log Entry with no exist apiVersion filter + [Tags] capif_api_auditing_service-5 + #Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + #Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Create Log Entry + ${request_body}= Create Log Entry ${register_user_info['aef_id']} ${register_user_info_invoker['api_invoker_id']} ${api_ids} ${api_names} + ${resp_1}= Post Request Capif + ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs + ... json=${request_body} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + + ${resp_2}= Get Request Capif + ... /logs/v1/apiInvocationLogs?aef-id=${register_user_info['aef_id']}&api-invoker-id=${register_user_info_invoker['api_invoker_id']}&api-version=${API_VERSION_NOT_VALID} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AMF_PROVIDER_USERNAME} + + # Check Results + # Check Results + Check Response Variable Type And Values ${resp_2} 404 ProblemDetails + ... title=Not Found + ... status=404 + ... detail=Parameters do not match any log entry + ... cause=No logs found + + + + diff --git a/tests/features/CAPIF Api Logging Service/__init__.robot b/tests/features/CAPIF Api Logging Service/__init__.robot new file mode 100644 index 00000000..fd3c43e4 --- /dev/null +++ b/tests/features/CAPIF Api Logging Service/__init__.robot @@ -0,0 +1,2 @@ +*** Settings *** +Force Tags capif_api_logging_service \ No newline at end of file diff --git a/tests/features/CAPIF Api Logging Service/capif_logging_api.robot b/tests/features/CAPIF Api Logging Service/capif_logging_api.robot new file mode 100644 index 00000000..5e8f0e3c --- /dev/null +++ b/tests/features/CAPIF Api Logging Service/capif_logging_api.robot @@ -0,0 +1,189 @@ +*** Settings *** +Resource /opt/robot-tests/tests/resources/common.resource +Library /opt/robot-tests/tests/libraries/bodyRequests.py +Library Collections +Resource /opt/robot-tests/tests/resources/common/basicRequests.robot +Resource ../../resources/common.resource + +Test Setup Reset Testing Environment + +*** Variables *** +${AEF_ID_NOT_VALID} aef-example +${SERVICE_API_ID_NOT_VALID} not-valid +${API_INVOKER_NOT_VALID} not-valid +${NOTIFICATION_DESTINATION} http://robot.testing:1080 + +*** Test Cases *** +Create a log entry + [Tags] capif_api_logging_service-1 + #Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + #Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Create Log Entry + ${request_body}= Create Log Entry ${register_user_info['aef_id']} ${register_user_info_invoker['api_invoker_id']} ${api_ids} ${api_names} + ${resp}= Post Request Capif + ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs + ... json=${request_body} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + + # Check Results + Check Response Variable Type And Values ${resp} 201 InvocationLog + ${resource_url}= Check Location Header ${resp} ${LOCATION_LOGGING_RESOURCE_REGEX} + +Create a log entry invalid aefId + [Tags] capif_api_logging_service-2 + #Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + #Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Create Log Entry + ${request_body}= Create Log Entry ${register_user_info['aef_id']} ${register_user_info_invoker['api_invoker_id']} ${api_ids} ${api_names} + ${resp}= Post Request Capif + ... /api-invocation-logs/v1/${AEF_ID_NOT_VALID}/logs + ... json=${request_body} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 404 ProblemDetails + ... title=Not Found + ... status=404 + ... detail=Exposer not exist + ... cause=Exposer id not found + + + +Create a log entry invalid serviceApi + [Tags] capif_api_logging_service-3 + #Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + #Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + # Create Log Entry + ${request_body}= Create Log Entry Bad Service ${register_user_info['aef_id']} ${register_user_info_invoker['api_invoker_id']} + ${resp}= Post Request Capif + ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs + ... json=${request_body} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 404 ProblemDetails + ... title=Not Found + ... status=404 + ... detail=Service API not exist + +Create a log entry invalid apiInvokerId + [Tags] capif_api_logging_service-4 + #Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + #Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Create Log Entry + ${request_body}= Create Log Entry ${register_user_info['aef_id']} ${API_INVOKER_NOT_VALID} ${api_ids} ${api_names} + ${resp}= Post Request Capif + ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs + ... json=${request_body} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 404 ProblemDetails + ... title=Not Found + ... status=404 + ... detail=Invoker not exist + ... cause=Invoker id not found + + +Create a log entry different aef_id in body + [Tags] capif_api_logging_service-5 + #Register APF + ${register_user_info}= Provider Default Registration + + # Publish one api + Publish Service Api ${register_user_info} + + #Register INVOKER + ${register_user_info_invoker} ${url} ${request_body}= Invoker Default Onboarding + + ${discover_response}= Get Request Capif + ... ${DISCOVER_URL}${register_user_info_invoker['api_invoker_id']} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${INVOKER_USERNAME} + + ${api_ids} ${api_names}= Get Api Ids And Names From Discover Response ${discover_response} + + # Create Log Entry + ${request_body}= Create Log Entry ${AEF_ID_NOT_VALID} ${register_user_info_invoker['api_invoker_id']} ${api_ids} ${api_names} + ${resp}= Post Request Capif + ... /api-invocation-logs/v1/${register_user_info['aef_id']}/logs + ... json=${request_body} + ... server=https://${CAPIF_HOSTNAME}/ + ... verify=ca.crt + ... username=${AEF_PROVIDER_USERNAME} + + # Check Results + Check Response Variable Type And Values ${resp} 401 ProblemDetails + ... title=Unauthorized + ... status=401 + ... detail=AEF id not matching in request and body + ... cause=Not identical AEF id + diff --git a/tests/libraries/api_logging_service/bodyRequests.py b/tests/libraries/api_logging_service/bodyRequests.py new file mode 100644 index 00000000..fe3faf1d --- /dev/null +++ b/tests/libraries/api_logging_service/bodyRequests.py @@ -0,0 +1,133 @@ +def create_log_entry(aefId=None, apiInvokerId=None, apiId=None, apiName=None): + data = { + "aefId": aefId, + "apiInvokerId": apiInvokerId, + "logs": [ + { + "apiId": apiId[0], + "apiName": apiName[0], + "apiVersion": "v1", + "resourceName": "string", + "uri": "http://resource/endpoint", + "protocol": "HTTP_1_1", + "operation": "GET", + "result": "string", + "invocationTime": "2023-03-30T10:30:21.408Z", + "invocationLatency": 0, + "inputParameters": "string", + "outputParameters": "string", + "srcInterface": { + "ipv4Addr": "192.168.1.1", + "fqdn": "string", + "port": 65535, + "apiPrefix": "string", + "securityMethods": [ + "PSK", + "string" + ] + }, + "destInterface": { + "ipv4Addr": "192.168.1.23", + "fqdn": "string", + "port": 65535, + "apiPrefix": "string", + "securityMethods": [ + "PSK", + "string" + ] + }, + "fwdInterface": "string" + }, + { + "apiId": apiId[0], + "apiName": apiName[0], + "apiVersion": "v2", + "resourceName": "string", + "uri": "http://resource/endpoint", + "protocol": "HTTP_1_1", + "operation": "GET", + "result": "string", + "invocationTime": "2023-03-30T10:30:21.408Z", + "invocationLatency": 0, + "inputParameters": "string", + "outputParameters": "string", + "srcInterface": { + "ipv4Addr": "192.168.1.1", + "fqdn": "string", + "port": 65535, + "apiPrefix": "string", + "securityMethods": [ + "PSK", + "string" + ] + }, + "destInterface": { + "ipv4Addr": "192.168.1.23", + "fqdn": "string", + "port": 65535, + "apiPrefix": "string", + "securityMethods": [ + "PSK", + "string" + ] + }, + "fwdInterface": "string" + } + ], + "supportedFeatures": "ffff" + } + return data + +def create_log_entry_bad_service(aefId=None, apiInvokerId=None): + data = { + "aefId": aefId, + "apiInvokerId": apiInvokerId, + "logs": [ + { + "apiId": "not-exist", + "apiName": "not-exist", + "apiVersion": "string", + "resourceName": "string", + "uri": "string", + "protocol": "HTTP_1_1", + "operation": "GET", + "result": "string", + "invocationTime": "2023-03-30T10:30:21.408Z", + "invocationLatency": 0, + "inputParameters": "string", + "outputParameters": "string", + "srcInterface": { + "ipv4Addr": "192.168.1.1", + "fqdn": "string", + "port": 65535, + "apiPrefix": "string", + "securityMethods": [ + "PSK", + "string" + ] + }, + "destInterface": { + "ipv4Addr": "192.168.1.23", + "fqdn": "string", + "port": 65535, + "apiPrefix": "string", + "securityMethods": [ + "PSK", + "string" + ] + }, + "fwdInterface": "string" + } + ], + "supportedFeatures": "ffff" + } + return data + +def get_api_ids_and_names_from_discover_response(discover_response): + api_ids=[] + api_names=[] + service_api_descriptions = discover_response.json()['serviceAPIDescriptions'] + for service_api_description in service_api_descriptions: + api_ids.append(service_api_description['apiId']) + api_names.append(service_api_description['apiName']) + return api_ids, api_names diff --git a/tests/libraries/bodyRequests.py b/tests/libraries/bodyRequests.py index 2641b9f7..5e451242 100644 --- a/tests/libraries/bodyRequests.py +++ b/tests/libraries/bodyRequests.py @@ -1,5 +1,6 @@ from common.bodyRequests import * from api_invoker_management.bodyRequests import * +from api_logging_service.bodyRequests import * from api_publish_service.bodyRequests import * from api_events.bodyRequests import * from security_api.bodyRequests import * diff --git a/tests/libraries/common/types.json b/tests/libraries/common/types.json index 2819562d..28f2b07d 100644 --- a/tests/libraries/common/types.json +++ b/tests/libraries/common/types.json @@ -364,10 +364,10 @@ "sourceNfInstanceId": "UUID" } }, - "NfSetId":{ + "NfSetId": { "regex": "set[a-zA-Z0-9\\-]+\\.(nrf|udm|amf|smf|ausf|nef|pcf|smsf|nssf|udr|lmf|gmlc|5g_eir|sepp|upf|n3iwf|af|udsf|bsf|chf|nwdaf|pcscf|cbcf|hss|ucmf|sor_af|spaf|mme|scsas|scef|scp|nssaaf|icscf|scscf|dra|ims_as|aanf|5g_ddnmf|nsacf|mfaf|easdf|dccf|mb_smf|tsctsf|adrf|gba_bsf|cef|mb_upf|nswof|pkmf|mnpf|sms_gmsc|sms_iwmsc|mbsf|mbstf|panf)set\\.5gc(\\.nid[A-Fa-f0-9]{11})?\\.mnc[0-9]{3}\\.mcc[0-9]{3}" }, - "NfServiceSetId":{ + "NfServiceSetId": { "regex": "set[a-zA-Z0-9\\-]+\\.(nrf|udm|amf|smf|ausf|nef|pcf|smsf|nssf|udr|lmf|gmlc|5g_eir|sepp|upf|n3iwf|af|udsf|bsf|chf|nwdaf|pcscf|cbcf|hss|ucmf|sor_af|spaf|mme|scsas|scef|scp|nssaaf|icscf|scscf|dra|ims_as|aanf|5g_ddnmf|nsacf|mfaf|easdf|dccf|mb_smf|tsctsf|adrf|gba_bsf|cef|mb_upf|nswof|pkmf|mnpf|sms_gmsc|sms_iwmsc|mbsf|mbstf|panf)set\\.5gc(\\.nid[A-Fa-f0-9]{11})?\\.mnc[0-9]{3}\\.mcc[0-9]{3}" }, "ScopeRegex": { @@ -446,7 +446,7 @@ }, "optional_attributes": {} }, - "PlmnIdNid":{ + "PlmnIdNid": { "mandatory_attributes": { "mcc": "Mcc", "mnc": "Mnc" @@ -472,7 +472,7 @@ "SlideDifferentiator": { "regex": "^[A-Fa-f0-9]{6}$" }, - "Nid":{ + "Nid": { "regex": "^[A-Fa-f0-9]{11}$" }, "AccessTokenRsp": { @@ -541,5 +541,42 @@ }, "ApiProviderFuncRole": { "enum": ["AEF", "APF", "AMF"] + }, + "InvocationLog": { + "mandatory_attributes": { + "aefId": "string", + "apiInvokerId": "string", + "logs": "Log" + }, + "optional_attributes": { + "log_id":"string", + "supportedFeatures": "SupportedFeatures" + } + }, + "Log": { + "mandatory_attributes": { + "apiId": "string", + "apiName": "string", + "apiVersion": "string", + "resourceName": "string", + "protocol": "Protocol", + "result": "string" + }, + "optional_attributes": { + "log_id": "string", + "uri": "URI", + "operation": "Operation", + "invocationTime": "string", + "invocationLatency": "integer", + "inputParameters": "Any", + "outputParameters": "Any", + "srcInterface": "InterfaceDescription", + "destInterface": "InterfaceDescription", + "fwdInterface": "string", + "supportedFeatures": "SupportedFeatures" + } + }, + "Any": { + "Check": false } } diff --git a/tests/requirements.txt b/tests/requirements.txt index 71b55e23..1a394530 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -2,6 +2,6 @@ robotframework-mongodb-library==3.2 requests==2.28.1 configparser==5.3.0 -redis==4.3.4 +redis==4.5.4 rfc3987==1.3.8 robotframework-httpctrl \ No newline at end of file diff --git a/tests/resources/common/basicRequests.robot b/tests/resources/common/basicRequests.robot index 9ba4dff5..39edeafd 100644 --- a/tests/resources/common/basicRequests.robot +++ b/tests/resources/common/basicRequests.robot @@ -23,6 +23,8 @@ ${LOCATION_SECURITY_RESOURCE_REGEX} ... ^/capif-security/v1/trustedInvokers/[0-9a-zA-Z]+ ${LOCATION_PROVIDER_RESOURCE_REGEX} ... ^/api-provider-management/v1/registrations/[0-9a-zA-Z]+ +${LOCATION_LOGGING_RESOURCE_REGEX} +... ^/api-invocation-logs/v1/[0-9a-zA-Z]+/logs/[0-9a-zA-Z]+ ${INVOKER_ROLE} invoker ${AMF_ROLE} amf