Commit e318fd03 authored by Jorge Echevarria Uribarri's avatar Jorge Echevarria Uribarri Committed by GitHub
Browse files

Merge pull request #12 from Telefonica/check_invoker_authentication

future updates, upgrade of api translator and adaptation to supported…
parents f1a8c1bc bbc34512
Loading
Loading
Loading
Loading
+7 −7
Original line number Diff line number Diff line
@@ -88,19 +88,19 @@ OpenCAPIF SDK brings a set of functions to integrate with the 5G Core's function
| /registrations/{registrationId} (PUT)                 | [update_provider()](./doc/sdk_full_documentation.md#update-and-offboard-provider)                                           | Updates a service provider's registration for a specific `registrationId`.                  |
| /registrations/{registrationId} (DELETE)              | [offboard_provider()](./doc/sdk_full_documentation.md#update-and-offboard-provider)                                         | Deletes a service provider's registration for a specific `registrationId`.                  |
| /allServiceAPIs (GET)                                 | [discover()](./doc/sdk_full_documentation.md#discover-process)                                                  | Retrieves a list of all available service APIs.             |
| /trustedInvokers (PUT//POST)                          | [get_tokens()](./doc/sdk_full_documentation.md#discover-process)                                                  | Registers or updates trusted invokers.                      |
| /securities/{securityId}/token (GET)                  | [get_tokens()](./doc/sdk_full_documentation.md#obtain-invoker-tokens)                                                | Retrieves a security token for a specific `securityId`. This JWT token is used to query the targeted services.      |
| /trustedInvokers (PUT//POST)                          | [get_tokens(supp_features)](./doc/sdk_full_documentation.md#discover-process)                                                  | Registers or updates trusted invokers.                      |
| /securities/{securityId}/token (GET)                  | [get_tokens(supp_features)](./doc/sdk_full_documentation.md#obtain-invoker-tokens)                                                | Retrieves a security token for a specific `securityId`. This JWT token is used to query the targeted services.      |
| /{apfId}/service-apis(POST)                           | [publish_services()](./doc/sdk_full_documentation.md#services-publishing)                                          | Registers a new service API into the system for a specific `apfId`                |
| /{apfId}/service-apis/{serviceApiId} (DELETE)         | [unpublish_service()](./doc/sdk_full_documentation.md#services-deletion)                                         | Deletes a service API from the system for a specific `apfId`and `serviceApiId`                      |
| /{apfId}/service-apis/{serviceApiId} (PUT)            | [update_service()](./doc/sdk_full_documentation.md#services-update)                                            | Updates the details of an existing service API for a specific `apfId`and `serviceApiId`             |
| /{apfId}/service-apis/{serviceApiId} (GET)                           | [get_service()](./doc/sdk_full_documentation.md#get-services)                                               | Retrieves the details of a specific service API for a specific `apfId` and `serviceApiId`           |
| /{apfId}/service-apis (GET)            | [get_all_services()](./doc/sdk_full_documentation.md#get-all-services)                                          | Retrieves a list of all available service APIs for a specific `apfId`            |
| /aef-security/v1/check-authentication (POST)            | [check_authentication(supported_features)](./doc/sdk_full_documentation.md#check_authentication)                                          | This custom operation allows the API invoker to confirm the `supported_features` from the API exposing function(AEF)            |
| /api-invocation-logs/v1/{aefId}/logs (POST)             | [create_logs(aefId, jwt)](./doc/sdk_full_documentation.md#create_logs) | This operation allows to the Provider to notice to the CCF about the query of an invoker with the JWT token recieved
| /capif-events/v1/{subscriberId}/subscriptions (POST)             | [create_subscription(name, id)](./doc/sdk_full_documentation.md#create_subscription) | This operation allows to the Invoker/AEF/APF/AMF to ask to the CCF about notifications related to certain functionalities.
| /api-invocation-logs/v1/{aefId}/logs (POST)             | [create_logs(aefId, jwt,supp_features)](./doc/sdk_full_documentation.md#create_logs) | This operation allows to the Provider to notice to the CCF about the query of an invoker with the JWT token recieved
| /capif-events/v1/{subscriberId}/subscriptions (POST)             | [create_subscription(name, id, supp_features)](./doc/sdk_full_documentation.md#create_subscription) | This operation allows to the Invoker/AEF/APF/AMF to ask to the CCF about notifications related to certain functionalities.
| /capif-events/v1/{subscriberId}/subscriptions/{subscriptionId} (DELETE)             | [delete_subscription(name, id)](./doc/sdk_full_documentation.md#delete_subscription) | This operation allows to the Invoker/AEF/APF/AMF to withdraw the petition to receive notifications related to certain functionalities.
| /capif-events/v1/{subscriberId}/subscriptions/{subscriptionId} (PUT)             | [update_subscription(name, id)](./doc/sdk_full_documentation.md#update_subscription) | This operation allows to the Invoker/AEF/APF/AMF to modify to the petition to receive notifications related to certain functionalities. **ONLY AVAILABLE IN OPENCAPIF RELEASE 2**
| /capif-events/v1/{subscriberId}/subscriptions/{subscriptionId} (PATCH)             | [patch_subscription(name, id)](./doc/sdk_full_documentation.md#patch_subscription) | This operation allows to the Invoker/AEF/APF/AMF to modify to the petition to receive notifications related to certain functionalities. **ONLY AVAILABLE IN OPENCAPIF RELEASE 2**
| /capif-events/v1/{subscriberId}/subscriptions/{subscriptionId} (PUT)             | [update_subscription(name, id, supp_features)](./doc/sdk_full_documentation.md#update_subscription) | This operation allows to the Invoker/AEF/APF/AMF to modify to the petition to receive notifications related to certain functionalities. **ONLY AVAILABLE IN OPENCAPIF RELEASE 2**
| /capif-events/v1/{subscriberId}/subscriptions/{subscriptionId} (PATCH)             | [patch_subscription(name, id, supp_features)](./doc/sdk_full_documentation.md#patch_subscription) | This operation allows to the Invoker/AEF/APF/AMF to modify to the petition to receive notifications related to certain functionalities. **ONLY AVAILABLE IN OPENCAPIF RELEASE 2**

NOTE: Above mentioned CAPIF APIs are defined in these 3GPP references:
- [CAPIF Invoker API specification](https://github.com/jdegre/5GC_APIs/blob/Rel-18/TS29222_CAPIF_API_Invoker_Management_API.yaml)
@@ -161,7 +161,7 @@ Now, it is described in 4 simple steps how a Provider can be developed in just s
  provider.onboard_provider()

  #translator = opencapif_sdk.api_schema_translator("./path/to/openapi.yaml")
  #translator.build("api_description_name",ip="0.0.0.0",port=9090,supported_features="0",api_supp_features="0")
  #translator.build("https://192.168.1.10:8080/exampleAPI/v1", "0", "0")
  provider.api_description_path = "./api_description_name.json"

  APF = provider.provider_capif_ids["APF-1"]
+2 −1
Original line number Diff line number Diff line
@@ -198,7 +198,8 @@ This schema could be obtained by applying this code.
    import opencapif_sdk
    
    translator = api_schema_translator("./path/to/openapi.yaml")
    translator.build("api_description_name",ip="0.0.0.0",port=9090)
    translator.build("https://192.168.1.10:8080/exampleAPI/v1", "0", "0")

```
This code will read `openapi.yaml`, ensure the structure of it and translate the content into ServiceAPIDescription schema, then will create a .json named `api_description_name`. Also it is necessary to fill the ip and port fields to create correctly the schema.
# OpenCAPIF SDK known issues
+14 −5
Original line number Diff line number Diff line
@@ -166,10 +166,12 @@ The provider must be onboarded before using these features.
### Create logs

OpenCAPIF SDK references:
- **Function**: `create_logs(aefId, jwt)`
- **Function**: `create_logs(aefId, jwt, supp_features)`

The provider notifies to the CCF that the published API has been used by certain invoker.

`supp_features` parameter is optional and it stands for communicating to the CCF the supported features.It's default value its 0.

For leveraging this feature the Provider must have onboarded and published an API previously.

**Required SDK input**:
@@ -182,10 +184,12 @@ For leveraging this feature the Provider must have onboarded and published an AP
### Create subscription

OpenCAPIF SDK references:
- **Function**: `create_subscription(name, id)`
- **Function**: `create_subscription(name, id, supp_features)`

The provider ask to the CCF about notifications related to services such as SERVICE_API_AVAILABLE or API_INVOKER_UPDATED.

`supp_features` parameter is optional and it stands for communicating to the CCF the supported features.It's default value its 0.

This services are specificated in [CAPIF Events API management](https://github.com/jdegre/5GC_APIs/blob/Rel-18/TS29222_CAPIF_Events_API.yaml) explained in [SDK configuration](./sdk_configuration.md#descriptions-of-capif_sdk_config-fields)

For leveraging this feature the Provider must have onboarded previously.
@@ -213,12 +217,14 @@ For leveraging this feature the Provider must have onboarded and created a subsc
### Update subscription

OpenCAPIF SDK references:
- **Function**: `update_subscription(name, id)`
- **Function**: `update_subscription(name, id, supp_features)`

The provider ask to the CCF about updating the subscription for receiving different services such as SERVICE_API_AVAILABLE or API_INVOKER_UPDATED, changing the URL for receiving the notifications...

This services are specificated in [CAPIF Events API management](https://github.com/jdegre/5GC_APIs/blob/Rel-18/TS29222_CAPIF_Events_API.yaml) explained in [SDK configuration](./sdk_configuration.md#descriptions-of-capif_sdk_config-fields)

`supp_features` parameter is optional and it stands for communicating to the CCF the supported features.It's default value its 0.

For leveraging this feature the Provider must have onboarded and created a subscription previously.

![Events_feature](./images/flows-event_subscription.jpg)
@@ -238,6 +244,8 @@ OpenCAPIF SDK references:

The provider ask to the CCF about updating the subscription for receiving different services such as SERVICE_API_AVAILABLE or API_INVOKER_UPDATED.

`supp_features` parameter is optional and it stands for communicating to the CCF the supported features.It's default value its 0.

This services are specificated in [CAPIF Events API management](https://github.com/jdegre/5GC_APIs/blob/Rel-18/TS29222_CAPIF_Events_API.yaml) explained in [SDK configuration](./sdk_configuration.md#events_configuration)

For leveraging this feature the Provider must have onboarded and created a subscription previously.
@@ -295,10 +303,11 @@ Use the [discover_filter](./sdk_configuration.md) to retrieve access to target A
### Obtain JWT Tokens

OpenCAPIF SDK references:
- **Function**: `get_tokens()`
- **Function**: `get_tokens(supp_features)`
- **Script**: `invoker_service_get_token.py`

The SDK facilitates JWT token creation for secure access to target APIs. This process stores JWT access token in `capif_api_security_context_details.json`.
`supp_features` parameter is optional and it stands for retrieve the token of the services that have certain supported features.It's default value its 0.

![Invoker_get_token](./images/flows-invoker_get_tokens.jpg)

@@ -337,7 +346,7 @@ This schema could be obtained by applying this code.
    import opencapif_sdk
    
    translator = opencapif_sdk.api_schema_translator("./path/to/openapi.yaml")
    translator.build("api_description_name",ip="0.0.0.0",port=9090,supported_features="0",api_supp_features="0")
    translator.build("https://192.168.1.10:8080/exampleAPI/v1", "0", "0")
```
This code will read `openapi.yaml`, ensure the structure of it and translate the content into ServiceAPIDescription schema, then will create a .json named `api_description_name`. Also it is necessary to fill the ip and port fields to create correctly the schema.
The supported_features and api_supp_features fields corresponds to the capabilities of the provider and the service that the user is sharing.
+64 −15
Original line number Diff line number Diff line
@@ -2,9 +2,9 @@ import json
import logging
import os
import re
import socket
import yaml


log_path = 'logs/builder_logs.log'

log_dir = os.path.dirname(log_path)
@@ -34,31 +34,81 @@ class api_schema_translator:
        self.api_info = self.__load_api_file(self.api_path)
        self.__validate_api_info()

    def build(self, api_name, supported_features, api_supp_features, ip=None, port=None, fqdn=None, ipv6Addr=None):
    def __validate_ip_port(self, ip, port):
        """Validates if an IP address and port are correctly formatted."""
        try:
            socket.inet_aton(ip)  # Validate IPv4
            return isinstance(port, int) and 0 < port < 65536
        except socket.error:
            return False

    def __parse_url(self, url):
        """
        Parses the URL to extract apiRoot, apiName, and apiVersion.
        Handles:
        - URLs with or without ports.
        - API root as an IP, FQDN, or preconfigured prefix.
        - Assigns default ports for HTTP (80) and HTTPS (443) if missing.
        """

        pattern = r"^(https?:\/\/)?([\w\.-]+|\[.*\])(?::(\d+))?(\/[\w\/-]+)?\/([\w-]+)\/(v\d+)$"
        match = re.match(pattern, url)

        if not match:
            self.logger.error(f"Invalid URL format: {url}")
            return None, None, None, None, None, None, None

        scheme, host, port, api_root, api_name, api_version = match.groups()

        # Asignar puerto por defecto si no está presente
        if not port:
            port = 443 if scheme == "https" else 80  # Si no hay esquema, asumimos HTTPS
        else:
            port = int(port)

        if not api_name or not api_version:
            self.logger.error(f"URL must contain API name and version in the format: <apiRoot>/<apiName>/v<version>")
            return None, None, None, None, None, None, None

        # Si apiRoot está presente, eliminar "/" innecesarios
        api_root = api_root.strip('/') if api_root else None

        if re.match(r"^\d{1,3}(\.\d{1,3}){3}$", host):  # IPv4
            return host, port, None, None, api_root, api_name, api_version
        elif re.match(r"^\[.*\]$", host):  # IPv6 (brackets format)
            return None, port, None, host.strip("[]"), api_root, api_name, api_version
        else:  # FQDN
            return None, port, host, None, api_root, api_name, api_version

    def build(self, url, supported_features, api_supp_features):
        """
        Builds the API description and saves it to a JSON file.
        Supports either IPv4 (ip), IPv6 (ipv6Addr), or FQDN (fqdn).
        Extracts apiRoot, apiName, and apiVersion.
        Supports IP, FQDN, and URLs with or without explicit ports.
        """
        # Validate required fields
        # Validación de campos requeridos
        if not supported_features or not api_supp_features:
            self.logger.error("Both 'supported_features' and 'api_supp_features' are required. Aborting build.")
            return

        # Validate that at least one of ip, ipv6Addr, or fqdn is provided
        if not (ip or ipv6Addr or fqdn):
            self.logger.error("At least one of 'ip', 'ipv6Addr', or 'fqdn' must be provided. Aborting build.")
        # Parseamos la URL
        ip, port, fqdn, ipv6Addr, api_root, api_name, api_version = self.__parse_url(url)

        # Validamos que al menos una dirección sea válida
        if not (ip or fqdn or ipv6Addr or api_root):
            self.logger.error("Invalid URL: No valid IP, IPv6, FQDN, or API root found. Aborting build.")
            return

        # Validate IP and port if IPv4 is provided
        # Validamos IP y puerto si es IPv4
        if ip and not self.__validate_ip_port(ip, port):
            self.logger.error("Invalid IP or port. Aborting build.")
            return

        # Build the API data
        # Construcción de la API
        try:
            api_data = {
                "apiName": self.api_info["info"].get("title", api_name),
                "aefProfiles": self.__build_aef_profiles(ip, port, fqdn, ipv6Addr),
                "apiName": api_name,
                "aefProfiles": self.__build_aef_profiles(ip, port, api_version, fqdn, ipv6Addr),
                "description": self.api_info["info"].get("description", "No description provided"),
                "supportedFeatures": supported_features,
                "shareableInfo": {
@@ -73,7 +123,7 @@ class api_schema_translator:
                "ccfId": "string"
            }

            # Save the API data to a JSON file
            # Guardamos los datos en un archivo JSON
            with open(f"{api_name}.json", "w") as outfile:
                json.dump(api_data, outfile, indent=4)
            self.logger.info(f"API description saved to {api_name}.json")
@@ -81,7 +131,6 @@ class api_schema_translator:
        except Exception as e:
            self.logger.error(f"An error occurred during the build process: {e}")


    def __load_api_file(self, api_file: str):
        """Loads the Swagger API configuration file and converts YAML to JSON format if necessary."""
        try:
@@ -112,7 +161,7 @@ class api_schema_translator:
        else:
            self.logger.info("All required components are present in the API specification.")

    def __build_aef_profiles(self, ip, port, fqdn=None, ipv6Addr=None):
    def __build_aef_profiles(self, ip, port, api_version, fqdn=None, ipv6Addr=None, ):
        """Builds the aefProfiles section based on the paths and components in the API info."""
        aef_profiles = []

@@ -149,7 +198,7 @@ class api_schema_translator:
            "aefId": "",  # Placeholder AEF ID
            "versions": [
                {
                    "apiVersion": "v1",
                    "apiVersion": f"{api_version}",
                    "expiry": "2100-11-30T10:32:02.004Z",
                    "resources": resources,
                    "custOperations": [
Loading