fix(seguranca): move algoritmo de geracao de chave do JS pro backend
O algoritmo (CHAVE_INTERNA + funcoes gerarReativacao/gerarInstalacao) ficava 100% legivel no codigo-fonte da pagina — qualquer pessoa com acesso ao painel conseguia abrir o DevTools e extrair a logica pra gerar chaves de licenca fora do sistema. Move tudo pra app/gerador_chave.py (roda so no servidor) e expoe via POST /gerador-chave/gerar (autenticado, protegido por CSRF). A pagina agora so manda os dados do formulario e mostra o resultado que volta do backend. Algoritmo verificado byte-a-byte contra os 3 exemplos reais conhecidos (cliente 000001, ALUGUEL). Bonus: o byte inicial agora usa secrets.randbelow (CSPRNG) em vez de Math.random() do JS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a3a84abd37
commit
5c577a644a
@@ -0,0 +1,56 @@
|
|||||||
|
"""
|
||||||
|
Algoritmo de geração de chave (reativação e instalação), reverso-projetado
|
||||||
|
do sistema legado (Delphi). Fica só aqui no backend — NUNCA deve voltar a
|
||||||
|
ser exposto em JavaScript de página, porque isso permite qualquer pessoa
|
||||||
|
com acesso ao painel extrair o algoritmo e gerar chaves fora do sistema.
|
||||||
|
|
||||||
|
Verificado byte-a-byte contra 3 exemplos reais de chave de instalação
|
||||||
|
(cliente 000001, ALUGUEL): byte inicial 0xDC -> "DC45092C",
|
||||||
|
0x2E -> "2E35083D", 0x3D -> "3D25090C" (ver testes).
|
||||||
|
"""
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
CHAVE_INTERNA = (
|
||||||
|
"YUQL23KL23DF90WI5E1JAS467NMCXXL6JAOAUWWMCL0AOMM4A4VZYW9KHJUI2347"
|
||||||
|
"EJHJKDF3424SKL K3LAKDJSL9RTIKJ"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _cifrar(texto: str, byte_inicial: int) -> list[int]:
|
||||||
|
"""Cifra por feedback de byte anterior + XOR com CHAVE_INTERNA rotativa.
|
||||||
|
Retorna a lista de bytes cifrados (sem o byte_inicial na frente)."""
|
||||||
|
anterior = byte_inicial
|
||||||
|
cifrados = []
|
||||||
|
for i, ch in enumerate(texto):
|
||||||
|
valor = (ord(ch) + anterior) % 255
|
||||||
|
cifrado = valor ^ ord(CHAVE_INTERNA[i % len(CHAVE_INTERNA)])
|
||||||
|
cifrados.append(cifrado)
|
||||||
|
anterior = cifrado
|
||||||
|
return cifrados
|
||||||
|
|
||||||
|
|
||||||
|
def gerar_reativacao(codigo_cliente: str, mes: int, ano: int, byte_inicial: int | None = None) -> str:
|
||||||
|
"""Chave de reativação — mês/cliente/ano cifrados por inteiro (22 caracteres hex)."""
|
||||||
|
cliente = str(codigo_cliente).zfill(6)
|
||||||
|
texto = str(mes).zfill(2) + cliente + str(ano)[-2:]
|
||||||
|
aleatorio = byte_inicial if byte_inicial is not None else secrets.randbelow(256)
|
||||||
|
cifrados = _cifrar(texto, aleatorio)
|
||||||
|
return "".join(f"{v:02X}" for v in [aleatorio, *cifrados])
|
||||||
|
|
||||||
|
|
||||||
|
def gerar_instalacao(codigo_cliente: str, modalidade: str, byte_inicial: int | None = None) -> str:
|
||||||
|
"""Chave de instalação — só a modalidade ("30"=ALUGUEL / "00"=COMPRA) é
|
||||||
|
cifrada; o valor derivado do código do cliente (cliente*19+31) entra sem
|
||||||
|
cifrar, no meio dos 6 caracteres cifrados (3 antes + 3 depois)."""
|
||||||
|
try:
|
||||||
|
cliente_num = int(str(codigo_cliente))
|
||||||
|
except ValueError:
|
||||||
|
cliente_num = 0
|
||||||
|
valor_cliente = cliente_num * 19 + 31
|
||||||
|
texto_modalidade = "30" if modalidade == "ALUGUEL" else "00"
|
||||||
|
|
||||||
|
aleatorio = byte_inicial if byte_inicial is not None else secrets.randbelow(256)
|
||||||
|
cifrados = _cifrar(texto_modalidade, aleatorio)
|
||||||
|
criptografado = "".join(f"{v:02X}" for v in [aleatorio, *cifrados])
|
||||||
|
parte1, parte2 = criptografado[0:3], criptografado[3:6]
|
||||||
|
return f"{parte1}{valor_cliente}{parte2}"
|
||||||
@@ -1,19 +1,60 @@
|
|||||||
"""
|
"""
|
||||||
Calculadora de chave de reativação, para uso manual (reenvio/correção
|
Calculadora de chave de reativação/instalação, para uso manual (reenvio/
|
||||||
pontual). 100% client-side (mesmo algoritmo gerarReativacao já usado no
|
correção pontual). O algoritmo em si (app/gerador_chave.py) roda só aqui
|
||||||
workflow n8n e em Gerar chave WEB/index.html) — esta rota só serve a
|
no backend — a página não recebe mais nada além do resultado já pronto,
|
||||||
página; NUNCA grava em Clientes_Chaves.
|
pra ninguém conseguir extrair o algoritmo pelo código-fonte da página.
|
||||||
|
NUNCA grava em Clientes_Chaves.
|
||||||
"""
|
"""
|
||||||
from fastapi import APIRouter, Depends, Request
|
import re
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
|
from fastapi import APIRouter, Depends, Form, Request
|
||||||
|
from fastapi.responses import HTMLResponse, JSONResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
from ..auth import exigir_login
|
from ..auth import exigir_login
|
||||||
|
from ..csrf import exigir_csrf, token_do_contexto
|
||||||
|
from ..gerador_chave import gerar_instalacao, gerar_reativacao
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
templates = Jinja2Templates(directory="app/templates")
|
templates = Jinja2Templates(directory="app/templates")
|
||||||
|
|
||||||
|
_RE_CODIGO = re.compile(r"^[0-9]{1,6}$")
|
||||||
|
_RE_REFERENCIA = re.compile(r"^\d{4}-\d{2}$")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/gerador-chave", response_class=HTMLResponse)
|
@router.get("/gerador-chave", response_class=HTMLResponse)
|
||||||
def pagina(request: Request, usuario: str = Depends(exigir_login)):
|
def pagina(request: Request, usuario: str = Depends(exigir_login)):
|
||||||
return templates.TemplateResponse(request, "gerador_chave.html", {})
|
return templates.TemplateResponse(request, "gerador_chave.html", {"csrf_token": token_do_contexto(request)})
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/gerador-chave/gerar", dependencies=[Depends(exigir_csrf)])
|
||||||
|
def gerar(
|
||||||
|
tipo: str = Form(...),
|
||||||
|
codigo: str = Form(...),
|
||||||
|
referencia: str = Form(""),
|
||||||
|
modalidade: str = Form(""),
|
||||||
|
usuario: str = Depends(exigir_login),
|
||||||
|
):
|
||||||
|
codigo = codigo.strip()
|
||||||
|
if not _RE_CODIGO.match(codigo):
|
||||||
|
return JSONResponse({"erro": "Código do cliente deve ter até 6 dígitos numéricos."}, status_code=400)
|
||||||
|
codigo_formatado = codigo.zfill(6)
|
||||||
|
|
||||||
|
if tipo == "reativacao":
|
||||||
|
if not _RE_REFERENCIA.match(referencia):
|
||||||
|
return JSONResponse({"erro": "Selecione o mês/ano de referência."}, status_code=400)
|
||||||
|
ano_str, mes_str = referencia.split("-")
|
||||||
|
mes, ano = int(mes_str), int(ano_str)
|
||||||
|
if not (1 <= mes <= 12):
|
||||||
|
return JSONResponse({"erro": "Mês fora do intervalo 1-12."}, status_code=400)
|
||||||
|
if not (2000 <= ano <= 2100):
|
||||||
|
return JSONResponse({"erro": "Ano inválido (use entre 2000 e 2100)."}, status_code=400)
|
||||||
|
chave = gerar_reativacao(codigo_formatado, mes, ano)
|
||||||
|
elif tipo == "instalacao":
|
||||||
|
if modalidade not in ("ALUGUEL", "COMPRA"):
|
||||||
|
return JSONResponse({"erro": "Modalidade inválida."}, status_code=400)
|
||||||
|
chave = gerar_instalacao(codigo_formatado, modalidade)
|
||||||
|
else:
|
||||||
|
return JSONResponse({"erro": "Tipo de chave inválido."}, status_code=400)
|
||||||
|
|
||||||
|
return JSONResponse({"chave": chave})
|
||||||
|
|||||||
@@ -34,56 +34,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const CHAVE_INTERNA =
|
// O algoritmo de geração de chave roda só no backend (app/gerador_chave.py)
|
||||||
"YUQL23KL23DF90WI5E1JAS467NMCXXL6JAOAUWWMCL0AOMM4A4VZYW9KHJUI2347" +
|
// — esta página só manda os dados do formulário pro endpoint e mostra o
|
||||||
"EJHJKDF3424SKL K3LAKDJSL9RTIKJ";
|
// resultado. Nunca reintroduza a lógica de cifra aqui: qualquer pessoa com
|
||||||
|
// acesso à página consegue ler o código-fonte JS pelo navegador.
|
||||||
// ==========================
|
const CSRF_TOKEN = "{{ csrf_token }}";
|
||||||
// REATIVAÇÃO — mês/cliente/ano cifrados por inteiro (22 caracteres)
|
|
||||||
// ==========================
|
|
||||||
function gerarReativacao(codigoCliente, mes, ano, byteInicial = null) {
|
|
||||||
const cliente = String(codigoCliente).padStart(6, '0');
|
|
||||||
const texto = String(mes).padStart(2, '0') + cliente + String(ano).slice(-2);
|
|
||||||
const aleatorio = byteInicial !== null ? byteInicial : Math.floor(Math.random() * 256);
|
|
||||||
let anterior = aleatorio;
|
|
||||||
const resultado = [aleatorio];
|
|
||||||
const bytes = [];
|
|
||||||
for (let i = 0; i < texto.length; i++) bytes.push(texto.charCodeAt(i) & 0xff);
|
|
||||||
for (let i = 0; i < bytes.length; i++) {
|
|
||||||
const valor = (bytes[i] + anterior) % 255;
|
|
||||||
const cifrado = valor ^ CHAVE_INTERNA.charCodeAt(i % CHAVE_INTERNA.length);
|
|
||||||
resultado.push(cifrado);
|
|
||||||
anterior = cifrado;
|
|
||||||
}
|
|
||||||
return resultado.map(v => v.toString(16).padStart(2, '0').toUpperCase()).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================
|
|
||||||
// INSTALAÇÃO — só a modalidade ("30"=ALUGUEL / "00"=COMPRA) é cifrada
|
|
||||||
// (mesmo algoritmo de cifra da reativação, texto diferente); o valor
|
|
||||||
// derivado do código do cliente (cliente×19+31) entra sem cifrar, no
|
|
||||||
// meio dos 6 caracteres cifrados (3 antes + 3 depois).
|
|
||||||
// ==========================
|
|
||||||
function gerarInstalacao(codigoCliente, modalidade, byteInicial = null) {
|
|
||||||
const clienteNum = parseInt(String(codigoCliente), 10) || 0;
|
|
||||||
const valorCliente = clienteNum * 19 + 31;
|
|
||||||
const textoModalidade = modalidade === 'ALUGUEL' ? '30' : '00';
|
|
||||||
|
|
||||||
const aleatorio = byteInicial !== null ? byteInicial : Math.floor(Math.random() * 256);
|
|
||||||
let anterior = aleatorio;
|
|
||||||
const bytes = [aleatorio];
|
|
||||||
for (let i = 0; i < textoModalidade.length; i++) {
|
|
||||||
const valor = (textoModalidade.charCodeAt(i) + anterior) % 255;
|
|
||||||
const cifrado = valor ^ CHAVE_INTERNA.charCodeAt(i % CHAVE_INTERNA.length);
|
|
||||||
bytes.push(cifrado);
|
|
||||||
anterior = cifrado;
|
|
||||||
}
|
|
||||||
|
|
||||||
const criptografado = bytes.map(v => v.toString(16).padStart(2, '0').toUpperCase()).join('');
|
|
||||||
const parte1 = criptografado.substring(0, 3);
|
|
||||||
const parte2 = criptografado.substring(3, 6);
|
|
||||||
return `${parte1}${valorCliente}${parte2}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $tipo = document.getElementById('tipo');
|
const $tipo = document.getElementById('tipo');
|
||||||
const $codigo = document.getElementById('codigo');
|
const $codigo = document.getElementById('codigo');
|
||||||
@@ -117,25 +72,32 @@ function mostrarErro(msg) {
|
|||||||
$copiar.style.display = 'none';
|
$copiar.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('gerar').addEventListener('click', () => {
|
document.getElementById('gerar').addEventListener('click', async () => {
|
||||||
$erro.style.display = 'none';
|
$erro.style.display = 'none';
|
||||||
const codigoCliente = String($codigo.value || '').trim();
|
|
||||||
if (!/^[0-9]{1,6}$/.test(codigoCliente)) return mostrarErro('Código do cliente deve ter até 6 dígitos numéricos.');
|
|
||||||
const codigoFormatado = codigoCliente.padStart(6, '0');
|
|
||||||
|
|
||||||
let chave;
|
const corpo = new URLSearchParams({
|
||||||
if ($tipo.value === 'reativacao') {
|
csrf_token: CSRF_TOKEN,
|
||||||
if (!/^\d{4}-\d{2}$/.test($referencia.value)) return mostrarErro('Selecione o mês/ano de referência.');
|
tipo: $tipo.value,
|
||||||
const [anoStr, mesStr] = $referencia.value.split('-');
|
codigo: $codigo.value,
|
||||||
const mesNum = Number(mesStr), anoNum = Number(anoStr);
|
referencia: $referencia.value,
|
||||||
if (!(mesNum >= 1 && mesNum <= 12)) return mostrarErro('Mês fora do intervalo 1-12.');
|
modalidade: $modalidade.value,
|
||||||
if (!(anoNum >= 2000 && anoNum <= 2100)) return mostrarErro('Ano inválido (use entre 2000 e 2100).');
|
});
|
||||||
chave = gerarReativacao(codigoFormatado, mesNum, anoNum);
|
|
||||||
} else {
|
let resposta, dados;
|
||||||
chave = gerarInstalacao(codigoFormatado, $modalidade.value);
|
try {
|
||||||
|
resposta = await fetch('/gerador-chave/gerar', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
body: corpo,
|
||||||
|
});
|
||||||
|
dados = await resposta.json();
|
||||||
|
} catch (e) {
|
||||||
|
return mostrarErro('Não foi possível falar com o servidor — tente de novo.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$resultado.textContent = chave;
|
if (!resposta.ok) return mostrarErro(dados.erro || 'Não foi possível gerar a chave.');
|
||||||
|
|
||||||
|
$resultado.textContent = dados.chave;
|
||||||
$resultado.style.display = 'block';
|
$resultado.style.display = 'block';
|
||||||
$copiar.style.display = 'inline-block';
|
$copiar.style.display = 'inline-block';
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
from app.gerador_chave import gerar_instalacao, gerar_reativacao
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_instalacao_bate_com_exemplo_real_byte_dc():
|
||||||
|
assert gerar_instalacao("000001", "ALUGUEL", byte_inicial=0xDC) == "DC45092C"
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_instalacao_bate_com_exemplo_real_byte_2e():
|
||||||
|
assert gerar_instalacao("000001", "ALUGUEL", byte_inicial=0x2E) == "2E35083D"
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_instalacao_bate_com_exemplo_real_byte_3d():
|
||||||
|
assert gerar_instalacao("000001", "ALUGUEL", byte_inicial=0x3D) == "3D25090C"
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_instalacao_compra_usa_modalidade_00():
|
||||||
|
# Mesmo byte inicial, modalidade diferente -> cifrado diferente do ALUGUEL.
|
||||||
|
aluguel = gerar_instalacao("000001", "ALUGUEL", byte_inicial=0xDC)
|
||||||
|
compra = gerar_instalacao("000001", "COMPRA", byte_inicial=0xDC)
|
||||||
|
assert aluguel != compra
|
||||||
|
assert compra.startswith("DC") # byte inicial some no começo, sem cifrar
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_instalacao_sem_byte_inicial_gera_aleatorio_valido():
|
||||||
|
chave = gerar_instalacao("000001", "ALUGUEL")
|
||||||
|
assert len(chave) == 8
|
||||||
|
assert chave[3:5].isdigit() # valor_cliente embutido no meio
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_reativacao_e_deterministico_com_byte_fixo():
|
||||||
|
chave1 = gerar_reativacao("000123", 8, 2026, byte_inicial=0x10)
|
||||||
|
chave2 = gerar_reativacao("000123", 8, 2026, byte_inicial=0x10)
|
||||||
|
assert chave1 == chave2
|
||||||
|
assert len(chave1) == 22 # 11 bytes (1 aleatorio + 10 do texto) em hex
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_reativacao_sem_byte_inicial_varia():
|
||||||
|
# Estatisticamente, gerar várias vezes sem byte fixo não deve sempre
|
||||||
|
# bater igual (chance de colisão é 1/256).
|
||||||
|
chaves = {gerar_reativacao("000123", 8, 2026) for _ in range(20)}
|
||||||
|
assert len(chaves) > 1
|
||||||
@@ -8,18 +8,95 @@ app_teste = FastAPI()
|
|||||||
app_teste.include_router(router_chave_manual.router)
|
app_teste.include_router(router_chave_manual.router)
|
||||||
app_teste.dependency_overrides[exigir_login] = lambda: "teste"
|
app_teste.dependency_overrides[exigir_login] = lambda: "teste"
|
||||||
client = TestClient(app_teste)
|
client = TestClient(app_teste)
|
||||||
|
CSRF_TESTE = "teste-csrf-token"
|
||||||
|
client.cookies.set("csrf_token", CSRF_TESTE)
|
||||||
|
|
||||||
|
|
||||||
def test_pagina_gerador_carrega():
|
def test_pagina_gerador_carrega():
|
||||||
resposta = client.get("/gerador-chave")
|
resposta = client.get("/gerador-chave")
|
||||||
assert resposta.status_code == 200
|
assert resposta.status_code == 200
|
||||||
assert "gerarReativacao" in resposta.text
|
|
||||||
assert "gerarInstalacao" in resposta.text
|
|
||||||
assert "Gerador de Chave" in resposta.text
|
assert "Gerador de Chave" in resposta.text
|
||||||
|
# O algoritmo NUNCA pode voltar a aparecer no HTML/JS da página — ele
|
||||||
|
# roda só no backend (app/gerador_chave.py) desde que essa exposição
|
||||||
|
# foi corrigida.
|
||||||
|
assert "CHAVE_INTERNA" not in resposta.text
|
||||||
|
assert "function gerarReativacao" not in resposta.text
|
||||||
|
assert "function gerarInstalacao" not in resposta.text
|
||||||
|
|
||||||
|
|
||||||
def test_pagina_gerador_nao_grava_nada():
|
def test_pagina_gerador_nao_grava_nada():
|
||||||
# Rota é GET-only: não existe POST /gerador-chave, então não há como
|
# GET /gerador-chave não recebe csrf_token no cookie/form combinando
|
||||||
# este router gravar em Clientes_Chaves.
|
# com nada que grave — só há dois POSTs possíveis nesse router:
|
||||||
|
# /gerador-chave (não existe, 405) e /gerador-chave/gerar (não grava
|
||||||
|
# em Clientes_Chaves, só calcula e devolve).
|
||||||
resposta = client.post("/gerador-chave")
|
resposta = client.post("/gerador-chave")
|
||||||
assert resposta.status_code == 405
|
assert resposta.status_code == 405
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_reativacao_com_sucesso():
|
||||||
|
resposta = client.post(
|
||||||
|
"/gerador-chave/gerar",
|
||||||
|
data={"csrf_token": CSRF_TESTE, "tipo": "reativacao", "codigo": "123", "referencia": "2026-08"},
|
||||||
|
)
|
||||||
|
assert resposta.status_code == 200
|
||||||
|
corpo = resposta.json()
|
||||||
|
assert len(corpo["chave"]) == 22
|
||||||
|
assert all(c in "0123456789ABCDEF" for c in corpo["chave"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_instalacao_com_sucesso_bate_com_exemplo_conhecido():
|
||||||
|
# Não dá pra fixar o byte aleatório via HTTP, mas confere que o valor do
|
||||||
|
# cliente (1*19+31=50) aparece embutido no meio da chave, como o
|
||||||
|
# algoritmo real exige.
|
||||||
|
resposta = client.post(
|
||||||
|
"/gerador-chave/gerar",
|
||||||
|
data={"csrf_token": CSRF_TESTE, "tipo": "instalacao", "codigo": "000001", "modalidade": "ALUGUEL"},
|
||||||
|
)
|
||||||
|
assert resposta.status_code == 200
|
||||||
|
corpo = resposta.json()
|
||||||
|
assert len(corpo["chave"]) == 8
|
||||||
|
assert corpo["chave"][3:5] == "50"
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_codigo_invalido_bloqueia():
|
||||||
|
resposta = client.post(
|
||||||
|
"/gerador-chave/gerar",
|
||||||
|
data={"csrf_token": CSRF_TESTE, "tipo": "instalacao", "codigo": "abc123", "modalidade": "ALUGUEL"},
|
||||||
|
)
|
||||||
|
assert resposta.status_code == 400
|
||||||
|
assert "6 dígitos" in resposta.json()["erro"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_reativacao_sem_referencia_bloqueia():
|
||||||
|
resposta = client.post(
|
||||||
|
"/gerador-chave/gerar",
|
||||||
|
data={"csrf_token": CSRF_TESTE, "tipo": "reativacao", "codigo": "123", "referencia": ""},
|
||||||
|
)
|
||||||
|
assert resposta.status_code == 400
|
||||||
|
assert "mês/ano" in resposta.json()["erro"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_instalacao_modalidade_invalida_bloqueia():
|
||||||
|
resposta = client.post(
|
||||||
|
"/gerador-chave/gerar",
|
||||||
|
data={"csrf_token": CSRF_TESTE, "tipo": "instalacao", "codigo": "123", "modalidade": "X"},
|
||||||
|
)
|
||||||
|
assert resposta.status_code == 400
|
||||||
|
assert "Modalidade" in resposta.json()["erro"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_tipo_invalido_bloqueia():
|
||||||
|
resposta = client.post(
|
||||||
|
"/gerador-chave/gerar",
|
||||||
|
data={"csrf_token": CSRF_TESTE, "tipo": "outracoisa", "codigo": "123"},
|
||||||
|
)
|
||||||
|
assert resposta.status_code == 400
|
||||||
|
assert "Tipo de chave" in resposta.json()["erro"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_gerar_sem_csrf_bloqueia():
|
||||||
|
resposta = client.post(
|
||||||
|
"/gerador-chave/gerar",
|
||||||
|
data={"tipo": "instalacao", "codigo": "123", "modalidade": "ALUGUEL"},
|
||||||
|
)
|
||||||
|
assert resposta.status_code == 403
|
||||||
|
|||||||
Reference in New Issue
Block a user