Commit 19074818 authored by guillecxb's avatar guillecxb
Browse files

new features of configuration

parent c4429b7d
Loading
Loading
Loading
Loading
+65 −1
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ import requests
from flask import request, make_response
import jsonschema
from jsonschema import validate, ValidationError
from ..utils.utils import is_snake_case, validate_snake_case_keys


# create blueprint backoffice_routes
@@ -58,6 +59,10 @@ def replace_configuration():
    if not isinstance(new_config["settings"], dict) or not new_config["settings"]:
        return jsonify({"error": "'settings' debe ser un objeto con al menos una categoría"}), 400

    error_response = validate_snake_case_keys(new_config["settings"])
    if error_response:
        return error_response  # Retorna el error si se encuentra una clave incorrecta

    return operations.replace_configuration(new_config)


@@ -91,6 +96,37 @@ def add_new_config_setting():
    return operations.add_new_config_setting(param_path, new_value)


@configuration_routes.route("/configuration/remove-config-param", methods=["DELETE", "OPTIONS"])
@cross_origin()
@jwt_required()
def remove_configuration_param():
    if request.method == "OPTIONS":
        return make_response("", 200)

    data = request.json
    param_path = data.get("param_path")

    if not param_path:
        return jsonify({"error": "param_path es requerido"}), 400

    return operations.remove_configuration_param(param_path)


@configuration_routes.route("/configuration/remove-config-category", methods=["DELETE", "OPTIONS"])
@cross_origin()
@jwt_required()
def remove_configuration_category():
    if request.method == "OPTIONS":
        return make_response("", 200)

    data = request.json
    category_name = data.get("category_name")

    if not category_name:
        return jsonify({"error": "category_name es requerido"}), 400

    return operations.remove_configuration_category(category_name)



### REGISTER CONFIGURATION ###
@@ -159,3 +195,31 @@ def add_new_config_register_setting():
        return jsonify({"error": "param_path y new_value son requeridos"}), 400
    
    return operations.add_new_config_register_setting(param_path, new_value)


@cross_origin()
@configuration_routes.route("/configuration/register/remove-config-param", methods=["DELETE"])
@jwt_required()
def remove_register_configuration_param():
    """Elimina un parámetro dentro de una categoría en 'settings' en el register."""
    data = request.json
    param_path = data.get("param_path")

    if not param_path:
        return jsonify({"error": "param_path es requerido"}), 400

    return operations.remove_register_configuration_param(param_path)


@cross_origin()
@configuration_routes.route("/configuration/register/remove-config-category", methods=["DELETE"])
@jwt_required()
def remove_register_configuration_category():
    """Elimina una categoría completa en 'settings' en el register."""
    data = request.json
    category_name = data.get("category_name")

    if not category_name:
        return jsonify({"error": "category_name es requerido"}), 400

    return operations.remove_register_configuration_category(category_name)
+145 −1
Original line number Diff line number Diff line
@@ -1006,6 +1006,46 @@ class Operations:
            return jsonify({"error": str(e)}), 500
        

    def remove_configuration_param(self, param_path):
        """ Elimina un parámetro dentro de una categoría en 'settings' """
        url = "https://{}/helper/removeConfigParam".format(os.getenv('CAPIF_HOSTNAME'))
        payload = {
            "param_path": param_path
        }

        try:
            response = requests.delete(
                url,
                json=payload,
                verify="newcerts/ca_root.crt",
                cert=("newcerts/superadmin.crt", "newcerts/superadmin.key")
            )
            response.raise_for_status()
            return jsonify(response.json()), response.status_code
        except requests.exceptions.RequestException as e:
            return jsonify({"error": str(e)}), 500


    def remove_configuration_category(self, category_name):
        """ Elimina una categoría completa dentro de 'settings' """
        url = "https://{}/helper/removeConfigCategory".format(os.getenv('CAPIF_HOSTNAME'))
        payload = {
            "category_name": category_name
        }

        try:
            response = requests.delete(
                url,
                json=payload,
                verify="newcerts/ca_root.crt",
                cert=("newcerts/superadmin.crt", "newcerts/superadmin.key")
            )
            response.raise_for_status()
            return jsonify(response.json()), response.status_code
        except requests.exceptions.RequestException as e:
            return jsonify({"error": str(e)}), 500

          

    ### REGISTER CONFIGURATION ###

