Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new field and instructions in docs to inform system of origin #130

Merged
merged 9 commits into from
Oct 3, 2024
27 changes: 19 additions & 8 deletions src/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@
"""

from datetime import timedelta
from typing import Annotated
import os
from typing import Union
import json
import os
from typing import Annotated, Union

from fastapi import Depends, FastAPI, HTTPException, status, Header, Response
from fastapi import Depends, FastAPI, HTTPException, status, Header, Request, Response
from fastapi.security import OAuth2PasswordRequestForm
from fastapi.responses import RedirectResponse
from fastapi.responses import JSONResponse, RedirectResponse
from sqlalchemy.exc import IntegrityError


import schemas
import crud
from db_config import DbContextManager, create_db_and_tables
import crud_auth
import email_config
from db_config import DbContextManager, create_db_and_tables
import schemas
from util import check_permissions

ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES"))
TEST_ENVIRONMENT = os.environ.get("TEST_ENVIRONMENT", 'False') == 'True'
TEST_ENVIRONMENT = os.environ.get("TEST_ENVIRONMENT", "False") == "True"

# ## INIT --------------------------------------------------

Expand Down Expand Up @@ -56,6 +55,18 @@ async def on_startup():
await crud_auth.init_user_admin()


@app.middleware("http")
async def check_user_agent(request: Request, call_next):
user_agent = request.headers.get("User-Agent", None)

if not user_agent:
return JSONResponse(
status_code=400,
content={"detail": "User-Agent header is required"},
)
return await call_next(request)


@app.get("/", include_in_schema=False)
async def docs_redirect(
accept: Union[str, None] = Header(default="text/html")
Expand Down
1 change: 1 addition & 0 deletions src/crud_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ async def init_user_admin():
is_admin=True,
origem_unidade="SIAPE",
cod_unidade_autorizadora=1,
sistema_gerador=f"API PGD {os.getenv('TAG_NAME', 'dev-build') or 'dev-build'}",
)

async with db_session as session:
Expand Down
24 changes: 23 additions & 1 deletion src/docs/description.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@ participa do
> Conjunta.


### Identificação do sistema gerador

O sistema de informações que opera o Programa de Gestão no órgão, bem
como a sua versão, devem ser informados de duas maneiras:

1. No cadastro do usuário da API, no campo `sistema_gerador`, informar
nome e versão. Ver seção "**0. Auth**", abaixo.
2. No cabeçalho `User-Agent` da requisição feita pelo sistema ao acessar
a API, informar sequência de texto (string) padronizada, contendo:
- nome do sistema
- versão do sistema
- url onde se pode obter uma descrição ou informações gerais sobre o
sistema.

A string deve seguir o formato: `Nome do sistema/versão (+url)`.
Exemplo:

`User-Agent: Petrvs/2.1 (https://www.gov.br/servidor/pt-br/assuntos/programa-de-gestao/sistemas-e-api-de-dados/sistema-pgd-petrvs)`.


### Esquemas de dados

Explore a seção **Schemas** abaixo nesta documentação para ler as
Expand Down Expand Up @@ -61,7 +81,9 @@ Campos utilizados:
* `origem_unidade`: O nome do sistema de unidades utilizado nos campos que
se referem a unidades (SIAPE ou SIORG);
* `cod_unidade_autorizadora`: Unidade autorizadora do PGD à qual o usuário
pertence e está autorizado a enviar e consultar dados.
pertence e está autorizado a enviar e consultar dados;
* `sistema_gerador`: Nome e versão do software utilizado para operar o
Programa de Gestão e gerar os dados enviados. Exemplo: "Petrvs 2.1".


### 0.1. Permissões
Expand Down
6 changes: 6 additions & 0 deletions src/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,12 @@ class Users(Base):
comment="Unidade autorizadora do PGD à qual o usuário "
"pertence e está autorizado a enviar e consultar dados.",
)
sistema_gerador = Column(
String,
nullable=False,
comment="Nome e versão do software utilizado para operar o Programa "
'de Gestão e gerar os dados enviados. Exemplo: "Petrvs 2.1".',
)
data_atualizacao = Column(
DateTime,
onupdate=now(),
Expand Down
4 changes: 4 additions & 0 deletions src/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,10 @@ class UsersGetSchema(UsersInputSchema):
title="Código da organização que autorizou o PGD",
description=Users.cod_unidade_autorizadora.comment,
)
sistema_gerador: str = Field(
title="sistema gerador dos dados",
description=Users.sistema_gerador.comment,
)


class UsersSchema(UsersGetSchema):
Expand Down
10 changes: 9 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"disabled": False,
"origem_unidade": "SIAPE",
"cod_unidade_autorizadora": 1,
"sistema_gerador": "API PGD CI Tests",
},
{
"username": "[email protected]",
Expand All @@ -41,6 +42,7 @@
# "disabled": False, # defaults set to False
"origem_unidade": "SIAPE",
"cod_unidade_autorizadora": 2,
"sistema_gerador": "API PGD CI Tests",
},
]

Expand All @@ -49,6 +51,8 @@

API_BASE_URL = "http://localhost:5057"

TEST_USER_AGENT = "API PGD CI Test (+https://github.com/gestaogovbr/api-pgd)"


def get_bearer_token(username: str, password: str) -> str:
"""Login on api-pgd and returns token to nexts authenticaded calls.
Expand Down Expand Up @@ -79,7 +83,11 @@ def get_bearer_token(username: str, password: str) -> str:

