Add 'gerenciador/' from commit '34cc9c75a57dedbf61a6e6a1ba1f35f76e72434b'
git-subtree-dir: gerenciador git-subtree-mainline:f4f546c928git-subtree-split:34cc9c75a5
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app import rate_limit, session
|
||||
from app.auth import NaoAutenticado, UsuarioLogado, exigir_login
|
||||
from app.routers import auth as router_auth
|
||||
|
||||
app_teste = FastAPI()
|
||||
app_teste.include_router(router_auth.router)
|
||||
|
||||
|
||||
@app_teste.exception_handler(NaoAutenticado)
|
||||
def _handler(request, exc):
|
||||
from fastapi.responses import RedirectResponse
|
||||
|
||||
return RedirectResponse(url=f"/login?next={exc.caminho_original}", status_code=303)
|
||||
|
||||
|
||||
@app_teste.get("/rota-protegida-teste")
|
||||
def _rota_protegida(usuario: UsuarioLogado = Depends(exigir_login)):
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
client = TestClient(app_teste, follow_redirects=False)
|
||||
CSRF_TESTE = "teste-csrf-token"
|
||||
client.cookies.set("csrf_token", CSRF_TESTE)
|
||||
|
||||
|
||||
def test_get_login_mostra_formulario():
|
||||
resposta = client.get("/login")
|
||||
assert resposta.status_code == 200
|
||||
assert "senha" in resposta.text.lower()
|
||||
|
||||
|
||||
def test_post_login_credenciais_corretas_grava_cookie_e_redireciona():
|
||||
resposta = client.post(
|
||||
"/login", data={"usuario": "admin", "senha": "teste123", "next": "/clientes", "csrf_token": CSRF_TESTE}
|
||||
)
|
||||
assert resposta.status_code == 303
|
||||
assert resposta.headers["location"] == "/clientes"
|
||||
assert session.SESSION_COOKIE_NAME in resposta.cookies
|
||||
|
||||
|
||||
def test_post_login_sem_next_redireciona_para_clientes():
|
||||
resposta = client.post("/login", data={"usuario": "admin", "senha": "teste123", "csrf_token": CSRF_TESTE})
|
||||
assert resposta.status_code == 303
|
||||
assert resposta.headers["location"] == "/clientes"
|
||||
|
||||
|
||||
def test_post_login_next_externo_e_ignorado_por_seguranca():
|
||||
resposta = client.post(
|
||||
"/login",
|
||||
data={"usuario": "admin", "senha": "teste123", "next": "https://site-malicioso.com", "csrf_token": CSRF_TESTE},
|
||||
)
|
||||
assert resposta.status_code == 303
|
||||
assert resposta.headers["location"] == "/clientes"
|
||||
|
||||
|
||||
def test_post_login_next_com_barra_invertida_e_ignorado_por_seguranca():
|
||||
# "/\evil.com" é tratado por navegadores modernos como equivalente a
|
||||
# "//evil.com" (URL protocol-relative) — precisa ser bloqueado também.
|
||||
resposta = client.post(
|
||||
"/login",
|
||||
data={"usuario": "admin", "senha": "teste123", "next": "/\\evil.com", "csrf_token": CSRF_TESTE},
|
||||
)
|
||||
assert resposta.status_code == 303
|
||||
assert resposta.headers["location"] == "/clientes"
|
||||
|
||||
|
||||
def test_post_login_credenciais_erradas_nao_grava_cookie():
|
||||
resposta = client.post(
|
||||
"/login", data={"usuario": "admin", "senha": "errada", "csrf_token": CSRF_TESTE}
|
||||
)
|
||||
assert resposta.status_code == 200
|
||||
assert "inválid" in resposta.text.lower()
|
||||
assert session.SESSION_COOKIE_NAME not in resposta.cookies
|
||||
|
||||
|
||||
def test_get_rota_protegida_sem_login_redireciona_para_login():
|
||||
# Testes anteriores deixaram um painel_session válido no cookie jar do
|
||||
# client (persistente, como um navegador real) — limpa antes de simular
|
||||
# a requisição de um visitante não autenticado.
|
||||
client.cookies.delete(session.SESSION_COOKIE_NAME)
|
||||
resposta = client.get("/rota-protegida-teste")
|
||||
assert resposta.status_code == 303
|
||||
assert resposta.headers["location"] == "/login?next=/rota-protegida-teste"
|
||||
|
||||
|
||||
def test_post_login_apos_muitas_falhas_e_bloqueado_mesmo_com_senha_certa():
|
||||
# TestClient reporta sempre a mesma origem ("testclient") — limpa
|
||||
# antes pra não herdar a falha registrada em
|
||||
# test_post_login_credenciais_erradas_nao_grava_cookie() acima, e
|
||||
# limpa depois pra não vazar bloqueio pros testes seguintes deste
|
||||
# arquivo (que também usam essa mesma origem "testclient").
|
||||
rate_limit._falhas.pop("testclient", None)
|
||||
try:
|
||||
for _ in range(rate_limit.MAX_TENTATIVAS):
|
||||
client.post("/login", data={"usuario": "admin", "senha": "errada", "csrf_token": CSRF_TESTE})
|
||||
|
||||
resposta = client.post(
|
||||
"/login", data={"usuario": "admin", "senha": "teste123", "csrf_token": CSRF_TESTE}
|
||||
)
|
||||
assert resposta.status_code == 429
|
||||
assert "muitas tentativas" in resposta.text.lower()
|
||||
assert session.SESSION_COOKIE_NAME not in resposta.cookies
|
||||
finally:
|
||||
rate_limit._falhas.pop("testclient", None)
|
||||
|
||||
|
||||
def test_post_logout_apaga_cookie_e_redireciona():
|
||||
cookies = {session.SESSION_COOKIE_NAME: session.criar_token("admin", "Admin", "ADMIN")}
|
||||
resposta = client.post("/logout", data={"csrf_token": CSRF_TESTE}, cookies=cookies)
|
||||
assert resposta.status_code == 303
|
||||
assert resposta.headers["location"] == "/login"
|
||||
# cookie apagado: o Set-Cookie da resposta zera o valor
|
||||
set_cookie = resposta.headers.get("set-cookie", "")
|
||||
assert session.SESSION_COOKIE_NAME in set_cookie
|
||||
|
||||
|
||||
def test_post_logout_apaga_cookie_de_preferencia_de_paginacao():
|
||||
from app.routers.clientes import POR_PAGINA_COOKIE
|
||||
|
||||
cookies = {session.SESSION_COOKIE_NAME: session.criar_token("admin", "Admin", "ADMIN")}
|
||||
resposta = client.post("/logout", data={"csrf_token": CSRF_TESTE}, cookies=cookies)
|
||||
assert resposta.status_code == 303
|
||||
set_cookie = resposta.headers.get("set-cookie", "")
|
||||
assert POR_PAGINA_COOKIE in set_cookie
|
||||
# confirma que o cookie foi de fato apagado (Max-Age=0), não só regravado
|
||||
trecho_cookie = next(
|
||||
parte for parte in resposta.headers.get_list("set-cookie") if POR_PAGINA_COOKIE in parte
|
||||
)
|
||||
assert "Max-Age=0" in trecho_cookie
|
||||
Reference in New Issue
Block a user