@@ -1292,3 +1332,107 @@ class Operations:
        except requests.exceptions.RequestException as e:
            return jsonify({"error": f"Error when adding config setting: {str(e)}"}), 500
        


    def remove_register_configuration_param(self, param_path):
        """Elimina un parámetro dentro de una categoría en 'settings' en el register."""
        
        def login():
            """Obtiene el access token desde el servicio register."""
            try:
                url = "https://{}/login".format(os.getenv('REGISTER_HOSTNAME'))
                response = requests.post(
                    url,
                    auth=HTTPBasicAuth(self.username, self.password),
                    verify=False
                )
                if response.status_code == 200:
                    return response.json().get("access_token")
            except requests.exceptions.RequestException as e:
                return None
            return None

        def delete_param(token):
            """Realiza la solicitud DELETE para eliminar un parámetro específico."""
            headers = {
                "Authorization": f"Bearer {token}",
                "Content-Type": "application/json"
            }
            payload = {
                "param_path": param_path
            }
            try:
                url = "https://{}/configuration/removeConfigParam".format(os.getenv('REGISTER_HOSTNAME'))
                response = requests.delete(url, json=payload, headers=headers, verify=False)
                return response
            except requests.exceptions.RequestException as e:
                return None

        try:
            # Obtener el token de autenticación
            access_token = login()
            if not access_token:
                return jsonify({"error": "No se pudo obtener el token de autenticación"}), 401

            # Enviar la solicitud DELETE al servicio register
            response = delete_param(access_token)
            if response is None:
                return jsonify({"error": "No se pudo conectar con el servicio register"}), 500

            return jsonify(response.json()), response.status_code

        except requests.exceptions.RequestException as e:
            return jsonify({"error": f"Error al eliminar el parámetro de configuración: {str(e)}"}), 500


    def remove_register_configuration_category(self, category_name):
        """Elimina una categoría completa dentro de 'settings' en el register."""

        def login():
            """Obtiene el access token desde el servicio register."""
            try:
                url = "https://{}/login".format(os.getenv('REGISTER_HOSTNAME'))
                response = requests.post(
                    url,
                    auth=HTTPBasicAuth(self.username, self.password),
                    verify=False
                )
                if response.status_code == 200:
                    return response.json().get("access_token")
            except requests.exceptions.RequestException as e:
                return None
            return None

        def delete_category(token):
            """Realiza la solicitud DELETE para eliminar una categoría completa."""
            headers = {
                "Authorization": f"Bearer {token}",
                "Content-Type": "application/json"
            }
            payload = {
                "category_name": category_name
            }
            try:
                url = "https://{}/configuration/removeConfigCategory".format(os.getenv('REGISTER_HOSTNAME'))
                response = requests.delete(url, json=payload, headers=headers, verify=False)
                return response
            except requests.exceptions.RequestException as e:
                return None

        try:
            # Obtener el token de autenticación
            access_token = login()
            if not access_token:
                return jsonify({"error": "No se pudo obtener el token de autenticación"}), 401

            # Enviar la solicitud DELETE al servicio register
            response = delete_category(access_token)
            if response is None:
                return jsonify({"error": "No se pudo conectar con el servicio register"}), 500

            return jsonify(response.json()), response.status_code

        except requests.exceptions.RequestException as e:
            return jsonify({"error": f"Error al eliminar la categoría de configuración: {str(e)}"}), 500

            
 No newline at end of file
+17 −0
Original line number Diff line number Diff line
import re
from flask import jsonify

def is_snake_case(value):
    """Verifica si una clave está en snake_case (solo letras minúsculas, números y guiones bajos)."""
    return bool(re.match(r'^[a-z0-9_]+$', value))

# Verificar que TODAS las claves dentro de settings estén en snake_case
def validate_snake_case_keys(obj, path="settings"):
    """Recorre el JSON validando que todas las claves estén en snake_case."""
    for key, value in obj.items():
        if not is_snake_case(key):
            return jsonify({"error": f"La clave '{path}.{key}' no está en snake_case"}), 400
        if isinstance(value, dict):
            error_response = validate_snake_case_keys(value, f"{path}.{key}")
            if error_response:
                return error_response 
 No newline at end of file
