ATALAIA
  1. Início
  2. Recursos
  3. Blog
  4. Testes de autorização que a sua equipa de QA pode assumir
Testes · Guia

Testes de autorização que a sua equipa de QA pode assumir

O controlo de acessos quebrado é o bug que o QA está mais bem posicionado para apanhar. Uma matriz de papel por endpoint e algumas linhas de pytest tornam-no rotina.

3 min de leituraEquipa Atalaia

QAAutorizaçãopytestTestes de API

O controlo de acessos quebrado está no topo do OWASP Top 10 por uma razão. É também a classe de bug que uma equipa de QA está mais bem posicionada para encontrar. Não exige competências de exploração. Exige uma declaração clara de quem deve poder fazer o quê, e testes que verifiquem que o sistema concorda.

A maioria das suites de QA já testa que o utilizador certo consegue fazer a coisa certa. A lacuna é o caso negativo: que o utilizador errado não consegue. Este guia mostra como tornar esses casos sistemáticos.

Comece por uma matriz

Escreva cada role que o seu produto tem e cada endpoint ou ação que importa. Preencha cada célula com o resultado esperado. Isto é tanto um exercício de requisitos como de testes, e muitas vezes revela desacordos de que ninguém sabia.

EndpointAnónimoViewerEditorAdminAdmin de outro tenant
GET /projects/{id}401200200200404
PATCH /projects/{id}401403200200404
DELETE /projects/{id}401403403204404
POST /projects/{id}/members401403403201404
GET /admin/audit-log401403403200403

A última coluna é a que as equipas esquecem. "Admin de outro tenant" é um utilizador com todos os privilégios numa organização diferente. Não deve obter nada dos seus objetos. Muitos bugs de controlo de acessos só aparecem nessa coluna, porque todas as verificações de role passam e só falta a verificação de propriedade.

Definir os atores

O ambiente de testes precisa de um utilizador por coluna, em pelo menos dois tenants, mais um objeto pertencente ao primeiro tenant. Crie-os numa fixture para que cada teste parta do mesmo estado. Use contas de teste descartáveis que só existem no ambiente de testes.

# conftest.py
import pytest
from api_client import Client, create_tenant, create_user, create_project

@pytest.fixture(scope="session")
def world():
    a = create_tenant("tenant-a")
    b = create_tenant("tenant-b")
    users = {
        "anonymous": None,
        "viewer": create_user(a, role="viewer"),
        "editor": create_user(a, role="editor"),
        "admin": create_user(a, role="admin"),
        "other_admin": create_user(b, role="admin"),
    }
    project = create_project(a, name="qa-fixture")
    return {"users": users, "project": project}

def client_for(world, actor):
    user = world["users"][actor]
    return Client(token=user.token if user else None)

Transformar a matriz em testes

Codifique a matriz como dados e deixe o pytest gerar um teste por célula. Quando um requisito muda, altera uma linha na tabela, não uma função de teste.

# test_authz_projects.py
import pytest
from conftest import client_for

ACTORS = ["anonymous", "viewer", "editor", "admin", "other_admin"]

MATRIX = {
    ("GET",    "/projects/{id}"):         [401, 200, 200, 200, 404],
    ("PATCH",  "/projects/{id}"):         [401, 403, 200, 200, 404],
    ("DELETE", "/projects/{id}"):         [401, 403, 403, 204, 404],
    ("POST",   "/projects/{id}/members"): [401, 403, 403, 201, 404],
}

CASES = [
    (method, path, actor, expected)
    for (method, path), row in MATRIX.items()
    for actor, expected in zip(ACTORS, row)
]

@pytest.mark.parametrize("method,path,actor,expected", CASES)
def test_project_authz(world, method, path, actor, expected):
    client = client_for(world, actor)
    url = path.format(id=world["project"].id)
    resp = client.request(method, url, json={"name": "x"})
    assert resp.status_code == expected, (
        f"{actor} {method} {url}: got {resp.status_code}, want {expected}"
    )

Corra as células destrutivas, como DELETE, contra um objeto novo por teste, ou ordene-as no fim, para que um delete bem-sucedido não estrague as células seguintes.

Além dos status codes

Um status code correto é necessário, não suficiente. Adicione algumas verificações que olham para o que vem de volta:

  • Endpoints de listagem. Um viewer que lista projetos vê apenas os projetos do seu tenant. Valide os ids, não a contagem.
  • Exposição ao nível do campo. A resposta de um viewer não inclui campos como dados de faturação ou emails de membros se a matriz disser que não devia.
  • Mass assignment. Um editor que envie "role": "admin" ou "tenant_id" num body de PATCH não altera esses campos.
  • Referências indiretas. Objetos filhos como /projects/{id}/files/{file_id} verificam que o ficheiro pertence a esse projeto, e não apenas que o projeto está acessível.

O que fazer na segunda-feira

  1. Escolha um recurso, como projetos ou faturas, e escreva com o produto a sua matriz de papel por endpoint.
  2. Adicione a coluna "outro tenant" e acorde a regra do 403 ou 404.
  3. Crie a fixture com um utilizador por coluna, em dois tenants.
  4. Codifique a matriz como dados e corra-a na CI em cada build.
  5. Adicione uma nova linha sempre que uma story acrescentar um endpoint. Ligue-a ao abuse case da story, para que o requisito e o teste fiquem juntos.

Onde isto fica na linha

Vamos conversar

Uma chamada de 30 minutos. Sem slides, sem tabela de preços, e um próximo passo em qualquer caso.