def prepare_header(username: Optional[str], password: Optional[str]) -> dict:
"""Prepara o cabeçalho para ser utilizado em requisições."""
headers = {"accept": "application/json", "Content-Type": "application/json"}
headers = {
"accept": "application/json",
"Content-Type": "application/json",
"User-Agent": TEST_USER_AGENT,
}

if username and password:
user_token = get_bearer_token(username, password)
Expand Down
67 changes: 67 additions & 0 deletions tests/endpoints_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Testes relacionados com acessos aos endpoints.
"""

from typing import Optional

from fastapi import status
from httpx import Client
import pytest

from .conftest import TEST_USER_AGENT

# Entrypoint da API e redirecionamentos


def test_redirect_to_docs_html(client: Client):
"""Testa se o acesso por um navegador na raiz redireciona para o /docs."""
response = client.get("/", follow_redirects=False, headers={"Accept": "text/html"})

assert response.status_code == status.HTTP_307_TEMPORARY_REDIRECT
assert response.headers["Location"] == "/docs"


def test_redirect_to_entrypoint_json(client: Client):
"""Testa se o acesso por um script na raiz redireciona para o /openapi.json."""
response = client.get(
"/", follow_redirects=False, headers={"Accept": "application/json"}
)

assert response.status_code == status.HTTP_307_TEMPORARY_REDIRECT
assert response.headers["Location"] == "/openapi.json"


# Teste de cabeçalho User-Agent


@pytest.mark.parametrize(
"user_agent",
[
None,
TEST_USER_AGENT,
],
)
def test_user_agent_header(
truncate_participantes, # pylint: disable=unused-argument
example_part, # pylint: disable=unused-argument
user_agent: Optional[str],
input_part,
header_usr_1,
client: Client,
):
"""Testa efetuar requisições com e sem o cabeçalho user-agent."""
headers = header_usr_1.copy()
user_agent = headers["User-Agent"] or ""

response = client.get(
f"/organizacao/SIAPE/{input_part['cod_unidade_autorizadora']}"
f"/{input_part['cod_unidade_lotacao']}"
f"/participante/{input_part['matricula_siape']}",
headers=headers,
)

if user_agent:
assert response.status_code == status.HTTP_200_OK
else:
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json()["detail"] == "User-Agent header is required"
28 changes: 0 additions & 28 deletions tests/entrypoint_test.py

This file was deleted.

3 changes: 3 additions & 0 deletions tests/user_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"disabled": False,
"origem_unidade": "SIAPE",
"cod_unidade_autorizadora": 1,
"sistema_gerador": "API PGD CI Test",
},
# to get and delete (not created)
{
Expand All @@ -33,6 +34,7 @@
"disabled": False,
"origem_unidade": "SIAPE",
"cod_unidade_autorizadora": 1,
"sistema_gerador": "API PGD CI Test",
},
# to get without one of required fields (email, password, cod_unidade_autorizadora)
{
Expand All @@ -48,6 +50,7 @@
# "disabled": False, # defaults do False
"origem_unidade": "SIAPE",
"cod_unidade_autorizadora": 1,
"sistema_gerador": "API PGD CI Test",
},
]

Expand Down