+33 −33
Original line number Diff line number Diff line
-----BEGIN CERTIFICATE-----
MIIDIzCCAgugAwIBAgIUbj+1FhGf5fzAF/yIX/6vv5S2v8QwDQYJKoZIhvcNAQEL
BQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzEzMTEwNjMwWhcNMzUwMzExMTEw
NzAwWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAOLPB3bJQ421jSqNAVWYMw3z4cpRJzYyzq3QgOv6g+oI9tEo46f2Ir9o
tXOnQQz2pdK32W2l8KWKnTMn27eBppkoCYAaLF5wn2Tyj82VaEFqOAup4TofYu7G
0H033L89Q4RAouvmGur6kJODYIHHRuklT1pf2wPM3rTwGy4xOvCzNBjPNSbCuqqJ
WZpyFkgoBj2ylBAnWU/WI6A7coAmZEGxUuUy76rFu7sHYNHVW2uHS3JM+QkxU0IE
zKO3M7H7NyROwupapGXDRkCB6l2DXM/p/A8cu/hX70w+/hbJhMKy7C4kNODQwTJg
2H3WGRnTuJMCQxvDkqXmJM/kOMTSxdsCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG
MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFF3MglPEJXxJGKvYazVbi4Ythldg
MB8GA1UdIwQYMBaAFF3MglPEJXxJGKvYazVbi4YthldgMBAGA1UdEQQJMAeCBWNh
cGlmMA0GCSqGSIb3DQEBCwUAA4IBAQBlvC1lQH3jGO8yTB0/pANXP3NutlUC9ABE
j7KZHjAVg7nyknTQzpSj+j9qJ6sCzdKAefckR1dr0a8bQaubqwrEbcscB0SBuvph
vAfT3ZyA3rvJRFdJgziDUFrZeOZQm7gr0HhPzATn9c7Rygi7JxkAuZMeI37uGavV
GrPUaSOzR8jOPL1KtTU/Ub5RIN0TCgJJHNOdC9NU7hHRJTii3YG5+j3fstF5cZqs
Qh6fx0dPvDSlxgxpQI09uZMI6kLJaE3gQ8EvGfV2lpuD12suMb6OTN3sXzEN5pbI
JJ96jq5KpEY9N1EMHXCulAxPfh3QDj12tQRwFcMDujd/V4u9SGtd
MIIDIzCCAgugAwIBAgIUT9S3y1ElP0qhHopA4Vv42GYkW2EwDQYJKoZIhvcNAQEL
BQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzE0MDgxMDIzWhcNMzUwMzEyMDgx
MDUyWjAQMQ4wDAYDVQQDEwVjYXBpZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAJrlaYEx0tYdKLzdSGxJ/zJPWcS3/ltf6fUztddMaN1MtlQ4x4ZcFgzI
IXUhcQP23qWtJ+Y9hSfpkE8LVl43Ry4/DMxEilP27uWkt3B+7mLmg1ySOkdk37y6
pK3DUIlbMIc8YEU2fsE5AcN5OIXLgXP1SBoshzZzSfSvN/bTp8e130K6b9RwbIe9
RuPI0JARRo6Y83JrdQAQV7Jj8CqpyjvHNhon/kUlHCsjaXN8AAiNeaCskM5Wm/H3
FyNT2YybBqFXhvjif7lhm6qOZBGE1aZjGj6QgMZ4+kPWe5UoqojJKPx7jfUH2UvM
HuVHKg4FcwOAJQQVozHB1rrqtR0TpaMCAwEAAaN1MHMwDgYDVR0PAQH/BAQDAgEG
MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEsOH8HB75slPeqa+0TbVKnfZtdo
MB8GA1UdIwQYMBaAFEsOH8HB75slPeqa+0TbVKnfZtdoMBAGA1UdEQQJMAeCBWNh
cGlmMA0GCSqGSIb3DQEBCwUAA4IBAQB3R1zU/vIZRsOxBQSN8071BIH3h0z0ovsQ
iwUU1sEe2GsB4P68WkxQNFLWj8kDD2nloLFDUekJKek0bLukzI3CUplKP1qMlIR7
WctZlVD7tokOw3k8X7cJnh6jQ41gAt3JySQSThb/3nR8hR2ZvtrMw+tPbFb5Fnhp
JW2ypcccz00w8zSDNUJvrXXIxT3yJB1dOMKed+zNWtD8reph6t7r4x0kp6PAuTmr
GrjhUmC92s5UQi7gZD/umOfwCTQpBLUs1vOh00pV/WJI5trdrXndWfD2iXuklcFJ
Av52MIzrnMbOd/6MAZ0R0sz8aU0lftIxgnvYq1xtt28LUzTNIxHu
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDKDCCAhCgAwIBAgIUddmL+RrRmJYqaGCW90a7aunKX8gwDQYJKoZIhvcNAQEL
BQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzEzMTEwNjMxWhcNMzAwMzEyMTEw
NzAxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwKjD5AMkH4G0w+zCQWnziu1j
CxyZ43BWZRTe75LllJ0YU3Y5SHNHZMgbY0wgLtuiMi5NLuHiOObvHcOsZR0/cYeR
9ZE22xWRfTBoHQK2lLPUX5BXaPuMCbZAI1e/A2iBJOII36o+F0Gf85qvmqv+N81j
t14BwS7Kw6tYqMLVadbKWr6mSrSO8urp9ZcjVj4AXWZXWs75Rid+NXPVsyJUvO0f
yUFchj9H/CH4Mh8jsw1onCQ5Eg1b2cZ9RAtJjkemxb9VlNNmZLJq1sMVyc2UHwu7
+Js4UQmkr2De+VG6GPeyCv2g7GzpnsIiIvNDo0LiBgAD8D3AdBTPToa6aBpdxQID
MIIDKDCCAhCgAwIBAgIUBSmgesyprqmIkFI8uSWj66N4AkkwDQYJKoZIhvcNAQEL
BQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzE0MDgxMDIzWhcNMzAwMzEzMDgx
MDUzWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtYod3Lpkqr0XmzIPMpBQtPaY
nxTftboKbG/XDNVWLSYA1Tqh73RNof7i9cOFnDH3IpjrGHFyfhd9TuX276qDVIB1
sRIvb7+pprnY9FpIOEFYwOkkFeKMrh2obRwrLIj97PvUg7S2cUslfz3QXVE37GdJ
zow6MGuDEdt9ICiyIsovEg93/+f/duGT1IhMUZXCjc6wTDjGVbUBBJ0sqt/idVHu
RzQgiEyggRBgHvh4r9jzOjZ/11OCE8rxBH8Ya2aWedtgBSsdGRr4tArIrNw92yOZ
7qT7ZBLti26Ky7DxcwvcVCFI1nZz6w+BB5o3xyrLjfz6f1yjukEk5Dt6VRIx4QID
AQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQUBXH5rGoV6N566W2tuc9KTaEhPYswHwYDVR0jBBgwFoAUXcyCU8QlfEkYq9hr
NVuLhi2GV2AwDQYJKoZIhvcNAQELBQADggEBANp/szFjdTGlAx13Jeut5Thyc5Co
j7SlAsNJxxvEvnZiy8pekd7Bw/Rtdt+Y7+lsjDE2Eo31AbR70uxpsqNAL4Sp6KjI
7CAuQyddrY3vhybX17eOseRTBctapXMZLIRmU9JnIJdlmTRwUYWCZnkQdqpV7hy0
7oWyx3F49A7KdXLEme2c/GtkzlnDc5+6x2Ssj+1Z9y7gaShDguXTyJLEiN3a8KUF
VilqY0aM02ffNqeRXuuqE+wsSZSrBCP5PIRPZPQSw1027E/5xCB8sWWjcvtqZSnL
2Wst0Np7eJQnnyQJ323UPX//6iMlchYihF9gllUN/eqRzHEc6fTiFskWL0I=
FgQUideMERtvYbD0iGjSDNlM6zs1GMIwHwYDVR0jBBgwFoAUSw4fwcHvmyU96pr7
RNtUqd9m12gwDQYJKoZIhvcNAQELBQADggEBAJfhWCEwK10SIbWjgKB4rfJhyxmU
PRtQ22TElZVYZXo8HvjbrL2qZbGLWVytvFPRSJjJuNiECTAcJF1zTeVXBBf8TEQ+
9GO0ZH1Ir56utInhiJKIRKpigecUZsdWma0V+ZVKPa8oPETqsFdZ+xf51B30x1al
0cStYKy31CK25MGMNDD9PDsIn4MUhgvrJC3vvF3v3u32yEg15Pzu436Jqt7+GVO2
zO7wnK95gE2+ajQ7Nv2/ak0rdbcckLbJqDj6OkAAe67XgHsFcGUX1KdsiNJqBIJ+
fkQQkOnWyy+4eeYpWQD0606xLhrk/nclCvefcrZPytcV4PdZERC60/EZlFo=
-----END CERTIFICATE-----
+50 −50
Original line number Diff line number Diff line
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDKDCCAhCgAwIBAgIUddmL+RrRmJYqaGCW90a7aunKX8gwDQYJKoZIhvcNAQEL
BQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzEzMTEwNjMxWhcNMzAwMzEyMTEw
NzAxWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwKjD5AMkH4G0w+zCQWnziu1j
CxyZ43BWZRTe75LllJ0YU3Y5SHNHZMgbY0wgLtuiMi5NLuHiOObvHcOsZR0/cYeR
9ZE22xWRfTBoHQK2lLPUX5BXaPuMCbZAI1e/A2iBJOII36o+F0Gf85qvmqv+N81j
t14BwS7Kw6tYqMLVadbKWr6mSrSO8urp9ZcjVj4AXWZXWs75Rid+NXPVsyJUvO0f
yUFchj9H/CH4Mh8jsw1onCQ5Eg1b2cZ9RAtJjkemxb9VlNNmZLJq1sMVyc2UHwu7
+Js4UQmkr2De+VG6GPeyCv2g7GzpnsIiIvNDo0LiBgAD8D3AdBTPToa6aBpdxQID
MIIDKDCCAhCgAwIBAgIUBSmgesyprqmIkFI8uSWj66N4AkkwDQYJKoZIhvcNAQEL
BQAwEDEOMAwGA1UEAxMFY2FwaWYwHhcNMjUwMzE0MDgxMDIzWhcNMzAwMzEzMDgx
MDUzWjAnMSUwIwYDVQQDExxjYXBpZiBJbnRlcm1lZGlhdGUgQXV0aG9yaXR5MIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtYod3Lpkqr0XmzIPMpBQtPaY
nxTftboKbG/XDNVWLSYA1Tqh73RNof7i9cOFnDH3IpjrGHFyfhd9TuX276qDVIB1
sRIvb7+pprnY9FpIOEFYwOkkFeKMrh2obRwrLIj97PvUg7S2cUslfz3QXVE37GdJ
zow6MGuDEdt9ICiyIsovEg93/+f/duGT1IhMUZXCjc6wTDjGVbUBBJ0sqt/idVHu
RzQgiEyggRBgHvh4r9jzOjZ/11OCE8rxBH8Ya2aWedtgBSsdGRr4tArIrNw92yOZ
7qT7ZBLti26Ky7DxcwvcVCFI1nZz6w+BB5o3xyrLjfz6f1yjukEk5Dt6VRIx4QID
AQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQUBXH5rGoV6N566W2tuc9KTaEhPYswHwYDVR0jBBgwFoAUXcyCU8QlfEkYq9hr
NVuLhi2GV2AwDQYJKoZIhvcNAQELBQADggEBANp/szFjdTGlAx13Jeut5Thyc5Co
j7SlAsNJxxvEvnZiy8pekd7Bw/Rtdt+Y7+lsjDE2Eo31AbR70uxpsqNAL4Sp6KjI
7CAuQyddrY3vhybX17eOseRTBctapXMZLIRmU9JnIJdlmTRwUYWCZnkQdqpV7hy0
7oWyx3F49A7KdXLEme2c/GtkzlnDc5+6x2Ssj+1Z9y7gaShDguXTyJLEiN3a8KUF
VilqY0aM02ffNqeRXuuqE+wsSZSrBCP5PIRPZPQSw1027E/5xCB8sWWjcvtqZSnL
2Wst0Np7eJQnnyQJ323UPX//6iMlchYihF9gllUN/eqRzHEc6fTiFskWL0I=
FgQUideMERtvYbD0iGjSDNlM6zs1GMIwHwYDVR0jBBgwFoAUSw4fwcHvmyU96pr7
RNtUqd9m12gwDQYJKoZIhvcNAQELBQADggEBAJfhWCEwK10SIbWjgKB4rfJhyxmU
PRtQ22TElZVYZXo8HvjbrL2qZbGLWVytvFPRSJjJuNiECTAcJF1zTeVXBBf8TEQ+
9GO0ZH1Ir56utInhiJKIRKpigecUZsdWma0V+ZVKPa8oPETqsFdZ+xf51B30x1al
0cStYKy31CK25MGMNDD9PDsIn4MUhgvrJC3vvF3v3u32yEg15Pzu436Jqt7+GVO2
zO7wnK95gE2+ajQ7Nv2/ak0rdbcckLbJqDj6OkAAe67XgHsFcGUX1KdsiNJqBIJ+
fkQQkOnWyy+4eeYpWQD0606xLhrk/nclCvefcrZPytcV4PdZERC60/EZlFo=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----
 No newline at end of file
Loading