From f1a12ffdf37f79561242e1294aa9034c5d0bab0a Mon Sep 17 00:00:00 2001 From: Kenio de Souza Date: Fri, 29 Aug 2025 14:33:25 -0300 Subject: [PATCH 1/8] =?UTF-8?q?feat(Valida=C3=A7=C3=B5es):=20=20Adicionado?= =?UTF-8?q?=20valida=C3=A7=C3=B5es=20no=20cadastro=20do=20usu=C3=A1rio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + actions/security/security.py | 43 + actions/validations/cpf.py | 34 + actions/validations/email.py | 8 +- actions/validations/text.py | 60 +- config/database/firebird.json | 4 +- config/database/firebird_exemple.json | 13 + .../actions/g_usuario/delete_action.py | 2 +- .../g_usuario_get_by_authenticate_action.py | 14 + .../g_usuario/g_usuario_get_by_cpf_action.py | 13 + .../g_usuario_get_by_email_action.py | 13 + .../g_usuario_get_by_login_action.py | 13 + ... => g_usuario_get_by_usuario_id_action.py} | 2 +- ...ave_action.py => g_usuario_save_action.py} | 2 +- ...how_action.py => g_usuario_show_action.py} | 2 +- .../g_usuario/g_usuario_update_action.py | 11 + .../actions/g_usuario/get_by_login_action.py | 14 - .../actions/g_usuario/index_action.py | 2 +- .../controllers/c_caixa_item_controller.py | 2 +- .../controllers/g_usuario_controller.py | 90 +- .../v1/administrativo/endpoints/g_usuario.py | 106 - .../endpoints/g_usuario_endpoint.py | 170 + ...tory.py => g_usuario_delete_repository.py} | 0 ...usuario_get_by_authenticate_repository.py} | 10 +- .../g_usuario_get_by_cpf_repository.py | 47 + .../g_usuario_get_by_email_repository.py | 18 + .../g_usuario_get_by_login_repository.py | 18 + ...g_usuario_get_by_usuario_id_repository.py} | 0 ...itory.py => g_usuario_index_repository.py} | 0 .../g_usuario/g_usuario_save_repository.py | 47 + ...sitory.py => g_usuario_show_repository.py} | 0 .../g_usuario/g_usuario_update_repository.py | 73 + .../repositories/g_usuario/save_repository.py | 116 - .../schemas/g_usuario_schema.py | 328 +- .../go/g_usuario_authenticate_service.py | 53 + .../g_usuario/go/g_usuario_get_cpf_service.py | 26 + .../go/g_usuario_get_email_service.py | 26 + .../go/g_usuario_get_login_service.py | 26 + ..._service.py => g_usuario_index_service.py} | 0 ...{me_service.py => g_usuario_me_service.py} | 2 +- .../g_usuario/go/g_usuario_save_service.py | 100 + ...w_service.py => g_usuario_show_service.py} | 2 +- .../g_usuario/go/g_usuario_update_service.py | 12 + .../services/g_usuario/go/login_service.py | 43 - .../services/g_usuario/go/save_service.py | 32 - packages/v1/api.py | 4 +- requirements.txt | 4 + storage/temp.json | 363 + storage/temp/request.json | 10148 ++++++++++++++++ 49 files changed, 11758 insertions(+), 361 deletions(-) create mode 100644 actions/security/security.py create mode 100644 actions/validations/cpf.py create mode 100644 config/database/firebird_exemple.json create mode 100644 packages/v1/administrativo/actions/g_usuario/g_usuario_get_by_authenticate_action.py create mode 100644 packages/v1/administrativo/actions/g_usuario/g_usuario_get_by_cpf_action.py create mode 100644 packages/v1/administrativo/actions/g_usuario/g_usuario_get_by_email_action.py create mode 100644 packages/v1/administrativo/actions/g_usuario/g_usuario_get_by_login_action.py rename packages/v1/administrativo/actions/g_usuario/{get_by_usuario_id_action.py => g_usuario_get_by_usuario_id_action.py} (75%) rename packages/v1/administrativo/actions/g_usuario/{save_action.py => g_usuario_save_action.py} (71%) rename packages/v1/administrativo/actions/g_usuario/{show_action.py => g_usuario_show_action.py} (81%) create mode 100644 packages/v1/administrativo/actions/g_usuario/g_usuario_update_action.py delete mode 100644 packages/v1/administrativo/actions/g_usuario/get_by_login_action.py delete mode 100644 packages/v1/administrativo/endpoints/g_usuario.py create mode 100644 packages/v1/administrativo/endpoints/g_usuario_endpoint.py rename packages/v1/administrativo/repositories/g_usuario/{delete_repository.py => g_usuario_delete_repository.py} (100%) rename packages/v1/administrativo/repositories/g_usuario/{get_by_login_repository.py => g_usuario_get_by_authenticate_repository.py} (62%) create mode 100644 packages/v1/administrativo/repositories/g_usuario/g_usuario_get_by_cpf_repository.py create mode 100644 packages/v1/administrativo/repositories/g_usuario/g_usuario_get_by_email_repository.py create mode 100644 packages/v1/administrativo/repositories/g_usuario/g_usuario_get_by_login_repository.py rename packages/v1/administrativo/repositories/g_usuario/{get_by_usuario_id_repository.py => g_usuario_get_by_usuario_id_repository.py} (100%) rename packages/v1/administrativo/repositories/g_usuario/{index_repository.py => g_usuario_index_repository.py} (100%) create mode 100644 packages/v1/administrativo/repositories/g_usuario/g_usuario_save_repository.py rename packages/v1/administrativo/repositories/g_usuario/{show_repository.py => g_usuario_show_repository.py} (100%) create mode 100644 packages/v1/administrativo/repositories/g_usuario/g_usuario_update_repository.py delete mode 100644 packages/v1/administrativo/repositories/g_usuario/save_repository.py create mode 100644 packages/v1/administrativo/services/g_usuario/go/g_usuario_authenticate_service.py create mode 100644 packages/v1/administrativo/services/g_usuario/go/g_usuario_get_cpf_service.py create mode 100644 packages/v1/administrativo/services/g_usuario/go/g_usuario_get_email_service.py create mode 100644 packages/v1/administrativo/services/g_usuario/go/g_usuario_get_login_service.py rename packages/v1/administrativo/services/g_usuario/go/{index_service.py => g_usuario_index_service.py} (100%) rename packages/v1/administrativo/services/g_usuario/go/{me_service.py => g_usuario_me_service.py} (82%) create mode 100644 packages/v1/administrativo/services/g_usuario/go/g_usuario_save_service.py rename packages/v1/administrativo/services/g_usuario/go/{show_service.py => g_usuario_show_service.py} (87%) create mode 100644 packages/v1/administrativo/services/g_usuario/go/g_usuario_update_service.py delete mode 100644 packages/v1/administrativo/services/g_usuario/go/login_service.py delete mode 100644 packages/v1/administrativo/services/g_usuario/go/save_service.py diff --git a/.gitignore b/.gitignore index 80ee3d1..6530f0c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ venv/ .env .env.* +# Ignora configuração de acesso ao banco de dados Firebird +config/database/firebird.json + # Bytecode compilado __pycache__/ *.py[cod] diff --git a/actions/security/security.py b/actions/security/security.py new file mode 100644 index 0000000..e23c3a6 --- /dev/null +++ b/actions/security/security.py @@ -0,0 +1,43 @@ +# core/security.py + +# Importa CryptContext da biblioteca passlib para operações de hash de senha +from passlib.context import CryptContext + +# Cria uma instância do contexto de criptografia +# O esquema usado é 'bcrypt', que é seguro e amplamente aceito +# O parâmetro 'deprecated="auto"' marca versões antigas como inseguras, se aplicável +CRYPTO = CryptContext(schemes=['bcrypt'], deprecated='auto') + + +class Security: + + # Verifica se a senha tem um hash válido + @staticmethod + def is_hash(senha: str) -> bool: + """ + Verifica se a string fornecida é um hash reconhecido pelo CryptContext. + """ + return CRYPTO.identify(senha) + + + # Verifica se uma senha fornecida corresponde ao hash armazenado + def verify_senha_api(plain_senha_api: str, hashed_senha_api: str) -> bool: + """ + Compara a senha fornecida em texto puro com o hash armazenado. + + :param plain_senha_api: Senha digitada pelo usuário + :param hashed_senha_api: Hash da senha armazenado no banco de dados + :return: True se corresponder, False se não + """ + return CRYPTO.verify(plain_senha_api, hashed_senha_api) + + + # Gera o hash de uma senha fornecida + def hash_senha_api(plain_senha_api: str) -> str: + """ + Gera e retorna o hash da senha fornecida. + + :param plain_senha_api: Senha em texto puro fornecida pelo usuário + :return: Hash da senha + """ + return CRYPTO.hash(plain_senha_api) diff --git a/actions/validations/cpf.py b/actions/validations/cpf.py new file mode 100644 index 0000000..2083800 --- /dev/null +++ b/actions/validations/cpf.py @@ -0,0 +1,34 @@ +import re + + +class CPF: + + @staticmethod + def is_valid_cpf(data: str) -> bool: + # Remove caracteres não numéricos + data = re.sub(r'\D', '', data) + + # Verifica se tem 11 dígitos + if len(data) != 11: + return False + + # CPFs com todos os dígitos iguais são inválidos + if data == data[0] * 11: + return False + + # Calcula o primeiro e segundo dígitos verificadores + def calcular_digito(digitos, peso): + soma = sum(int(a) * b for a, b in zip(digitos, peso)) + resto = soma % 11 + return '0' if resto < 2 else str(11 - resto) + + # Primeiro dígito verificador + peso1 = range(10, 1, -1) + digito1 = calcular_digito(data[:9], peso1) + + # Segundo dígito verificador + peso2 = range(11, 1, -1) + digito2 = calcular_digito(data[:10], peso2) + + # Verifica se os dígitos batem + return data[-2:] == digito1 + digito2 \ No newline at end of file diff --git a/actions/validations/email.py b/actions/validations/email.py index 4a31523..4839b95 100644 --- a/actions/validations/email.py +++ b/actions/validations/email.py @@ -4,8 +4,6 @@ import re class Email: @staticmethod - def validate(data: str) -> str: - # Validação de email - default = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$' - if not re.match(default, data): - raise f"Email inválido: {data}" \ No newline at end of file + def is_valid_email(email: str) -> bool: + """Check if email has a valid structure""" + return bool(re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email)) \ No newline at end of file diff --git a/actions/validations/text.py b/actions/validations/text.py index 36f7aab..51438bc 100644 --- a/actions/validations/text.py +++ b/actions/validations/text.py @@ -4,16 +4,60 @@ import re class Text: - @staticmethod - def sanitize(data: str) -> str: - """Trim spaces, escape HTML entities, collapse multiple spaces.""" - data = data.strip() - data = html.escape(data) - data = re.sub(r"\s+", " ", data) - return data - + # Remove as mascaras de números @staticmethod def just_numbers(data: str) -> str: """ Mantêm apenas os numeros """ data = re.sub(r"[^\d]", "", data) + return data + + # Verifica se um e-mail é válido + @staticmethod + def is_valid_email(email: str) -> bool: + """Check if email has a valid structure""" + return bool(re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email)) + + + """ + Sanitiza entradas de texto contra XSS e SQL Injection básicos. + - Remove espaços extras + - Escapa entidades HTML + - Remove padrões suspeitos de XSS e SQL Injection + - Normaliza múltiplos espaços em um só + """ + @staticmethod + def sanitize_input(data: str) -> str: + + if not data: + return data + + # 1) Remove espaços no início e no fim + data = data.strip() + + # 2) Escapa entidades HTML (< > & ") + data = html.escape(data) + + # 3) Remove múltiplos espaços seguidos + data = re.sub(r"\s+", " ", data) + + # 4) Remove tags